diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js index e4e04123ebc..e58c51104c5 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js @@ -118,7 +118,10 @@ export default class ShortcutsIssuable extends Shortcuts { // Wait for the sidebar to trigger('click') open // so it doesn't cause our dropdown to close preemptively setTimeout(() => { - document.querySelector(`.block.${name} .shortcut-sidebar-dropdown-toggle`).click(); + const editBtn = + document.querySelector(`.block.${name} .shortcut-sidebar-dropdown-toggle`) || + document.querySelector(`.block.${name} .edit-link`); + editBtn.click(); }, DEBOUNCE_DROPDOWN_DELAY); return false; } diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js index 03fc785ff26..100ca5b36d9 100644 --- a/app/assets/javascripts/pages/projects/edit/index.js +++ b/app/assets/javascripts/pages/projects/edit/index.js @@ -6,9 +6,9 @@ import initFilePickers from '~/file_pickers'; import mountBadgeSettings from '~/pages/shared/mount_badge_settings'; import initProjectDeleteButton from '~/projects/project_delete_button'; import initServiceDesk from '~/projects/settings_service_desk'; +import initTransferProjectForm from '~/projects/settings/init_transfer_project_form'; import initSearchSettings from '~/search_settings'; import initSettingsPanels from '~/settings_panels'; -import setupTransferEdit from '~/transfer_edit'; import UserCallout from '~/user_callout'; import initTopicsTokenSelector from '~/projects/settings/topics'; import initProjectPermissionsSettings from '../shared/permissions'; @@ -26,7 +26,7 @@ initServiceDesk(); initProjectLoadingSpinner(); initProjectPermissionsSettings(); -setupTransferEdit('.js-project-transfer-form', 'select.select2'); +initTransferProjectForm(); dirtySubmitFactory(document.querySelectorAll('.js-general-settings-form, .js-mr-settings-form')); diff --git a/app/assets/javascripts/projects/settings/components/transfer_project_form.vue b/app/assets/javascripts/projects/settings/components/transfer_project_form.vue new file mode 100644 index 00000000000..b98e1101884 --- /dev/null +++ b/app/assets/javascripts/projects/settings/components/transfer_project_form.vue @@ -0,0 +1,63 @@ + + diff --git a/app/assets/javascripts/projects/settings/init_transfer_project_form.js b/app/assets/javascripts/projects/settings/init_transfer_project_form.js new file mode 100644 index 00000000000..47b49031dc9 --- /dev/null +++ b/app/assets/javascripts/projects/settings/init_transfer_project_form.js @@ -0,0 +1,53 @@ +import Vue from 'vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import TransferProjectForm from './components/transfer_project_form.vue'; + +const prepareNamespaces = (rawNamespaces = '') => { + const data = JSON.parse(rawNamespaces); + return { + group: data?.group.map(convertObjectPropsToCamelCase), + user: data?.user.map(convertObjectPropsToCamelCase), + }; +}; + +export default () => { + const el = document.querySelector('.js-transfer-project-form'); + if (!el) { + return false; + } + + const { + targetFormId = null, + targetHiddenInputId = null, + buttonText: confirmButtonText = '', + phrase: confirmationPhrase = '', + confirmDangerMessage = '', + namespaces = '', + } = el.dataset; + + return new Vue({ + el, + provide: { + confirmDangerMessage, + }, + render(createElement) { + return createElement(TransferProjectForm, { + props: { + confirmButtonText, + confirmationPhrase, + namespaces: prepareNamespaces(namespaces), + }, + on: { + selectNamespace: (id) => { + if (targetHiddenInputId && document.getElementById(targetHiddenInputId)) { + document.getElementById(targetHiddenInputId).value = id; + } + }, + confirm: () => { + if (targetFormId) document.getElementById(targetFormId)?.submit(); + }, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue index 30c96daf7e3..5bbe44b20b3 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue @@ -47,7 +47,7 @@ export default { actionPrimary() { return { text: this.confirmButtonText, - attributes: [{ variant: 'danger', disabled: !this.isValid }], + attributes: [{ variant: 'danger', disabled: !this.isValid, class: 'qa-confirm-button' }], }; }, }, @@ -95,7 +95,7 @@ export default { diff --git a/app/assets/javascripts/vue_shared/components/namespace_select/namespace_select.vue b/app/assets/javascripts/vue_shared/components/namespace_select/namespace_select.vue new file mode 100644 index 00000000000..7d2af7983d1 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/namespace_select/namespace_select.vue @@ -0,0 +1,93 @@ + + diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 04c40826d13..6af46e22d8f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -119,7 +119,10 @@ class ProjectsController < Projects::ApplicationController if @project.errors[:new_namespace].present? flash[:alert] = @project.errors[:new_namespace].first + return redirect_to edit_project_path(@project) end + + render_edit end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/repositories/lfs_api_controller.rb b/app/controllers/repositories/lfs_api_controller.rb index 30cafb6747e..c8e44bc8399 100644 --- a/app/controllers/repositories/lfs_api_controller.rb +++ b/app/controllers/repositories/lfs_api_controller.rb @@ -76,7 +76,10 @@ module Repositories existing_oids = project.lfs_objects_oids(oids: objects_oids) objects.each do |object| - object[:actions] = upload_actions(object) unless existing_oids.include?(object[:oid]) + next if existing_oids.include?(object[:oid]) + next if should_auto_link? && oids_from_fork.include?(object[:oid]) && link_to_project!(object) + + object[:actions] = upload_actions(object) end objects @@ -150,6 +153,26 @@ module Repositories Gitlab::LfsToken.new(user).basic_encoding end + + def should_auto_link? + return false unless Feature.enabled?(:lfs_auto_link_fork_source, project) + return false unless project.forked? + + # Sanity check in case for some reason the user doesn't have access to the parent + can?(user, :download_code, project.fork_source) + end + + def oids_from_fork + @oids_from_fork ||= project.lfs_objects_oids_from_fork_source(oids: objects_oids) + end + + def link_to_project!(object) + lfs_object = LfsObject.for_oid_and_size(object[:oid], object[:size]) + + return unless lfs_object + + LfsObjectsProject.link_to_project!(lfs_object, project) + end end end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 106df168080..6acec417a75 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -88,6 +88,13 @@ module NamespacesHelper group.namespace_settings.public_send(method_name, **args) # rubocop:disable GitlabSecurity/PublicSend end + def namespaces_as_json(selected = :current_user) + { + group: formatted_namespaces(current_user.manageable_groups_with_routes), + user: formatted_namespaces([current_user.namespace]) + }.to_json + end + private # Many importers create a temporary Group, so use the real @@ -119,6 +126,17 @@ module NamespacesHelper [group_label.camelize, elements] end + + def formatted_namespaces(namespaces) + namespaces.sort_by(&:human_name).map! do |n| + { + id: n.id, + display_path: n.full_path, + human_name: n.human_name, + name: n.name + } + end + end end NamespacesHelper.prepend_mod_with('NamespacesHelper') diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 9765ac6f2e9..caeffae7bda 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -13,6 +13,7 @@ class LfsObject < ApplicationRecord scope :with_files_stored_locally, -> { where(file_store: LfsObjectUploader::Store::LOCAL) } scope :with_files_stored_remotely, -> { where(file_store: LfsObjectUploader::Store::REMOTE) } scope :for_oids, -> (oids) { where(oid: oids) } + scope :for_oid_and_size, -> (oid, size) { find_by(oid: oid, size: size) } validates :oid, presence: true, uniqueness: true diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb index e5632ff2842..bf6d1394569 100644 --- a/app/models/lfs_objects_project.rb +++ b/app/models/lfs_objects_project.rb @@ -21,9 +21,19 @@ class LfsObjectsProject < ApplicationRecord scope :project_id_in, ->(ids) { where(project_id: ids) } scope :lfs_object_in, -> (lfs_objects) { where(lfs_object: lfs_objects) } + def self.link_to_project!(lfs_object, project) + # We can't use an upsert here because there is no uniqueness constraint: + # https://gitlab.com/gitlab-org/gitlab/-/issues/347466 + self.safe_find_or_create_by!(lfs_object_id: lfs_object.id, project_id: project.id) # rubocop:disable Performance/ActiveRecordSubtransactionMethods + end + + def self.update_statistics_for_project_id(project_id) + ProjectCacheWorker.perform_async(project_id, [], [:lfs_objects_size]) # rubocop:disable CodeReuse/Worker + end + private def update_project_statistics - ProjectCacheWorker.perform_async(project_id, [], [:lfs_objects_size]) + self.class.update_statistics_for_project_id(project_id) end end diff --git a/app/models/project.rb b/app/models/project.rb index 6d35534bb6a..088a2f9ea27 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1569,6 +1569,12 @@ class Project < ApplicationRecord oids(lfs_objects, oids: oids) end + def lfs_objects_oids_from_fork_source(oids: []) + return [] unless forked? + + oids(fork_source.lfs_objects, oids: oids) + end + def personal? !group end diff --git a/app/services/groups/update_shared_runners_service.rb b/app/services/groups/update_shared_runners_service.rb index eb6b46a5613..c09dce0761f 100644 --- a/app/services/groups/update_shared_runners_service.rb +++ b/app/services/groups/update_shared_runners_service.rb @@ -8,7 +8,7 @@ module Groups validate_params update_shared_runners - update_pending_builds! + update_pending_builds_async success @@ -28,12 +28,18 @@ module Groups group.update_shared_runners_setting!(params[:shared_runners_setting]) end - def update_pending_builds! - return unless group.previous_changes.include?('shared_runners_enabled') + def update_pending_builds? + group.previous_changes.include?('shared_runners_enabled') + end - update_params = { instance_runners_enabled: group.shared_runners_enabled } + def update_pending_builds_async + return unless update_pending_builds? - ::Ci::UpdatePendingBuildService.new(group, update_params).execute + group.run_after_commit_or_now do |group| + pending_builds_params = { instance_runners_enabled: group.shared_runners_enabled } + + ::Ci::UpdatePendingBuildService.new(group, pending_builds_params).execute + end end end end diff --git a/app/views/projects/_transfer.html.haml b/app/views/projects/_transfer.html.haml index e48008e1cc6..9f9daa7ec6f 100644 --- a/app/views/projects/_transfer.html.haml +++ b/app/views/projects/_transfer.html.haml @@ -1,8 +1,11 @@ - return unless can?(current_user, :change_namespace, @project) +- form_id = "transfer-project-form" +- hidden_input_id = "new_namespace_id" +- initial_data = { namespaces: namespaces_as_json, button_text: s_('ProjectSettings|Transfer project'), confirm_danger_message: transfer_project_message(@project), phrase: @project.name, target_form_id: form_id, target_hidden_input_id: hidden_input_id } .sub-section %h4.danger-title= _('Transfer project') - = form_for @project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } do |f| + = form_for @project, url: transfer_project_path(@project), method: :put, html: { class: 'js-project-transfer-form', id: form_id } do |f| .form-group - link_start = ''.html_safe % { url: help_page_path('user/project/settings/index', anchor: 'transferring-an-existing-project-into-another-namespace') } %p= _("Transfer your project into another namespace. %{link_start}Learn more.%{link_end}").html_safe % { link_start: link_start, link_end: ''.html_safe } @@ -11,7 +14,6 @@ %li= _('You can only transfer the project to namespaces you manage.') %li= _('You will need to update your local repositories to point to the new location.') %li= _('Project visibility level will be changed to match namespace rules when transferring to a group.') + = hidden_field_tag(hidden_input_id) = label_tag :new_namespace_id, _('Select a new namespace'), class: 'gl-font-weight-bold' - .form-group - = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2' - = f.submit 'Transfer project', class: "gl-button btn btn-danger js-legacy-confirm-danger qa-transfer-button", data: { "confirm-danger-message" => transfer_project_message(@project) } + .js-transfer-project-form{ data: initial_data } diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml deleted file mode 100644 index 6d083c5c516..00000000000 --- a/app/views/projects/transfer.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - location.href = "#{edit_project_path(@project)}"; diff --git a/config/feature_flags/development/lfs_auto_link_fork_source.yml b/config/feature_flags/development/lfs_auto_link_fork_source.yml new file mode 100644 index 00000000000..b094d56294c --- /dev/null +++ b/config/feature_flags/development/lfs_auto_link_fork_source.yml @@ -0,0 +1,8 @@ +--- +name: lfs_auto_link_fork_source +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75972 +rollout_issue_url: +milestone: '14.6' +type: development +group: group::source code +default_enabled: false diff --git a/config/metrics/counts_28d/20210216184559_ci_templates_total_unique_counts_monthly.yml b/config/metrics/counts_28d/20210216184559_ci_templates_total_unique_counts_monthly.yml index c80a7dada11..637b02b93ed 100644 --- a/config/metrics/counts_28d/20210216184559_ci_templates_total_unique_counts_monthly.yml +++ b/config/metrics/counts_28d/20210216184559_ci_templates_total_unique_counts_monthly.yml @@ -49,6 +49,7 @@ options: - p_ci_templates_security_api_fuzzing_latest - p_ci_templates_security_secure_binaries - p_ci_templates_security_dast_api + - p_ci_templates_security_dast_api_latest - p_ci_templates_security_container_scanning - p_ci_templates_security_dast_latest - p_ci_templates_security_dependency_scanning @@ -153,6 +154,7 @@ options: - p_ci_templates_implicit_security_api_fuzzing_latest - p_ci_templates_implicit_security_secure_binaries - p_ci_templates_implicit_security_dast_api + - p_ci_templates_implicit_security_dast_api_latest - p_ci_templates_implicit_security_container_scanning - p_ci_templates_implicit_security_dast_latest - p_ci_templates_implicit_security_dependency_scanning diff --git a/config/metrics/counts_28d/20211122134101_p_ci_templates_implicit_security_dast_api_latest_monthly.yml b/config/metrics/counts_28d/20211122134101_p_ci_templates_implicit_security_dast_api_latest_monthly.yml new file mode 100644 index 00000000000..4b9bf4120f5 --- /dev/null +++ b/config/metrics/counts_28d/20211122134101_p_ci_templates_implicit_security_dast_api_latest_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_security_dast_api_latest_monthly +description: Monthly counts for DAST API latest CI template +product_section: sec +product_stage: secure +product_group: dynamic_analysis +product_category: dynamic_application_security_testing +value_type: number +status: active +milestone: '14.6' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73876 +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_implicit_security_dast_api_latest diff --git a/config/metrics/counts_28d/20211201194407_p_ci_templates_security_dast_api_latest_monthly.yml b/config/metrics/counts_28d/20211201194407_p_ci_templates_security_dast_api_latest_monthly.yml new file mode 100644 index 00000000000..cfcffe5caca --- /dev/null +++ b/config/metrics/counts_28d/20211201194407_p_ci_templates_security_dast_api_latest_monthly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_security_dast_api_latest_monthly +description: Monthly counts for DAST API latest CI template +product_section: sec +product_stage: secure +product_group: dynamic_analysis +product_category: dynamic_application_security_testing +value_type: number +status: active +milestone: "14.6" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73876 +time_frame: 28d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_security_dast_api_latest diff --git a/config/metrics/counts_7d/20210216184557_ci_templates_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20210216184557_ci_templates_total_unique_counts_weekly.yml index 31813735ea4..8d545b91d1f 100644 --- a/config/metrics/counts_7d/20210216184557_ci_templates_total_unique_counts_weekly.yml +++ b/config/metrics/counts_7d/20210216184557_ci_templates_total_unique_counts_weekly.yml @@ -49,6 +49,7 @@ options: - p_ci_templates_security_api_fuzzing_latest - p_ci_templates_security_secure_binaries - p_ci_templates_security_dast_api + - p_ci_templates_security_dast_api_latest - p_ci_templates_security_container_scanning - p_ci_templates_security_dast_latest - p_ci_templates_security_dependency_scanning @@ -153,6 +154,7 @@ options: - p_ci_templates_implicit_security_api_fuzzing_latest - p_ci_templates_implicit_security_secure_binaries - p_ci_templates_implicit_security_dast_api + - p_ci_templates_implicit_security_dast_api_latest - p_ci_templates_implicit_security_container_scanning - p_ci_templates_implicit_security_dast_latest - p_ci_templates_implicit_security_dependency_scanning diff --git a/config/metrics/counts_7d/20211122134101_p_ci_templates_implicit_security_dast_api_latest_weekly.yml b/config/metrics/counts_7d/20211122134101_p_ci_templates_implicit_security_dast_api_latest_weekly.yml new file mode 100644 index 00000000000..8d0415dedd2 --- /dev/null +++ b/config/metrics/counts_7d/20211122134101_p_ci_templates_implicit_security_dast_api_latest_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_implicit_security_dast_api_latest_weekly +description: Weekly counts for DAST API latest CI template +product_section: sec +product_stage: secure +product_group: dynamic_analysis +product_category: dynamic_application_security_testing +value_type: number +status: active +milestone: '14.6' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73876 +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_implicit_security_dast_api_latest diff --git a/config/metrics/counts_7d/20211201194402_p_ci_templates_security_dast_api_latest_weekly.yml b/config/metrics/counts_7d/20211201194402_p_ci_templates_security_dast_api_latest_weekly.yml new file mode 100644 index 00000000000..1d4f024b361 --- /dev/null +++ b/config/metrics/counts_7d/20211201194402_p_ci_templates_security_dast_api_latest_weekly.yml @@ -0,0 +1,25 @@ +--- +key_path: redis_hll_counters.ci_templates.p_ci_templates_security_dast_api_latest_weekly +description: Weekly counts for DAST API latest CI template +product_section: sec +product_stage: secure +product_group: dynamic_analysis +product_category: dynamic_application_security_testing +value_type: number +status: active +milestone: "14.6" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73876 +time_frame: 7d +data_source: redis_hll +data_category: optional +instrumentation_class: RedisHLLMetric +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +options: + events: + - p_ci_templates_security_dast_api_latest diff --git a/data/deprecations/14-6-deprecation-secure-dependency-scanning-bundler-audit.yml b/data/deprecations/14-6-deprecation-secure-dependency-scanning-bundler-audit.yml new file mode 100644 index 00000000000..23e59da21e4 --- /dev/null +++ b/data/deprecations/14-6-deprecation-secure-dependency-scanning-bundler-audit.yml @@ -0,0 +1,16 @@ +- name: "Deprecation of bundler-audit Dependency Scanning tool" # The name of the feature to be deprecated + announcement_milestone: "14.6" # The milestone when this feature was first announced as deprecated. + announcement_date: "2021-12-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + removal_milestone: "15.0" # The milestone when this feature is planned to be removed + body: | # Do not modify this line, instead modify the lines below. + As of 14.6 bundler-audit is being deprecated from Dependency Scanning. It will continue to be in our CI/CD template while deprecated. We are removing bundler-audit from Dependency Scanning on May 22, 2022 in 15.0. After this removal Ruby scanning functionality will not be affected as it is still being covered by Gemnasium. + + If you have explicitly excluded bundler-audit using DS_EXCLUDED_ANALYZERS you will need to clean up (remove the reference) in 15.0. If you have customized your pipeline's Dependency Scanning configuration, for example to edit the `bundler-audit-dependency_scanning` job, you will want to switch to gemnasium-dependency_scanning before removal in 15.0, to prevent your pipeline from failing. If you have not used the DS_EXCLUDED_ANALYZERS to reference bundler-audit, or customized your template specifically for bundler-audit, you will not need to take action. +# The following items are not published on the docs page, but may be used in the future. + stage: secure # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth + tiers: ultimate # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate] + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/289832 # (optional) This is a link to the deprecation issue in GitLab + documentation_url: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/analyzers.html # (optional) This is a link to the current documentation page + image_url: # (optional) This is a link to a thumbnail image depicting the feature + video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg + removal_date: 2022-05-22 # (optional - may be required in the future) YYYY-MM-DD format. This should almost always be the 22nd of a month (YYYY-MM-22), the date of the milestone release when this feature is planned to be removed diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index 71eee78c168..06ad16bbcba 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -9,8 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w GitLab offers a way to view the changes made within the GitLab server for owners and administrators on a [paid plan](https://about.gitlab.com/pricing/). -GitLab system administrators can also take advantage of the logs located on the -file system. See [the logs system documentation](logs.md#audit_jsonlog) for more details. +GitLab system administrators can also view all audit events by accessing the [`audit_json.log` file](logs.md#audit_jsonlog). You can: diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md index 6b390cfc77a..62fbf9e7166 100644 --- a/doc/administration/incoming_email.md +++ b/doc/administration/incoming_email.md @@ -10,7 +10,7 @@ GitLab has several features based on receiving incoming email messages: - [Reply by Email](reply_by_email.md): allow GitLab users to comment on issues and merge requests by replying to notification email. -- [New issue by email](../user/project/issues/managing_issues.md#new-issue-via-email): +- [New issue by email](../user/project/issues/managing_issues.md#by-sending-an-email): allow GitLab users to create a new issue by sending an email to a user-specific email address. - [New merge request by email](../user/project/merge_requests/creating_merge_requests.md#by-sending-an-email): diff --git a/doc/administration/index.md b/doc/administration/index.md index ef9c56c1615..d78c9d80b5f 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -133,7 +133,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. - Instances. - [Auditor users](auditor_users.md): Users with read-only access to all projects, groups, and other resources on the GitLab instance. - [Incoming email](incoming_email.md): Configure incoming emails to allow - users to [reply by email](reply_by_email.md), create [issues by email](../user/project/issues/managing_issues.md#new-issue-via-email) and + users to [reply by email](reply_by_email.md), create [issues by email](../user/project/issues/managing_issues.md#by-sending-an-email) and [merge requests by email](../user/project/merge_requests/creating_merge_requests.md#by-sending-an-email), and to enable [Service Desk](../user/project/service_desk.md). - [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a basic Postfix mail server with IMAP authentication on Ubuntu for incoming diff --git a/doc/development/cicd/cicd_reference_documentation_guide.md b/doc/development/cicd/cicd_reference_documentation_guide.md index aa3888cd866..892c9814c7f 100644 --- a/doc/development/cicd/cicd_reference_documentation_guide.md +++ b/doc/development/cicd/cicd_reference_documentation_guide.md @@ -9,14 +9,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w The CI/CD YAML reference uses a standard style to make it easier to use and update. The reference information should be kept as simple as possible, and expanded details -and examples documented in a separate page. +and examples should be documented on other pages. ## YAML reference structure Every YAML keyword must have its own section in the reference. The sections should be nested so that the keywords follow a logical tree structure. For example: -```plaintext +```markdown ### `artifacts` #### `artifacts:name` #### `artifacts:paths` @@ -27,128 +27,127 @@ be nested so that the keywords follow a logical tree structure. For example: ## YAML reference style -Each keyword entry in the reference should use the following style: +Each keyword entry in the reference: -````markdown -### `keyword-name` +- Must have a simple introductory section. The introduction should give the fundamental + information needed to use the keyword. Advanced details and tasks should be in + feature pages, not the reference page. -> Version information +- Must use the keyword name as the title, for example: -Keyword description and main details. + ```markdown + ### `artifacts` + ``` -**Keyword type**: +- Should include the following sections: + - [Keyword type](#keyword-type) + - [Possible inputs](#possible-inputs) + - [Example of `keyword-name`](#example-of-keyword-name) +- (Optional) Can also include the following sections when needed: + - [Additional details](#additional-details) + - [Related topics](#related-topics) +The keyword name must always be in backticks without a final `:`, like `artifacts`, not `artifacts:`. +If it is a subkey of another keyword, write out all the subkeys to the "parent" key the first time it +is used, like `artifacts:reports:dast`. Afterwards, you can use just the subkey alone, like `dast`. + +## Keyword type + +The keyword can be either a job or global keyword. If it can be used in a `default` +section, make not of that as well, for example: + +- `**Keyword type**: Global keyword.` +- `**Keyword type**: Job keyword. You can use it only as part of a job.` +- ``**Keyword type**: Job keyword. You can use it only as part of a job or in the [`default:` section](#default).`` + +### Possible inputs + +List all the possible inputs, and any extra details about the inputs, such as defaults +or changes due to different GitLab versions, for example: + +```markdown **Possible inputs**: -**Example of `keyword-name`**: - -(optional) In this example... - -(optional) **Additional details**: - -- List of extra details. - -(optional) **Related topics**: - -- List of links to topics related to the keyword. -```` - -- ``### `keyword-name` ``: The keyword name must always be in backticks. - If it is a subkey of another keyword, write out all the keywords, with each separated - by `:`, for example: `artifacts:reports:dast`. - -- ``> Version information``: The [version history details](../documentation/styleguide/index.md#version-text-in-the-version-history). - If the keyword is feature flagged, see the [feature flag documentation guide](../documentation/feature_flags.md) - as well. - -- `Keyword description and main details.`: A simple description of the keyword, and - how to use it. Additional use cases and benefits should be added to a page outside - the reference document. Link to that document in this section. - -- `**Keyword type**:`: Most keywords are defined at the job level, like `script`, - or at the pipeline level, like `stages`. Add the appropriate line: - - - `**Keyword type**: Job keyword. You can use it only as part of a job.` - - `**Keyword type**: Pipeline keyword. You cannot use it as part of a job.` - - If a keyword can be used at both the job and pipeline level, like `variables`, - explain it in detail instead of using the pre-written lines above. - -- `**Possible inputs**:`: Explain in detail which inputs the keyword can accept. - You can add the details in a sentence, paragraph, or list. - -- ``**Example of `keyword-name`**:``: An example configuration that uses the keyword. - Do not add extra keywords that are not required to understand the behavior. - -- (optional) `In this example...`: If the example needs extra details, - add the clarification text below the example. - -- (optional) `**Additional details**:` If there are any caveats or extra details you - want to document along with the keyword, add each one as a list item here. - -- (optional) `**Related topics**:` If there are any other keywords or pages that - relate to this keyword, add these links as list items here. - -### YAML reference style example - -See the [`only:changes` / `except:changes`](../../ci/yaml/index.md#onlychanges--exceptchanges) -documentation for an example of the YAML reference style. The following example is a -shortened version of that documentation's Markdown: - -````markdown -#### `only:changes` / `except:changes` - -> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/19232) in GitLab 11.4. - -Use the `changes` keyword with `only` to run a job, or with `except` to skip a job, -when a Git push event modifies a file. - -Use `changes` in pipelines with the following refs: - -- `branches` -- `external_pull_requests` -- `merge_requests` (see additional details about [using `only:changes` with pipelines for merge requests](../jobs/job_control.md#use-onlychanges-with-pipelines-for-merge-requests)) - -**Keyword type**: Job keyword. You can use it only as part of a job. - -**Possible inputs**: An array including any number of: - -- Paths to files. -- Wildcard paths for single directories, for example `path/to/directory/*`, or a directory - and all its subdirectories, for example `path/to/directory/**/*`. - -**Example of `only:changes`**: - -```yaml -docker build: - script: docker build -t my-image:$CI_COMMIT_REF_SLUG . - only: - refs: - - branches - changes: - - Dockerfile - - docker/scripts/* - - dockerfiles/**/* +- `true` (default if not defined) or `false`. ``` -In this example, `docker build` only runs in branch pipelines, and only if at least one of -these files changed: +```markdown +**Possible inputs**: -- `Dockerfile`. -- Any file in `docker/scripts` -- Any file in `dockerfiles/` or any of its subdirectories. +- A single exit code. +- An array of exit codes. +``` +```markdown +**Possible inputs**: + +- A string with the long description. +- The path to a file that contains the description. Introduced in [GitLab 13.7](https://gitlab.com/gitlab-org/release-cli/-/merge_requests/67). + - The file location must be relative to the project directory (`$CI_PROJECT_DIR`). + - If the file is a symbolic link, it must be in the `$CI_PROJECT_DIR`. + - The `./path/to/file` and filename can't contain spaces. +``` + +### Example of `keyword-name` + +An example of the keyword. Use the minimum number of other keywords necessary +to make the example valid. If the example needs explanation, add it after the example, +for example: + +````markdown +**Example of `dast`**: + +```yaml +stages: + - build + - dast + +include: + - template: DAST.gitlab-ci.yml + +dast: + dast_configuration: + site_profile: "Example Co" + scanner_profile: "Quick Passive Test" +``` + +In this example, the `dast` job extends the `dast` configuration added with the `include:` keyword +to select a specific site profile and scanner profile. +```` + +### Additional details + +The additional details should be an unordered list of extra information that is +useful to know, but not important enough to put in the introduction. This information +can include changes introduced in different GitLab versions. For example: + +```markdown **Additional details**: -- If you use refs other than `branches`, `external_pull_requests`, or `merge_requests`, - `changes` can't determine if a given file is new or old and always returns `true`. -- If you use `only: changes` with other refs, jobs ignore the changes and always run. -- If you use `except: changes` with other refs, jobs ignore the changes and never run. +- The expiration time period begins when the artifact is 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). +- To override the expiration date and protect artifacts from being automatically deleted: + - Select **Keep** on the job page. + - [In GitLab 13.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/22761), set the value of + `expire_in` to `never`. +``` +### Related topics + +The related topics should be an unordered list of crosslinks to related pages, including: + +- Specific tasks that you can accomplish with the keyword. +- Advanced examples of the keyword. +- Other related keywords that can be used together with this keyword. + +For example: + +```markdown **Related topics**: -- [`only: changes` and `except: changes` examples](../jobs/job_control.md#onlychanges--exceptchanges-examples). -- If you use `changes` with [only allow merge requests to be merged if the pipeline succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md#only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds), - you should [also use `only:merge_requests`](../jobs/job_control.md#use-onlychanges-with-pipelines-for-merge-requests). -- Use `changes` with [scheduled pipelines](../jobs/job_control.md#use-onlychanges-with-scheduled-pipelines). -```` +- You can specify a [fallback cache key](../caching/index.md#use-a-fallback-cache-key) + to use if the specified `cache:key` is not found. +- You can [use multiple cache keys](../caching/index.md#use-multiple-caches) in a single job. +- See the [common `cache` use cases](../caching/index.md#common-use-cases-for-caches) for more + `cache:key` examples. +``` diff --git a/doc/gitlab-basics/index.md b/doc/gitlab-basics/index.md index 5e4ec0e8c51..5ba5366eafa 100644 --- a/doc/gitlab-basics/index.md +++ b/doc/gitlab-basics/index.md @@ -29,7 +29,7 @@ The following are guides to basic GitLab functionality: - [Feature branch workflow](feature_branch_workflow.md). - [Fork a project](../user/project/repository/forking_workflow.md#creating-a-fork), to duplicate projects so they can be worked on in parallel. - [Add a file](add-file.md), to add new files to a project's repository. -- [Create an issue](../user/project/issues/managing_issues.md#create-a-new-issue), +- [Create an issue](../user/project/issues/managing_issues.md#create-an-issue), to start collaborating within a project. - [Create a merge request](../user/project/merge_requests/creating_merge_requests.md), to request changes made in a branch be merged into a project's repository. diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 066c91b7dd9..20c6d692cf6 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -116,6 +116,14 @@ Long term service and support (LTSS) for SUSE Linux Enterprise Server (SLES) 12 Announced: 2021-11-22 +### Deprecation of bundler-audit Dependency Scanning tool + +As of 14.6 bundler-audit is being deprecated from Dependency Scanning. It will continue to be in our CI/CD template while deprecated. We are removing bundler-audit from Dependency Scanning on May 22, 2022 in 15.0. After this removal Ruby scanning functionality will not be affected as it is still being covered by Gemnasium. + +If you have explicitly excluded bundler-audit using DS_EXCLUDED_ANALYZERS you will need to clean up (remove the reference) in 15.0. If you have customized your pipeline's Dependency Scanning configuration, for example to edit the `bundler-audit-dependency_scanning` job, you will want to switch to gemnasium-dependency_scanning before removal in 15.0, to prevent your pipeline from failing. If you have not used the DS_EXCLUDED_ANALYZERS to reference bundler-audit, or customized your template specifically for bundler-audit, you will not need to take action. + +Announced: 2021-12-22 + ### GitLab Serverless [GitLab Serverless](https://docs.gitlab.com/ee/user/project/clusters/serverless/) is a feature set to support Knative-based serverless development with automatic deployments and monitoring. diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md index 5b19a54bd91..66e5931fa4c 100644 --- a/doc/user/project/description_templates.md +++ b/doc/user/project/description_templates.md @@ -71,7 +71,7 @@ To create the `.gitlab/issue_templates` directory: 1. Select **New directory**. 1. Name your directory `issue_templates` and commit to your default branch. -To check if this has worked correctly, [create a new issue](issues/managing_issues.md#create-a-new-issue) +To check if this has worked correctly, [create a new issue](issues/managing_issues.md#create-an-issue) and see if you can choose a description template. ## Create a merge request template diff --git a/doc/user/project/issues/img/new_issue_from_email.png b/doc/user/project/issues/img/new_issue_from_email.png deleted file mode 100644 index 6da899ea37c..00000000000 Binary files a/doc/user/project/issues/img/new_issue_from_email.png and /dev/null differ diff --git a/doc/user/project/issues/img/new_issue_from_issue_board.png b/doc/user/project/issues/img/new_issue_from_issue_board.png deleted file mode 100644 index 30a1ffb9011..00000000000 Binary files a/doc/user/project/issues/img/new_issue_from_issue_board.png and /dev/null differ diff --git a/doc/user/project/issues/img/new_issue_from_open_issue_v13_6.png b/doc/user/project/issues/img/new_issue_from_open_issue_v13_6.png deleted file mode 100644 index 902aa40614a..00000000000 Binary files a/doc/user/project/issues/img/new_issue_from_open_issue_v13_6.png and /dev/null differ diff --git a/doc/user/project/issues/img/new_issue_from_projects_dashboard.png b/doc/user/project/issues/img/new_issue_from_projects_dashboard.png deleted file mode 100644 index 474ca2b45c0..00000000000 Binary files a/doc/user/project/issues/img/new_issue_from_projects_dashboard.png and /dev/null differ diff --git a/doc/user/project/issues/img/new_issue_from_tracker_list.png b/doc/user/project/issues/img/new_issue_from_tracker_list.png deleted file mode 100644 index 66793cb44fa..00000000000 Binary files a/doc/user/project/issues/img/new_issue_from_tracker_list.png and /dev/null differ diff --git a/doc/user/project/issues/img/new_issue_v13_1.png b/doc/user/project/issues/img/new_issue_v13_1.png deleted file mode 100644 index a66846c234e..00000000000 Binary files a/doc/user/project/issues/img/new_issue_v13_1.png and /dev/null differ diff --git a/doc/user/project/issues/img/select_project_from_group_level_issue_tracker_v13_11.png b/doc/user/project/issues/img/select_project_from_group_level_issue_tracker_v13_11.png deleted file mode 100644 index 4612ae254d4..00000000000 Binary files a/doc/user/project/issues/img/select_project_from_group_level_issue_tracker_v13_11.png and /dev/null differ diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index c535d16bc0f..85c30fa4ae5 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -29,7 +29,7 @@ To learn how the GitLab Strategic Marketing department uses GitLab issues with [ ## Related topics -- [Create issues](managing_issues.md#create-a-new-issue) +- [Create issues](managing_issues.md#create-an-issue) - [Create an issue from a template](../../project/description_templates.md#use-the-templates) - [Edit issues](managing_issues.md#edit-an-issue) - [Move issues](managing_issues.md#moving-issues) diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md index d357f45b1e7..0f02eec0ce9 100644 --- a/doc/user/project/issues/managing_issues.md +++ b/doc/user/project/issues/managing_issues.md @@ -4,148 +4,228 @@ 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/#assignments --- -# Managing issues **(FREE)** +# Manage issues **(FREE)** [GitLab Issues](index.md) are the fundamental medium for collaborating on ideas and planning work in GitLab. -Key actions for issues include: +## Create an issue -- [Creating issues](#create-a-new-issue) -- [Moving issues](#moving-issues) -- [Closing issues](#closing-issues) -- [Deleting issues](#deleting-issues) -- [Promoting issues](#promote-an-issue-to-an-epic) **(PREMIUM)** - -## Create a new issue - -When you create a new issue, you are prompted to enter the fields of the issue. +When you create an issue, you are prompted to enter the fields of the issue. If you know the values you want to assign to an issue, you can use [quick actions](../quick_actions.md) to enter them. -While creating an issue, you can associate it to an existing epic from current group by -selecting it using **Epic** dropdown. +You can create an issue in many ways in GitLab: -### Accessing the New Issue form +- [From a project](#from-a-project) +- [From a group](#from-a-group) +- [From another issue](#from-another-issue) +- [From an issue board](#from-an-issue-board) +- [By sending an email](#by-sending-an-email) +- Using a URL with prefilled fields +- [Using Service Desk](#using-service-desk) -There are many ways to get to the New Issue form from a project's page: +### From a project -- Navigate to your **Project's Dashboard** > **Issues** > **New Issue**: +Prerequisites: - ![New issue from the issue list view](img/new_issue_from_tracker_list.png) +- You must have at least the [Guest role](../../permissions.md) for the project. -- From an **open issue** in your project, click the vertical ellipsis (**{ellipsis_v}**) button - to open a dropdown menu, and then click **New Issue** to create a new issue in the same project: +To create an issue: - ![New issue from an open issue](img/new_issue_from_open_issue_v13_6.png) +1. On the top bar, select **Menu > Projects** and find your project. +1. Either: -- From your **Project's Dashboard**, click the plus sign (**+**) to open a dropdown - menu with a few options. Select **New Issue** to create an issue in that project: + - On the left sidebar, select **Issues**, and then, in the top right corner, select **New issue**. + - On the top bar, select the plus sign (**{plus-square}**) and then, under **This project**, + select **New issue**. - ![New issue from a project's dashboard](img/new_issue_from_projects_dashboard.png) +1. Complete the [fields](#fields-in-the-new-issue-form). +1. Select **Create issue**. -- From an **issue board**, create a new issue by clicking on the plus sign (**+**) at the top of a list. - It opens a new issue for that project, pre-labeled with its respective list. +The newly created issue opens. - ![From the issue board](img/new_issue_from_issue_board.png) +### From a group -### Elements of the New Issue form +Issues belong to projects, but when you're in a group, you can access and create issues that belong +to the projects in the group. -> 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. +Prerequisites: -![New issue from the issues list](img/new_issue_v13_1.png) +- You must have at least the [Guest role](../../permissions.md) for a project in the group. -When you're creating a new issue, these are the fields you can fill in: +To create an issue from a group: -- Title -- Description -- Checkbox to make the issue [confidential](confidential_issues.md) -- Assignee -- Weight -- [Epic](../../group/epics/index.md) -- Due date -- Milestone -- Labels - -### New issue from the group-level issue tracker - -To visit the issue tracker for all projects in your group: - -1. Go to the group dashboard. +1. On the top bar, select **Menu > Groups** and find your group. 1. On the left sidebar, select **Issues**. -1. In the top-right, select the **Select project to create issue** button. +1. In the top right corner, select **Select project to create issue**. 1. Select the project you'd like to create an issue for. The button now reflects the selected project. -1. Select the button to create an issue in the selected project. +1. Select **New issue in ``**. +1. Complete the [fields](#fields-in-the-new-issue-form). +1. Select **Create issue**. -![Select project to create issue](img/select_project_from_group_level_issue_tracker_v13_11.png) +The newly created issue opens. The project you selected most recently becomes the default for your next visit. -This should save you a lot of time and clicks, if you mostly create issues for the same project. +This can save you a lot of time and clicks, if you mostly create issues for the same project. -### New issue via Service Desk +### From another issue -Enable [Service Desk](../service_desk.md) for your project and offer email support. -Now, when your customer sends a new email, a new issue can be created in -the appropriate project and followed up from there. +> New issue becoming linked to the issue of origin [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68226) in GitLab 14.3. -### New issue via email +You can create a new issue from an existing one. The two issues can then be marked as related. -A link to **Email a new issue to this project** is displayed at the bottom of a project's -**Issues List** page. The link is shown only if your GitLab instance has [incoming email](../../../administration/incoming_email.md) -configured and there is at least one issue in the issue list. +Prerequisites: -![Bottom of a project issues page](img/new_issue_from_email.png) +- You must have at least the [Guest role](../../permissions.md) for the project. -When you click this link, an email address is generated and displayed, which should be used -by **you only**, to create issues in this project. You can save this address as a -contact in your email client for quick access. +To create an issue from another issue: + +1. In an existing issue, select the vertical ellipsis (**{ellipsis_v}**). +1. Select **New issue**. +1. Complete the [fields](#fields-in-the-new-issue-form). + The new issue's description is prefilled with `Related to #123`, where `123` is the ID of the + issue of origin. If you keep this mention in the description, the two issues become + [linked](related_issues.md). +1. Select **Create issue**. + +The newly created issue opens. + +### From an issue board + +You can create a new issue from an [issue board](../issue_board.md). + +Prerequisites: + +- You must have at least the [Guest role](../../permissions.md) for the project. + +To create an issue from a project issue board: + +1. On the top bar, select **Menu > Projects** and find your project. +1. Select **Issues > Boards**. +1. At the top of a board list, select **New issue** (**{plus-square}**). +1. Enter the issue's title. +1. Select **Create issue**. + +To create an issue from a group issue board: + +1. On the top bar, select **Menu > Groups** and find your group. +1. Select **Issues > Boards**. +1. At the top of a board list, select **New issue** (**{plus-square}**). +1. Enter the issue's title. +1. Under **Projects**, select the project in the group that the issue should belong to. +1. Select **Create issue**. + +The issue is created and shows up in the board list. It shares the list's characteristic, so, for +example, if the list is scoped to a label `Frontend`, the new issue also has this label. + +### By sending an email + +> Generated email address format changed in GitLab 11.7. +> The older format is still supported, so existing aliases and contacts still work. + +You can send an email to create an issue in a project on the project's +**Issues List** page. + +Prerequisites: + +- Your GitLab instance must have [incoming email](../../../administration/incoming_email.md) + configured. +- There must be at least one issue in the issue list. +- You must have at least the [Guest role](../../permissions.md) for the project. + +To email an issue to a project: + +1. On the top bar, select **Menu > Projects** and find your project. +1. Select **Issues**. +1. At the bottom of the page, select **Email a new issue to this project**. +1. To copy the email address, select **Copy** (**{copy-to-clipboard}**). +1. From your email client, send an email to this address. + The subject is used as the title of the new issue, and the email body becomes the description. + You can use [Markdown](../../markdown.md) and [quick actions](../quick_actions.md). + +A new issue is created, with your user as the author. +You can save this address as a contact in your email client to use it again. WARNING: -This is a private email address, generated just for you. **Keep it to yourself**, -as anyone who knows it can create issues or merge requests as if they -were you. If the address is compromised, or you want to regenerate it, -click **Email a new issue to this project**, followed by **reset it**. +The email address you see is a private email address, generated just for you. +**Keep it to yourself**, because anyone who knows it can create issues or merge requests as if they +were you. -Sending an email to this address creates a new issue associated with your account for -this project, where: +To regenerate the email address: -- The email subject becomes the issue title. -- The email body becomes the issue description. -- [Markdown](../../markdown.md) and [quick actions](../quick_actions.md) are supported. +1. On the issues list, select **Email a new issue to this project**. +1. Select **reset this token**. -NOTE: -In GitLab 11.7, we updated the format of the generated email address. However the -older format is still supported, allowing existing aliases or contacts to continue working. - -### New issue via URL with prefilled fields +### Using a URL with prefilled values To link directly to the new issue page with prefilled fields, use query string parameters in a URL. You can embed a URL in an external -HTML page to create issues with certain -fields prefilled. +HTML page to create issues with certain fields prefilled. -| Field | URL Parameter Name | Notes | -|----------------------|-----------------------|-------------------------------------------------------| -| title | `issue[title]` | | -| description | `issue[description]` | Cannot be used at the same time as `issuable_template`. | -| description template | `issuable_template` | Cannot be used at the same time as `issue[description]`. | -| issue type | `issue[issue_type]` | Either `incident` or `issue`. | -| confidential | `issue[confidential]` | Parameter value must be `true` to set to confidential. | +| Field | URL parameter | Notes | +| -------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Title | `issue[title]` | Must be [URL-encoded](../../../api/index.md#namespaced-path-encoding). | +| Issue type | `issue[issue_type]` | Either `incident` or `issue`. | +| Description template | `issuable_template` | Cannot be used at the same time as `issue[description]`. Must be [URL-encoded](../../../api/index.md#namespaced-path-encoding). | +| Description | `issue[description]` | Cannot be used at the same time as `issuable_template`. Must be [URL-encoded](../../../api/index.md#namespaced-path-encoding). | +| Confidential | `issue[confidential]` | If `true`, the issue is marked as confidential. | -Follow these examples to form your new issue URL with prefilled fields. +Adapt these examples to form your new issue URL with prefilled fields. +To create an issue in the GitLab project: -- For a new issue in the GitLab Community Edition project with a pre-filled title - and a pre-filled description, the URL would be `https://gitlab.com/gitlab-org/gitlab-foss/-/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea` -- For a new issue in the GitLab Community Edition project with a pre-filled title - and a pre-filled description template, the URL would be `https://gitlab.com/gitlab-org/gitlab-foss/-/issues/new?issue[title]=Validate%20new%20concept&issuable_template=Research%20proposal` -- For a new issue in the GitLab Community Edition project with a pre-filled title, - a pre-filled description, and the confidential flag set, the URL would be `https://gitlab.com/gitlab-org/gitlab-foss/-/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea&issue[confidential]=true` +- With a prefilled title and description: + + ```plaintext + https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue[title]=Whoa%2C%20we%27re%20half-way%20there&issue[description]=Whoa%2C%20livin%27%20in%20a%20URL + ``` + +- With a prefilled title and description template: + + ```plaintext + https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue[title]=Validate%20new%20concept&issuable_template=Feature%20Proposal%20-%20basic + ``` + +- With a prefilled title, description, and marked as confidential: + + ```plaintext + https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea&issue[confidential]=true + ``` + +### Using Service Desk + +To offer email support, enable [Service Desk](../service_desk.md) for your project. + +Now, when your customer sends a new email, a new issue can be created in +the appropriate project and followed up from there. + +### Fields in the new issue form + +> Adding the new issue to an epic [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13847) in GitLab 13.1. + +When you're creating a new issue, you can complete the following fields: + +- Title +- Type: either issue (default) or incident +- [Description template](../description_templates.md): overwrites anything in the Description text box +- Description: you can use [Markdown](../../markdown.md) and [quick actions](../quick_actions.md) +- Checkbox to make the issue [confidential](confidential_issues.md) +- [Assignees](#assignee) +- [Weight](issue_weight.md) +- [Epic](../../group/epics/index.md) +- [Due date](due_dates.md) +- [Milestone](../milestones/index.md) +- [Labels](../labels.md) ## Edit an issue You can edit an issue's title and description. +Prerequisites: + +- You must have at least the [Reporter role](../../permissions.md) for a project. + To edit an issue, select **Edit title and description** (**{pencil}**). ### Bulk edit issues at the project level diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md index 9469dead0dc..2bcfad97c25 100644 --- a/doc/user/project/working_with_projects.md +++ b/doc/user/project/working_with_projects.md @@ -4,7 +4,7 @@ group: Workspace 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/#assignments" --- -# Working with projects **(FREE)** +# Manage projects **(FREE)** Most work in GitLab is done in a [project](../../user/project/index.md). Files and code are saved in projects, and most features are in the scope of projects. @@ -16,7 +16,7 @@ To explore projects: 1. On the top bar, select **Menu > Projects**. 1. Select **Explore projects**. -GitLab displays a list of projects, sorted by last updated date. +The **Projects** page shows a list of projects, sorted by last updated date. - To view projects with the most [stars](#star-a-project), select **Most stars**. - To view projects with the largest number of comments in the past month, select **Trending**. @@ -26,14 +26,37 @@ The **Explore projects** tab is visible to unauthenticated users unless the [**Public** visibility level](../admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels) is restricted. Then the tab is visible only to signed-in users. +### Who can view the **Projects** page + +When you select a project, the project landing page shows the project contents. + +For public projects, and members of internal and private projects +with [permissions to view the project's code](../permissions.md#project-members-permissions), +the project landing page shows: + +- A [`README` or index file](repository/index.md#readme-and-index-files). +- A list of directories in the project's repository. + +For users without permission to view the project's code, the landing page shows: + +- The wiki homepage. +- The list of issues in the project. + +### Access a project page with the project ID + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53671) in GitLab 11.8. + +To access a project from the GitLab UI using the project ID, +visit the `/projects/:id` URL in your browser or other tool accessing the project. + ## Explore topics -You can explore popular project topics available on GitLab. To explore project topics: +To explore project topics: 1. On the top bar, select **Menu > Projects**. 1. Select **Explore topics**. -GitLab displays a list of topics sorted by the number of associated projects. +The **Projects** page shows list of topics sorted by the number of associated projects. To view projects associated with a topic, select a topic from the list. You can assign topics to a project on the [Project Settings page](settings/index.md#topics). @@ -260,9 +283,8 @@ To add a star to a project: ## Delete a project -After you delete a project, projects in personal namespaces are deleted immediately. You can -[enable delayed project removal](../group/index.md#enable-delayed-project-deletion) to -delay deletion of projects in a group. +After you delete a project, projects in personal namespaces are deleted immediately. To delay deletion of projects in a group +you can [enable delayed project removal](../group/index.md#enable-delayed-project-deletion). To delete a project: @@ -299,56 +321,27 @@ To leave a project: on the project dashboard when a project is part of a group under a [group namespace](../group/index.md#namespaces). -## Use your project as a Go package +## Use a project as a Go package -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. +Prerequisites: -Private projects, including projects in subgroups, can be used as a Go package. -These projects may require configuration to work correctly. GitLab responds 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 truncates the path for -private projects in subgroups to the first two segments, causing `go get` to -fail. +- Contact your administrator to enable the [GitLab Go Proxy](../packages/go_proxy/index.md). +- To use a private project in a subgroup as a Go package, you must [authenticate Go requests](#authenticate-go-requests-to-private-projects). Go requests that are not authenticated cause +`go get` to fail. You don't need to authenticate Go requests for projects that are not in subgroups. -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). +To use a project as a Go package, use the `go get` and `godoc.org` discovery requests. You can use the meta tags: -### Disable Go module features for private projects +- [`go-import`](https://golang.org/cmd/go/#hdr-Remote_import_paths) +- [`go-source`](https://github.com/golang/gddo/wiki/Source-Code-Links) -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). +### Authenticate Go requests to private projects -`GOPRIVATE`, `GONOPROXY`, and `GONOSUMDB` are comma-separated lists of Go -modules and Go module prefixes. For example, -`GOPRIVATE=gitlab.example.com/my/private/project` disables queries for that -one project, but `GOPRIVATE=gitlab.example.com` disables queries for *all* -projects on GitLab.com. Go does not query module proxies if the module name or a -prefix of it appears in `GOPRIVATE` or `GONOPROXY`. Go does not query checksum -databases if the module name or a prefix of it appears in `GONOPRIVATE` or -`GONOSUMDB`. +Prerequisites: -### Authenticate Go requests +- Your GitLab instance must be accessible with HTTPS. +- You must have a [personal access token](../profile/personal_access_tokens.md). -To authenticate requests to private projects made by Go, use a [`.netrc` -file](https://everything.curl.dev/usingcurl/netrc) 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 -does not transmit credentials over insecure connections. This authenticates -all HTTPS requests made directly by Go, but does not authenticate requests made -through Git. - -For example: +To authenticate Go requests, create a [`.netrc`](https://everything.curl.dev/usingcurl/netrc) file with the following information: ```plaintext machine gitlab.example.com @@ -356,98 +349,106 @@ login password ``` -NOTE: On Windows, Go reads `~/_netrc` instead of `~/.netrc`. -### Authenticate Git fetches +The `go` command does not transmit credentials over insecure connections. It authenticates +HTTPS requests made by Go, but does not authenticate requests made +through Git. -If a module cannot be fetched from a proxy, Go falls back to using Git (for -GitLab projects). Git uses `.netrc` to authenticate requests. You can also -configure Git to either: +### Authenticate Git requests -- Embed specific credentials in the request URL. -- Use SSH instead of HTTPS, as Go always uses HTTPS to fetch Git repositories. +If Go cannot fetch a module from a proxy, it uses Git. Git uses a `.netrc` file to authenticate requests, but you can +configure other authentication methods. -```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" +Configure Git to either: -# Use SSH instead of HTTPS: -git config --global url."git@gitlab.example.com".insteadOf "https://gitlab.example.com" -``` +- Embed credentials in the request URL: + + ```shell + git config --global url."https://${user}:${personal_access_token}@gitlab.example.com".insteadOf "https://gitlab.example.com" + ``` + +- Use SSH instead of HTTPS: + + ```shell + git config --global url."git@gitlab.example.com".insteadOf "https://gitlab.example.com" + ``` + +### Disable Go module fetching for private projects + +To [fetch modules or packages](../../development/go_guide/dependencies.md#fetching), Go uses +the [environment variables](../../development/go_guide/dependencies.md#proxies): + +- `GOPRIVATE` +- `GONOPROXY` +- `GONOSUMDB` + +To disable fetching: + +1. Disable `GOPRIVATE`: + - To disable queries for one project, disable `GOPRIVATE=gitlab.example.com/my/private/project`. + - To disable queries for all projects on GitLab.com, disable `GOPRIVATE=gitlab.example.com`. +1. Disable proxy queries in `GONOPROXY`. +1. Disable checksum queries in `GONOSUMDB`. + +- If the module name or its prefix is in `GOPRIVATE` or `GONOPROXY`, Go does not query module +proxies. +- If the module name or its prefix is in `GONOPRIVATE` or `GONOSUMDB`, Go does not query +Checksum databases. ### Fetch Go modules from Geo secondary sites -As Go modules are stored in Git repositories, you can use the [Geo](../../administration/geo/index.md) -feature that allows Git repositories to be accessed on the secondary Geo servers. +Use [Geo](../../administration/geo/index.md) to access Git repositories that contain Go modules +on secondary Geo servers. -In the following examples, the primary's site domain name is `gitlab.example.com`, -and the secondary's is `gitlab-secondary.example.com`. +You can use SSH or HTTP to access the Geo secondary server. -`go get` will initially generate some HTTP traffic to the primary, but when the module -download commences, the `insteadOf` configuration sends the traffic to the secondary. +#### Use SSH to access the Geo secondary server -#### Use SSH to access the Geo secondary - -To fetch Go modules from the secondary using SSH: +To access the Geo secondary server with SSH: 1. Reconfigure Git on the client to send traffic for the primary to the secondary: - ```plaintext + ```shell git config --global url."git@gitlab-secondary.example.com".insteadOf "https://gitlab.example.com" git config --global url."git@gitlab-secondary.example.com".insteadOf "http://gitlab.example.com" ``` -1. Ensure the client is set up for SSH access to GitLab repositories. This can be tested on the primary, - and GitLab will replicate the public key to the secondary. + - For `gitlab.example.com`, use the primary site domain name. + - For `gitlab-secondary.example.com`, use the secondary site domain name. + +1. Ensure the client is set up for SSH access to GitLab repositories. You can test this on the primary, + and GitLab replicates the public key to the secondary. + +The `go get` request generates HTTP traffic to the primary Geo server. When the module +download starts, the `insteadOf` configuration sends the traffic to the secondary Geo server. #### Use HTTP to access the Geo secondary -Using HTTP to fetch Go modules does not work with CI/CD job tokens, only with -persistent access tokens that are replicated to the secondary. +You must use persistent access tokens that replicate to the secondary server. You cannot use +CI/CD job tokens to fetch Go modules with HTTP. -To fetch Go modules from the secondary using HTTP: +To access the Geo secondary server with HTTP: -1. Put in place a Git `insteadOf` redirect on the client: +1. Add a Git `insteadOf` redirect on the client: - ```plaintext + ```shell git config --global url."https://gitlab-secondary.example.com".insteadOf "https://gitlab.example.com" ``` -1. Generate a [personal access token](../profile/personal_access_tokens.md) and - provide those credentials in the client's `~/.netrc` file: + - For `gitlab.example.com`, use the primary site domain name. + - For `gitlab-secondary.example.com`, use the secondary site domain name. - ```plaintext +1. Generate a [personal access token](../profile/personal_access_tokens.md) and + add the credentials in the client's `~/.netrc` file: + + ```shell machine gitlab.example.com login USERNAME password TOKEN machine gitlab-secondary.example.com login USERNAME password TOKEN ``` -## Access project page with project ID - -> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53671) in GitLab 11.8. - -To quickly access a project from the GitLab UI using the project ID, -visit the `/projects/:id` URL in your browser or other tool accessing the project. - -## Project's landing page - -The project's landing page shows different information depending on -the project's visibility settings and user permissions. - -For public projects, and to members of internal and private projects -with [permissions to view the project's code](../permissions.md#project-members-permissions): - -- The content of a - [`README` or an index file](repository/index.md#readme-and-index-files) - is displayed (if any), followed by the list of directories in the - project's repository. -- If the project doesn't contain either of these files, the - visitor sees the list of files and directories of the repository. - -For users without permissions to view the project's code, GitLab displays: - -- The wiki homepage, if any. -- The list of issues in the project. +The `go get` request generates HTTP traffic to the primary Geo server. When the module +download starts, the `insteadOf` configuration sends the traffic to the secondary Geo server. ## Related topics diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml index 544774d3b06..01041f4f056 100644 --- a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml @@ -11,11 +11,11 @@ variables: FUZZAPI_VERSION: "1" SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers" - FUZZAPI_IMAGE: ${SECURE_ANALYZERS_PREFIX}/api-fuzzing:${FUZZAPI_VERSION} + FUZZAPI_IMAGE: api-fuzzing apifuzzer_fuzz: stage: fuzz - image: $FUZZAPI_IMAGE + image: $SECURE_ANALYZERS_PREFIX/$FUZZAPI_IMAGE:$FUZZAPI_VERSION allow_failure: true rules: - if: $API_FUZZING_DISABLED diff --git a/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml new file mode 100644 index 00000000000..57f1993921d --- /dev/null +++ b/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml @@ -0,0 +1,52 @@ +# To contribute improvements to CI/CD templates, please follow the Development guide at: +# https://docs.gitlab.com/ee/development/cicd/templates.html +# This specific template is located at: +# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Dast-API.gitlab-ci.yml + +# To use this template, add the following to your .gitlab-ci.yml file: +# +# include: +# template: DAST-API.latest.gitlab-ci.yml +# +# You also need to add a `dast` stage to your `stages:` configuration. A sample configuration for DAST API: +# +# stages: +# - build +# - test +# - deploy +# - dast + +# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html + +# Configure DAST API scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/index.html). +# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html#available-cicd-variables + +variables: + # Setting this variable affects all Security templates + # (SAST, Dependency Scanning, ...) + SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers" + # + DAST_API_VERSION: "1" + DAST_API_IMAGE: api-fuzzing + +dast_api: + stage: dast + image: $SECURE_ANALYZERS_PREFIX/$DAST_API_IMAGE:$DAST_API_VERSION + allow_failure: true + rules: + - if: $DAST_API_DISABLED + when: never + - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH && + $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME + when: never + - if: $CI_COMMIT_BRANCH + script: + - /peach/analyzer-dast-api + artifacts: + when: always + paths: + - gl-assets + - gl-dast-api-report.json + - gl-*.log + reports: + dast: gl-dast-api-report.json diff --git a/lib/gitlab/graphql/tracers/logger_tracer.rb b/lib/gitlab/graphql/tracers/logger_tracer.rb index c7ba56824db..3302b2bae3f 100644 --- a/lib/gitlab/graphql/tracers/logger_tracer.rb +++ b/lib/gitlab/graphql/tracers/logger_tracer.rb @@ -11,19 +11,20 @@ module Gitlab end def trace(key, data) - result = yield - + yield + rescue StandardError => e + data[:exception] = e + raise e + ensure case key when "execute_query" log_execute_query(**data) end - - result end private - def log_execute_query(query: nil, duration_s: 0) + def log_execute_query(query: nil, duration_s: 0, exception: nil) # execute_query should always have :query, but we're just being defensive return unless query @@ -39,6 +40,8 @@ module Gitlab query_string: query.query_string } + Gitlab::ExceptionLogFormatter.format!(exception, info) + info.merge!(::Gitlab::ApplicationContext.current) info.merge!(analysis_info) if analysis_info diff --git a/lib/gitlab/graphql/tracers/timer_tracer.rb b/lib/gitlab/graphql/tracers/timer_tracer.rb index 326620a22bc..8e058621110 100644 --- a/lib/gitlab/graphql/tracers/timer_tracer.rb +++ b/lib/gitlab/graphql/tracers/timer_tracer.rb @@ -17,13 +17,9 @@ module Gitlab def trace(key, data) start_time = Gitlab::Metrics::System.monotonic_time - result = yield - - duration_s = Gitlab::Metrics::System.monotonic_time - start_time - - data[:duration_s] = duration_s - - result + yield + ensure + data[:duration_s] = Gitlab::Metrics::System.monotonic_time - start_time end end end diff --git a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml index 40922433635..d90960b344c 100644 --- a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml +++ b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml @@ -119,6 +119,10 @@ category: ci_templates redis_slot: ci_templates aggregation: weekly +- name: p_ci_templates_security_dast_api_latest + category: ci_templates + redis_slot: ci_templates + aggregation: weekly - name: p_ci_templates_security_container_scanning category: ci_templates redis_slot: ci_templates @@ -551,6 +555,10 @@ category: ci_templates redis_slot: ci_templates aggregation: weekly +- name: p_ci_templates_implicit_security_dast_api_latest + category: ci_templates + redis_slot: ci_templates + aggregation: weekly - name: p_ci_templates_implicit_security_container_scanning category: ci_templates redis_slot: ci_templates diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb index 0ba856e8a6e..da1f16f4cfc 100644 --- a/qa/qa/page/project/settings/advanced.rb +++ b/qa/qa/page/project/settings/advanced.rb @@ -5,7 +5,6 @@ module QA module Project module Settings class Advanced < Page::Base - include Component::Select2 include Component::ConfirmModal view 'app/views/projects/edit.html.haml' do @@ -13,8 +12,10 @@ module QA element :change_path_button end - view 'app/views/projects/_transfer.html.haml' do - element :transfer_button + view "app/assets/javascripts/vue_shared/components/namespace_select/namespace_select.vue" do + element :namespaces_list + element :namespaces_list_groups + element :namespaces_list_item end view 'app/views/projects/settings/_archive.html.haml' do @@ -42,16 +43,22 @@ module QA click_element :change_path_button end + def select_namespace(item) + click_element :namespaces_list + + within_element(:namespaces_list) do + find_element(:namespaces_list_item, text: item).click + end + end + def transfer_project!(project_name, namespace) QA::Runtime::Logger.info "Transferring project: #{project_name} to namespace: #{namespace}" click_element_coordinates(:archive_project_content) - expand_select_list - # Workaround for a failure to search when there are no spaces around the / # https://gitlab.com/gitlab-org/gitlab/-/issues/218965 - search_and_select(namespace.gsub(%r{([^\s])/([^\s])}, '\1 / \2')) + select_namespace(namespace.gsub(%r{([^\s])/([^\s])}, '\1 / \2')) click_element(:transfer_button) fill_confirmation_text(project_name) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index a0f0b388b9e..950be154096 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -899,10 +899,34 @@ RSpec.describe ProjectsController do describe '#transfer', :enable_admin_mode do render_views - let_it_be(:project, reload: true) { create(:project) } + let(:project) { create(:project) } + let_it_be(:admin) { create(:admin) } let_it_be(:new_namespace) { create(:namespace) } + shared_examples 'project namespace is not changed' do |flash_message| + it 'project namespace is not changed' do + controller.instance_variable_set(:@project, project) + sign_in(admin) + + old_namespace = project.namespace + + put :transfer, + params: { + namespace_id: old_namespace.path, + new_namespace_id: new_namespace_id, + id: project.path + }, + format: :js + + project.reload + + expect(project.namespace).to eq(old_namespace) + expect(response).to redirect_to(edit_project_path(project)) + expect(flash[:alert]).to eq flash_message + end + end + it 'updates namespace' do sign_in(admin) @@ -921,26 +945,15 @@ RSpec.describe ProjectsController do end context 'when new namespace is empty' do - it 'project namespace is not changed' do - controller.instance_variable_set(:@project, project) - sign_in(admin) + let(:new_namespace_id) { nil } - old_namespace = project.namespace + it_behaves_like 'project namespace is not changed', s_('TransferProject|Please select a new namespace for your project.') + end - put :transfer, - params: { - namespace_id: old_namespace.path, - new_namespace_id: nil, - id: project.path - }, - format: :js + context 'when new namespace is the same as the current namespace' do + let(:new_namespace_id) { project.namespace.id } - project.reload - - expect(project.namespace).to eq(old_namespace) - expect(response).to have_gitlab_http_status(:ok) - expect(flash[:alert]).to eq s_('TransferProject|Please select a new namespace for your project.') - end + it_behaves_like 'project namespace is not changed', s_('TransferProject|Project is already in this namespace.') end end diff --git a/spec/features/issuables/shortcuts_issuable_spec.rb b/spec/features/issuables/shortcuts_issuable_spec.rb index 78cd8d0bef3..7e8f39c47a7 100644 --- a/spec/features/issuables/shortcuts_issuable_spec.rb +++ b/spec/features/issuables/shortcuts_issuable_spec.rb @@ -44,4 +44,90 @@ RSpec.describe 'Blob shortcuts', :js do include_examples 'quotes the selected text' end end + + shared_examples "opens assignee dropdown for editing" do + it "opens assignee dropdown for editing" do + find('body').native.send_key('a') + + expect(find('.block.assignee')).to have_selector('.js-sidebar-assignee-data') + end + end + + describe 'pressing "a"' do + describe 'On an Issue' do + before do + stub_feature_flags(issue_assignees_widget: false) + visit project_issue_path(project, issue) + wait_for_requests + end + + include_examples 'opens assignee dropdown for editing' + end + + describe 'On a Merge Request' do + before do + stub_feature_flags(issue_assignees_widget: false) + visit project_merge_request_path(project, merge_request) + wait_for_requests + end + + include_examples 'opens assignee dropdown for editing' + end + end + + shared_examples "opens milestones dropdown for editing" do + it "opens milestones dropdown for editing" do + find('body').native.send_key('m') + + expect(find('[data-testid="milestone-edit"]')).to have_selector('.gl-new-dropdown-inner') + end + end + + describe 'pressing "m"' do + describe 'On an Issue' do + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + include_examples 'opens milestones dropdown for editing' + end + + describe 'On a Merge Request' do + before do + visit project_merge_request_path(project, merge_request) + wait_for_requests + end + + include_examples 'opens milestones dropdown for editing' + end + end + + shared_examples "opens labels dropdown for editing" do + it "opens labels dropdown for editing" do + find('body').native.send_key('l') + + expect(find('.js-labels-block')).to have_selector('[data-testid="labels-select-dropdown-contents"]') + end + end + + describe 'pressing "l"' do + describe 'On an Issue' do + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + include_examples 'opens labels dropdown for editing' + end + + describe 'On a Merge Request' do + before do + visit project_merge_request_path(project, merge_request) + wait_for_requests + end + + include_examples 'opens labels dropdown for editing' + end + end end diff --git a/spec/features/projects/settings/user_transfers_a_project_spec.rb b/spec/features/projects/settings/user_transfers_a_project_spec.rb index ba4c379ef0a..a88b9101869 100644 --- a/spec/features/projects/settings/user_transfers_a_project_spec.rb +++ b/spec/features/projects/settings/user_transfers_a_project_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'Projects > Settings > User transfers a project', :js do let(:group) { create(:group) } before do + stub_const('Gitlab::QueryLimiting::Transaction::THRESHOLD', 120) + group.add_owner(user) sign_in(user) end @@ -16,10 +18,12 @@ RSpec.describe 'Projects > Settings > User transfers a project', :js do visit edit_project_path(project) page.within('.js-project-transfer-form') do - page.find('.select2-container').click + page.find('[data-testid="transfer-project-namespace"]').click end - page.find("div[role='option']", text: group.full_name).click + page.within('[data-testid="transfer-project-namespace"]') do + page.find("li button", text: group.full_name).click + end click_button('Transfer project') diff --git a/spec/frontend/projects/settings/components/transfer_project_form_spec.js b/spec/frontend/projects/settings/components/transfer_project_form_spec.js new file mode 100644 index 00000000000..f7ce7c6f840 --- /dev/null +++ b/spec/frontend/projects/settings/components/transfer_project_form_spec.js @@ -0,0 +1,68 @@ +import { namespaces } from 'jest/vue_shared/components/namespace_select/mock_data'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import TransferProjectForm from '~/projects/settings/components/transfer_project_form.vue'; +import NamespaceSelect from '~/vue_shared/components/namespace_select/namespace_select.vue'; +import ConfirmDanger from '~/vue_shared/components/confirm_danger/confirm_danger.vue'; + +describe('Transfer project form', () => { + let wrapper; + + const confirmButtonText = 'Confirm'; + const confirmationPhrase = 'You must construct additional pylons!'; + + const createComponent = () => + shallowMountExtended(TransferProjectForm, { + propsData: { + namespaces, + confirmButtonText, + confirmationPhrase, + }, + }); + + const findNamespaceSelect = () => wrapper.findComponent(NamespaceSelect); + const findConfirmDanger = () => wrapper.findComponent(ConfirmDanger); + + beforeEach(() => { + wrapper = createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the namespace selector', () => { + expect(findNamespaceSelect().exists()).toBe(true); + }); + + it('renders the confirm button', () => { + expect(findConfirmDanger().exists()).toBe(true); + }); + + it('disables the confirm button by default', () => { + expect(findConfirmDanger().attributes('disabled')).toBe('true'); + }); + + describe('with a selected namespace', () => { + const [selectedItem] = namespaces.group; + + beforeEach(() => { + findNamespaceSelect().vm.$emit('select', selectedItem); + }); + + it('emits the `selectNamespace` event when a namespace is selected', () => { + const args = [selectedItem.id]; + + expect(wrapper.emitted('selectNamespace')).toEqual([args]); + }); + + it('enables the confirm button', () => { + expect(findConfirmDanger().attributes('disabled')).toBeUndefined(); + }); + + it('clicking the confirm button emits the `confirm` event', () => { + findConfirmDanger().vm.$emit('confirm'); + + expect(wrapper.emitted('confirm')).toBeDefined(); + }); + }); +}); diff --git a/spec/frontend/transfer_edit_spec.js b/spec/frontend/transfer_edit_spec.js index ad8c9c68f37..4091d753fe5 100644 --- a/spec/frontend/transfer_edit_spec.js +++ b/spec/frontend/transfer_edit_spec.js @@ -4,11 +4,11 @@ import { loadHTMLFixture } from 'helpers/fixtures'; import setupTransferEdit from '~/transfer_edit'; describe('setupTransferEdit', () => { - const formSelector = '.js-project-transfer-form'; - const targetSelector = 'select.select2'; + const formSelector = '.js-group-transfer-form'; + const targetSelector = '#new_parent_group_id'; beforeEach(() => { - loadHTMLFixture('projects/edit.html'); + loadHTMLFixture('groups/edit.html'); setupTransferEdit(formSelector, targetSelector); }); @@ -17,8 +17,8 @@ describe('setupTransferEdit', () => { }); it('enables submit button when selection changes to non-empty value', () => { - const nonEmptyValue = $(formSelector).find(targetSelector).find('option').not(':empty').val(); - $(formSelector).find(targetSelector).val(nonEmptyValue).trigger('change'); + const lastValue = $(formSelector).find(targetSelector).find('.dropdown-content li').last(); + $(formSelector).find(targetSelector).val(lastValue).trigger('change'); expect($(formSelector).find(':submit').prop('disabled')).toBeFalsy(); }); diff --git a/spec/frontend/vue_shared/components/namespace_select/mock_data.js b/spec/frontend/vue_shared/components/namespace_select/mock_data.js new file mode 100644 index 00000000000..c9d96672e85 --- /dev/null +++ b/spec/frontend/vue_shared/components/namespace_select/mock_data.js @@ -0,0 +1,11 @@ +export const group = [ + { id: 1, name: 'Group 1', humanName: 'Group 1' }, + { id: 2, name: 'Subgroup 1', humanName: 'Group 1 / Subgroup 1' }, +]; + +export const user = [{ id: 3, name: 'User namespace 1', humanName: 'User namespace 1' }]; + +export const namespaces = { + group, + user, +}; diff --git a/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js b/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js new file mode 100644 index 00000000000..8f07f63993d --- /dev/null +++ b/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js @@ -0,0 +1,86 @@ +import { GlDropdown, GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import NamespaceSelect, { + i18n, +} from '~/vue_shared/components/namespace_select/namespace_select.vue'; +import { user, group, namespaces } from './mock_data'; + +describe('Namespace Select', () => { + let wrapper; + + const createComponent = (props = {}) => + shallowMountExtended(NamespaceSelect, { + propsData: { + data: namespaces, + ...props, + }, + }); + + const wrappersText = (arr) => arr.wrappers.map((w) => w.text()); + const flatNamespaces = () => [...group, ...user]; + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownAttributes = (attr) => findDropdown().attributes(attr); + const selectedDropdownItemText = () => findDropdownAttributes('text'); + const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); + const findSectionHeaders = () => wrapper.findAllComponents(GlDropdownSectionHeader); + + beforeEach(() => { + wrapper = createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the dropdown', () => { + expect(findDropdown().exists()).toBe(true); + }); + + it('renders each dropdown item', () => { + const items = findDropdownItems().wrappers; + expect(items).toHaveLength(flatNamespaces().length); + }); + + it('renders the human name for each item', () => { + const dropdownItems = wrappersText(findDropdownItems()); + const flatNames = flatNamespaces().map(({ humanName }) => humanName); + expect(dropdownItems).toEqual(flatNames); + }); + + it('sets the initial dropdown text', () => { + expect(selectedDropdownItemText()).toBe(i18n.DEFAULT_TEXT); + }); + + it('splits group and user namespaces', () => { + const headers = findSectionHeaders(); + expect(headers).toHaveLength(2); + expect(wrappersText(headers)).toEqual([i18n.GROUPS, i18n.USERS]); + }); + + it('sets the dropdown to full width', () => { + expect(findDropdownAttributes('block')).toBeUndefined(); + + wrapper = createComponent({ fullWidth: true }); + + expect(findDropdownAttributes('block')).not.toBeUndefined(); + expect(findDropdownAttributes('block')).toBe('true'); + }); + + describe('with a selected namespace', () => { + const selectedGroupIndex = 1; + const selectedItem = group[selectedGroupIndex]; + + beforeEach(() => { + findDropdownItems().at(selectedGroupIndex).vm.$emit('click'); + }); + + it('sets the dropdown text', () => { + expect(selectedDropdownItemText()).toBe(selectedItem.humanName); + }); + + it('emits the `select` event when a namespace is selected', () => { + const args = [selectedItem]; + expect(wrapper.emitted('select')).toEqual([args]); + }); + }); +}); diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb index 68bc19cb429..6eb560e3f5c 100644 --- a/spec/helpers/namespaces_helper_spec.rb +++ b/spec/helpers/namespaces_helper_spec.rb @@ -45,6 +45,39 @@ RSpec.describe NamespacesHelper do user_group.add_owner(user) end + describe '#namespaces_as_json' do + let(:result) { helper.namespaces_as_json(user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + it 'returns the user\'s groups' do + json_data = Gitlab::Json.parse(result) + + expect(result).to include('group') + expect(json_data['group']).to include( + "id" => user_group.id, + "name" => user_group.name, + "display_path" => user_group.full_path, + "human_name" => user_group.human_name + ) + end + + it 'returns the user\'s namespace' do + user_namespace = user.namespace + json_data = Gitlab::Json.parse(result) + + expect(result).to include('user') + expect(json_data['user']).to include( + "id" => user_namespace.id, + "name" => user_namespace.name, + "display_path" => user_namespace.full_path, + "human_name" => user_namespace.human_name + ) + end + end + describe '#namespaces_options' do context 'when admin mode is enabled', :enable_admin_mode do it 'returns groups without being a member for admin' do diff --git a/spec/lib/gitlab/graphql/tracers/logger_tracer_spec.rb b/spec/lib/gitlab/graphql/tracers/logger_tracer_spec.rb index d83ac4dabc5..5bc077a963e 100644 --- a/spec/lib/gitlab/graphql/tracers/logger_tracer_spec.rb +++ b/spec/lib/gitlab/graphql/tracers/logger_tracer_spec.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "fast_spec_helper" -require "support/graphql/fake_query_type" +require "spec_helper" RSpec.describe Gitlab::Graphql::Tracers::LoggerTracer do let(:dummy_schema) do @@ -49,4 +48,15 @@ RSpec.describe Gitlab::Graphql::Tracers::LoggerTracer do dummy_schema.execute(query_string, variables: variables) end + + it 'logs exceptions for breaking queries' do + query_string = "query fooOperation { breakingField }" + + expect(::Gitlab::GraphqlLogger).to receive(:info).with(a_hash_including({ + 'exception.message' => 'This field is supposed to break', + 'exception.class' => 'RuntimeError' + })) + + expect { dummy_schema.execute(query_string) }.to raise_error(/This field is supposed to break/) + end end diff --git a/spec/lib/gitlab/graphql/tracers/metrics_tracer_spec.rb b/spec/lib/gitlab/graphql/tracers/metrics_tracer_spec.rb index ff6a76aa319..168f5aa529e 100644 --- a/spec/lib/gitlab/graphql/tracers/metrics_tracer_spec.rb +++ b/spec/lib/gitlab/graphql/tracers/metrics_tracer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' require 'rspec-parameterized' require "support/graphql/fake_query_type" @@ -36,7 +36,7 @@ RSpec.describe Gitlab::Graphql::Tracers::MetricsTracer do end with_them do - it 'increments sli' do + it 'increments apdex sli' do # Trigger initialization fake_schema @@ -56,5 +56,13 @@ RSpec.describe Gitlab::Graphql::Tracers::MetricsTracer do fake_schema.execute("query lorem { helloWorld }") end end + + it "does not record apdex for failing queries" do + query_string = "query fooOperation { breakingField }" + + expect(Gitlab::Metrics::RailsSlis.graphql_query_apdex).not_to receive(:increment) + + expect { fake_schema.execute(query_string) }.to raise_error(/This field is supposed to break/) + end end end diff --git a/spec/lib/gitlab/graphql/tracers/timer_tracer_spec.rb b/spec/lib/gitlab/graphql/tracers/timer_tracer_spec.rb index 7f837e28772..986120dcd95 100644 --- a/spec/lib/gitlab/graphql/tracers/timer_tracer_spec.rb +++ b/spec/lib/gitlab/graphql/tracers/timer_tracer_spec.rb @@ -20,6 +20,7 @@ RSpec.describe Gitlab::Graphql::Tracers::TimerTracer do before do current_time = 0 + allow(tracer_spy).to receive(:trace) allow(Gitlab::Metrics::System).to receive(:monotonic_time) do current_time += expected_duration end @@ -30,6 +31,18 @@ RSpec.describe Gitlab::Graphql::Tracers::TimerTracer do dummy_schema.execute(query_string) + expect_to_have_traced(tracer_spy, expected_duration, query_string) + end + + it "adds a duration_s even if the query failed" do + query_string = "query fooOperation { breakingField }" + + expect { dummy_schema.execute(query_string) }.to raise_error(/This field is supposed to break/) + + expect_to_have_traced(tracer_spy, expected_duration, query_string) + end + + def expect_to_have_traced(tracer_spy, expected_duration, query_string) # "parse" and "execute_query" are just arbitrary trace events expect(tracer_spy).to have_received(:trace).with("parse", { duration_s: expected_duration, diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb index df49b60c4fa..7378beeed06 100644 --- a/spec/models/lfs_objects_project_spec.rb +++ b/spec/models/lfs_objects_project_spec.rb @@ -25,6 +25,28 @@ RSpec.describe LfsObjectsProject do end end + describe '#link_to_project!' do + it 'does not throw error when duplicate exists' do + subject + + expect do + result = described_class.link_to_project!(subject.lfs_object, subject.project) + expect(result).to be_a(LfsObjectsProject) + end.not_to change { described_class.count } + end + + it 'upserts a new entry and updates the project cache' do + new_project = create(:project) + + allow(ProjectCacheWorker).to receive(:perform_async).and_call_original + expect(ProjectCacheWorker).to receive(:perform_async).with(new_project.id, [], [:lfs_objects_size]) + expect { described_class.link_to_project!(subject.lfs_object, new_project) } + .to change { described_class.count } + + expect(described_class.find_by(lfs_object_id: subject.lfs_object.id, project_id: new_project.id)).to be_present + end + end + describe '#update_project_statistics' do it 'updates project statistics when the object is added' do expect(ProjectCacheWorker).to receive(:perform_async) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 20fec9c21a2..5cdcffb98d1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3581,6 +3581,29 @@ RSpec.describe Project, factory_default: :keep do expect(project.forks).to contain_exactly(forked_project) end end + + describe '#lfs_object_oids_from_fork_source' do + let_it_be(:lfs_object) { create(:lfs_object) } + let_it_be(:another_lfs_object) { create(:lfs_object) } + + let(:oids) { [lfs_object.oid, another_lfs_object.oid] } + + context 'when fork has one of two LFS objects' do + before do + create(:lfs_objects_project, lfs_object: lfs_object, project: project) + create(:lfs_objects_project, lfs_object: another_lfs_object, project: forked_project) + end + + it 'returns OIDs of owned LFS objects', :aggregate_failures do + expect(forked_project.lfs_objects_oids_from_fork_source(oids: oids)).to eq([lfs_object.oid]) + expect(forked_project.lfs_objects_oids(oids: oids)).to eq([another_lfs_object.oid]) + end + + it 'returns empty when project is not a fork' do + expect(project.lfs_objects_oids_from_fork_source(oids: oids)).to eq([]) + end + end + end end it_behaves_like 'can housekeep repository' do diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 656ae744ac1..47786e20ed6 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -518,13 +518,43 @@ RSpec.describe 'Git LFS API and storage' do end context 'in source of fork project' do + let(:other_project) { create(:project, :empty_repo) } let(:project) { fork_project(other_project) } before do lfs_object.update!(projects: [other_project]) end - it_behaves_like 'batch upload with existing LFS object' + context 'when user has access to both the parent and fork' do + before do + project.add_developer(user) + other_project.add_developer(user) + end + + it 'links existing LFS objects to other project' do + expect(json_response['objects']).to be_kind_of(Array) + expect(json_response['objects'].first).to include(sample_object) + expect(json_response['objects'].first).not_to have_key('actions') + + expect(lfs_object.reload.projects.pluck(:id)).to match_array([other_project.id, project.id]) + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(lfs_auto_link_fork_source: false) + end + + it_behaves_like 'batch upload with existing LFS object' + end + end + + context 'when user does not have access to parent' do + before do + project.add_developer(user) + end + + it_behaves_like 'batch upload with existing LFS object' + end end end diff --git a/spec/services/groups/update_shared_runners_service_spec.rb b/spec/services/groups/update_shared_runners_service_spec.rb index 53870e810b1..6e938984052 100644 --- a/spec/services/groups/update_shared_runners_service_spec.rb +++ b/spec/services/groups/update_shared_runners_service_spec.rb @@ -63,6 +63,8 @@ RSpec.describe Groups::UpdateSharedRunnersService do let_it_be(:pending_build_2) { create(:ci_pending_build, project: project, instance_runners_enabled: false) } it 'updates pending builds for the group' do + expect(::Ci::UpdatePendingBuildService).to receive(:new).and_call_original + subject expect(pending_build_1.reload.instance_runners_enabled).to be_truthy @@ -73,6 +75,8 @@ RSpec.describe Groups::UpdateSharedRunnersService do let(:params) { { shared_runners_setting: 'invalid_enabled' } } it 'does not update pending builds for the group' do + expect(::Ci::UpdatePendingBuildService).not_to receive(:new).and_call_original + subject expect(pending_build_1.reload.instance_runners_enabled).to be_falsey @@ -99,6 +103,8 @@ RSpec.describe Groups::UpdateSharedRunnersService do let_it_be(:pending_build_2) { create(:ci_pending_build, project: project, instance_runners_enabled: true) } it 'updates pending builds for the group' do + expect(::Ci::UpdatePendingBuildService).to receive(:new).and_call_original + subject expect(pending_build_1.reload.instance_runners_enabled).to be_falsey diff --git a/spec/support/graphql/fake_query_type.rb b/spec/support/graphql/fake_query_type.rb index ffd851a6e6a..18cf2cf3e82 100644 --- a/spec/support/graphql/fake_query_type.rb +++ b/spec/support/graphql/fake_query_type.rb @@ -1,15 +1,22 @@ # frozen_string_literal: true +require 'graphql' module Graphql - class FakeQueryType < Types::BaseObject + class FakeQueryType < ::GraphQL::Schema::Object graphql_name 'FakeQuery' field :hello_world, String, null: true do argument :message, String, required: false end + field :breaking_field, String, null: true + def hello_world(message: "world") "Hello #{message}!" end + + def breaking_field + raise "This field is supposed to break" + end end end