diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 409a75596ce..399337b1433 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -646,10 +646,13 @@ lib/gitlab/checks/** /doc/administration/reference_architectures/ @axil /doc/administration/reply_by_email.md @lciutacu /doc/administration/reply_by_email_postfix_setup.md @axil -/doc/administration/reporting/ @axil +/doc/administration/reporting/ @idurham +/doc/administration/reporting/spamcheck.md @axil /doc/administration/repository_checks.md @eread /doc/administration/repository_storage_paths.md @eread /doc/administration/restart_gitlab.md @axil +/doc/administration/review_abuse_reports.md @idurham +/doc/administration/review_spam_logs.md @idurham /doc/administration/self_hosted_models/ @jglassman1 /doc/administration/server_hooks.md @eread /doc/administration/settings/account_and_limit_settings.md @brendan777 @@ -965,6 +968,7 @@ lib/gitlab/checks/** /doc/development/git_object_deduplication.md @proglottis @toon /doc/development/gitaly.md @proglottis @toon /doc/development/gitpod_internals.md @gl-dx/eng-prod +/doc/development/identity_verification.md @gitlab-org/software-supply-chain-security/authorization/approvers /doc/development/image_scaling.md @abdwdd @alexpooley /doc/development/internal_analytics/ @gitlab-org/analytics-section/product-analytics/engineers/frontend @gitlab-org/analytics-section/analytics-instrumentation/engineers /doc/development/internal_analytics/product_analytics.md @gitlab-org/analytics-section/product-analytics/engineers/frontend @@ -975,14 +979,15 @@ lib/gitlab/checks/** /doc/development/observability/ @gitlab-org/analytics-section/product-analytics/engineers/frontend /doc/development/omnibus.md @gitlab-org/distribution /doc/development/organization/ @abdwdd @alexpooley -/doc/development/permissions.md @gitlab-org/govern/authorization/approvers -/doc/development/permissions/ @gitlab-org/govern/authorization/approvers +/doc/development/permissions.md @gitlab-org/software-supply-chain-security/authorization/approvers +/doc/development/permissions/ @gitlab-org/software-supply-chain-security/authorization/approvers /doc/development/pipelines/ @gl-dx/eng-prod -/doc/development/policies.md @gitlab-org/govern/authentication/approvers +/doc/development/policies.md @gitlab-org/software-supply-chain-security/authentication/approvers /doc/development/prometheus_metrics.md @gitlab-org/analytics-section/product-analytics/engineers/frontend /doc/development/search/ @gitlab-org/search-team/migration-maintainers /doc/development/sec/ @gitlab-org/secure/composition-analysis-be @gitlab-org/secure/static-analysis /doc/development/software_design.md @gl-dx/eng-prod +/doc/development/spam_protection_and_captcha/ @gitlab-org/software-supply-chain-security/authorization/approvers /doc/development/stage_group_observability/ @gitlab-org/analytics-section/product-analytics/engineers/frontend /doc/development/tracing.md @gitlab-org/analytics-section/product-analytics/engineers/frontend /doc/downgrade_ee_to_ce/ @axil @@ -1077,8 +1082,8 @@ lib/gitlab/checks/** /doc/user/compliance/license_approval_policies.md @rlehmann1 /doc/user/compliance/license_scanning_of_cyclonedx_files/ @rdickenson /doc/user/crm/ @msedlakjakubowski -/doc/user/custom_roles.md @rlehmann1 -/doc/user/custom_roles/ @rlehmann1 +/doc/user/custom_roles.md @idurham +/doc/user/custom_roles/ @idurham /doc/user/discussions/ @aqualls /doc/user/duo_amazon_q/ @sselhorn /doc/user/duo_workflow/ @sselhorn @@ -1107,6 +1112,8 @@ lib/gitlab/checks/** /doc/user/group/issues_analytics/ @lciutacu /doc/user/group/iterations/ @msedlakjakubowski /doc/user/group/manage.md @emily.sahlani +/doc/user/group/moderate_users.md @idurham +/doc/user/group/reporting/ @idurham /doc/user/group/repositories_analytics/ @lyspin /doc/user/group/roadmap/ @msedlakjakubowski /doc/user/group/saml_sso/ @idurham @@ -1128,7 +1135,7 @@ lib/gitlab/checks/** /doc/user/packages/container_registry/ @lyspin /doc/user/packages/dependency_proxy/ @lyspin /doc/user/packages/harbor_container_registry/ @lyspin -/doc/user/permissions.md @rlehmann1 +/doc/user/permissions.md @idurham /doc/user/profile/account/ @idurham /doc/user/profile/account/create_accounts.md @lciutacu /doc/user/profile/achievements.md @emily.sahlani @@ -1204,6 +1211,7 @@ lib/gitlab/checks/** /doc/user/project/wiki/ @msedlakjakubowski /doc/user/project/working_with_projects.md @emily.sahlani /doc/user/public_access.md @emily.sahlani +/doc/user/report_abuse.md @idurham /doc/user/reserved_names.md @emily.sahlani /doc/user/rich_text_editor.md @msedlakjakubowski /doc/user/search/ @ashrafkhamis diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index f14794685c9..61d833290e4 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -f1a54081b5323ecbb0f360e8d479e7878a87138f +1ea2d19a4cd8e6f4bfdd3fbde0a82f8b129e9e40 diff --git a/app/assets/javascripts/diffs/components/diff_stats.vue b/app/assets/javascripts/diffs/components/diff_stats.vue index 02cf173ca14..ba97f0d5b21 100644 --- a/app/assets/javascripts/diffs/components/diff_stats.vue +++ b/app/assets/javascripts/diffs/components/diff_stats.vue @@ -66,14 +66,14 @@ export default { {{ diffsCount }} {{ filesText }}
+ {{ addedLines }}
βˆ’ diff --git a/app/assets/javascripts/diffs/components/file_row_stats.vue b/app/assets/javascripts/diffs/components/file_row_stats.vue index 6301ca80290..dde37fc96b5 100644 --- a/app/assets/javascripts/diffs/components/file_row_stats.vue +++ b/app/assets/javascripts/diffs/components/file_row_stats.vue @@ -11,8 +11,8 @@ export default { diff --git a/app/assets/javascripts/diffs/utils/diff_file.js b/app/assets/javascripts/diffs/utils/diff_file.js index e19d77636bc..1b0d7b31f2d 100644 --- a/app/assets/javascripts/diffs/utils/diff_file.js +++ b/app/assets/javascripts/diffs/utils/diff_file.js @@ -136,9 +136,9 @@ export function stats(file) { valid = true; if (diff > 0) { - classes = 'gl-text-green-600'; + classes = 'gl-text-success'; } else if (diff < 0) { - classes = 'gl-text-red-500'; + classes = 'gl-text-danger'; } } diff --git a/app/assets/javascripts/issuable/popover/index.js b/app/assets/javascripts/issuable/popover/index.js index 8574299ce93..f336cc8056b 100644 --- a/app/assets/javascripts/issuable/popover/index.js +++ b/app/assets/javascripts/issuable/popover/index.js @@ -95,13 +95,13 @@ export default (elements, issuablePopoverMount = handleIssuablePopoverMount) => const listenerAddedAttr = 'data-popover-listener-added'; elements.forEach((el) => { - const { projectPath, groupPath, iid, referenceType, milestone, project } = el.dataset; + const { projectPath, groupPath, iid, referenceType, milestone } = el.dataset; let { namespacePath } = el.dataset; const title = el.dataset.mrTitle || el.title; const { innerText } = el; namespacePath = namespacePath || groupPath || projectPath; const isIssuable = Boolean(namespacePath && title && iid); - const isMilestone = Boolean(milestone && project); + const isMilestone = Boolean(milestone); if (!el.getAttribute(listenerAddedAttr) && referenceType && (isIssuable || isMilestone)) { el.addEventListener('mouseenter', ({ target }) => { diff --git a/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue b/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue index f1e23aca281..39b3234b2fb 100644 --- a/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue +++ b/app/assets/javascripts/members/placeholders/components/csv_upload_modal.vue @@ -46,13 +46,24 @@ export default { axios .post(this.reassignmentCsvPath, formData) - .then(() => { - // nothing to do here, modal already closes itself - }) - .catch(() => { + .then((response) => { createAlert({ - message: s__('UserMapping|Something went wrong while uploading the CSV file.'), + message: response.data.message, + variant: 'success', }); + }) + .catch((error) => { + if (error.response.data?.message) { + const { message } = error.response.data; + + createAlert({ + message, + }); + } else { + createAlert({ + message: s__('UserMapping|Something went wrong while uploading the CSV file.'), + }); + } }); }, isValidFileType(file) { @@ -119,8 +130,8 @@ export default { icon="download" data-testid="csv-download-button" class="vertical-align-text-top" - >{{ s__('UserMapping|Download the prefilled CSV template.') }} + >{{ s__('UserMapping|Download the prefilled CSV template.') }} +
  • {{ s__('UserMapping|Review and complete the CSV file.') }}
  • {{ s__('UserMapping|Upload the completed CSV file.') }}
  • diff --git a/app/assets/javascripts/merge_request_dashboard/components/merge_request.vue b/app/assets/javascripts/merge_request_dashboard/components/merge_request.vue index 99a7b62e0ac..a5af115e8ef 100644 --- a/app/assets/javascripts/merge_request_dashboard/components/merge_request.vue +++ b/app/assets/javascripts/merge_request_dashboard/components/merge_request.vue @@ -72,10 +72,11 @@ export default { }, isMergeRequestBroken() { return ( - this.mergeRequest.commitCount === 0 || - !this.mergeRequest.sourceBranchExists || - !this.mergeRequest.targetBranchExists || - this.mergeRequest.conflicts + this.mergeRequest.state === 'opened' && + (this.mergeRequest.commitCount === 0 || + !this.mergeRequest.sourceBranchExists || + !this.mergeRequest.targetBranchExists || + this.mergeRequest.conflicts) ); }, }, @@ -177,6 +178,7 @@ export default { name="warning-solid" variant="subtle" class="gl-mt-1" + data-testid="mr-broken-badge" /> {{ mergeRequest.diffStatsSummary.fileCount }}
    -
    +
    + {{ mergeRequest.diffStatsSummary.additions }}
    -
    +
    βˆ’ {{ mergeRequest.diffStatsSummary.deletions }}
    diff --git a/app/assets/javascripts/merge_request_dashboard/queries/merge_request.fragment.graphql b/app/assets/javascripts/merge_request_dashboard/queries/merge_request.fragment.graphql index d4c078c5470..d0c9ae76b2e 100644 --- a/app/assets/javascripts/merge_request_dashboard/queries/merge_request.fragment.graphql +++ b/app/assets/javascripts/merge_request_dashboard/queries/merge_request.fragment.graphql @@ -9,6 +9,7 @@ fragment MergeRequestDashboardFragment on MergeRequest { title webUrl draft + state author { ...User } diff --git a/app/assets/javascripts/merge_requests/list/index.js b/app/assets/javascripts/merge_requests/list/index.js index 1d8f3b60693..ed9c6c59871 100644 --- a/app/assets/javascripts/merge_requests/list/index.js +++ b/app/assets/javascripts/merge_requests/list/index.js @@ -32,7 +32,6 @@ export async function mountMergeRequestListsApp({ newMergeRequestPath, showExportButton, issuableType, - issuableCount, email, exportCsvPath, rssUrl, @@ -71,7 +70,6 @@ export async function mountMergeRequestListsApp({ newMergeRequestPath, showExportButton: parseBoolean(showExportButton), issuableType, - issuableCount: Number(issuableCount), email, exportCsvPath, rssUrl, diff --git a/app/assets/javascripts/todos/components/toggle_snoozed_status.vue b/app/assets/javascripts/todos/components/toggle_snoozed_status.vue index b6259b47f0c..7efa88bc62b 100644 --- a/app/assets/javascripts/todos/components/toggle_snoozed_status.vue +++ b/app/assets/javascripts/todos/components/toggle_snoozed_status.vue @@ -6,6 +6,8 @@ import { s__, sprintf } from '~/locale'; import { nHoursAfter } from '~/lib/utils/datetime_utility'; import { reportToSentry } from '~/ci/utils'; import { localeDateFormat } from '~/lib/utils/datetime/locale_dateformat'; +import Tracking from '~/tracking'; +import { INSTRUMENT_TODO_ITEM_CLICK } from '~/todos/constants'; import snoozeTodoMutation from './mutations/snooze_todo.mutation.graphql'; import unSnoozeTodoMutation from './mutations/un_snooze_todo.mutation.graphql'; @@ -15,6 +17,7 @@ export default { GlDisclosureDropdown, GlTooltip, }, + mixins: [Tracking.mixin()], inject: ['currentTime'], props: { todo: { @@ -62,7 +65,12 @@ export default { day: dateFormat(forAnHour, 'DDDD'), time: toTimeString(forAnHour), }), - action: () => this.snooze(forAnHour), + action: () => { + this.track(INSTRUMENT_TODO_ITEM_CLICK, { + label: 'snooze_for_one_hour', + }); + this.snooze(forAnHour); + }, }, { text: s__('Todos|Until later today'), @@ -70,7 +78,12 @@ export default { day: dateFormat(untilLaterToday, 'DDDD'), time: toTimeString(untilLaterToday), }), - action: () => this.snooze(untilLaterToday), + action: () => { + this.track(INSTRUMENT_TODO_ITEM_CLICK, { + label: 'snooze_until_later_today', + }); + this.snooze(untilLaterToday); + }, }, { text: s__('Todos|Until tomorrow'), @@ -79,6 +92,10 @@ export default { time: toTimeString(untilTomorrow), }), action: () => { + this.track(INSTRUMENT_TODO_ITEM_CLICK, { + label: 'snooze_until_tomorrow', + }); + this.snooze(untilTomorrow); }, }, @@ -119,6 +136,9 @@ export default { } }, async unSnooze() { + this.track(INSTRUMENT_TODO_ITEM_CLICK, { + label: 'remove_snooze', + }); try { const { data } = await this.$apollo.mutate({ mutation: unSnoozeTodoMutation, diff --git a/app/assets/javascripts/vue_shared/components/diff_stats_dropdown.vue b/app/assets/javascripts/vue_shared/components/diff_stats_dropdown.vue index db8348588c1..3292670e536 100644 --- a/app/assets/javascripts/vue_shared/components/diff_stats_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/diff_stats_dropdown.vue @@ -12,7 +12,7 @@ export const i18n = { const variantCssColorMap = { success: 'gl-text-success', - danger: 'gl-text-red-500', + danger: 'gl-text-danger', }; export default { @@ -128,8 +128,8 @@ export default { >{{ item.text }} {{ additionsText(item.added) }}, {{ deletionsText(item.removed) }} diff --git a/app/assets/javascripts/vue_shared/components/file_finder/item.vue b/app/assets/javascripts/vue_shared/components/file_finder/item.vue index ba4511749a5..30e72f39d9a 100644 --- a/app/assets/javascripts/vue_shared/components/file_finder/item.vue +++ b/app/assets/javascripts/vue_shared/components/file_finder/item.vue @@ -103,10 +103,10 @@ export default { - + {{ file.addedLines }} - + {{ file.removedLines }} diff --git a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue index 34a1f300b08..99e7c369c3b 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue @@ -251,7 +251,6 @@ export default { this.$emit('input', target.value); this.saveDraft(); - this.autosizeTextarea(); }, renderMarkdown(markdown) { const url = setUrlParams( diff --git a/app/controllers/groups/bulk_placeholder_assignments_controller.rb b/app/controllers/groups/bulk_placeholder_assignments_controller.rb new file mode 100644 index 00000000000..0f1caec0778 --- /dev/null +++ b/app/controllers/groups/bulk_placeholder_assignments_controller.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Groups + class BulkPlaceholderAssignmentsController < Groups::ApplicationController + include WorkhorseAuthorization + + PERMITTED_FILE_EXTENSIONS = %w[csv].freeze + + before_action :authorize_owner_access! + + feature_category :importers + + def show + return render_404 unless Feature.enabled?(:importer_user_mapping_reassignment_csv, current_user) + + csv_response = Import::SourceUsers::GenerateCsvService.new(group, current_user: current_user).execute + + if csv_response.success? + send_data( + csv_response.payload, + filename: "bulk_reassignments_for_namespace_#{group.id}_#{Time.current.to_i}.csv", + type: 'text/csv; charset=utf-8' + ) + else + redirect_back_or_default(options: { alert: csv_response.message }) + end + end + + def create + return render_404 unless Feature.enabled?(:importer_user_mapping_reassignment_csv, current_user) + + unless file_is_valid?(file_params[:file]) + respond_to do |format| + format.json do + render json: { message: s_('UserMapping|You must upload a CSV file with a .csv file extension.') }, + status: :unprocessable_entity + end + end + return + end + + respond_to do |format| + format.json do + render json: { + message: s_('UserMapping|The file is being processed and you will receive an email when completed.') + } + end + end + end + + private + + def file_params + params.permit(:file) + end + + def uploader_class + FileUploader + end + + def file_extension_allowlist + PERMITTED_FILE_EXTENSIONS + end + + def maximum_size + Gitlab::CurrentSettings.max_attachment_size.megabytes + end + end +end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 47c1f07acd6..004bc898420 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -13,7 +13,6 @@ class Groups::GroupMembersController < Groups::ApplicationController end # Authorize - before_action :authorize_owner_access!, only: :bulk_reassignment_file before_action :authorize_admin_group_member!, except: admin_not_required_endpoints before_action :authorize_read_group_member!, only: :index @@ -52,22 +51,6 @@ class Groups::GroupMembersController < Groups::ApplicationController ) end - def bulk_reassignment_file - return render_404 unless Feature.enabled?(:importer_user_mapping_reassignment_csv, current_user) - - csv_response = Import::SourceUsers::GenerateCsvService.new(membershipable, current_user: current_user).execute - - if csv_response.success? - send_data( - csv_response.payload, - filename: "bulk_reassignments_for_namespace_#{membershipable.id}_#{Time.current.to_i}.csv", - type: 'text/csv; charset=utf-8' - ) - else - redirect_back_or_default(options: { alert: csv_response.message }) - end - end - # MembershipActions concern alias_method :membershipable, :group diff --git a/app/helpers/groups/group_members_helper.rb b/app/helpers/groups/group_members_helper.rb index ecf7886cf97..fe44a1b3a30 100644 --- a/app/helpers/groups/group_members_helper.rb +++ b/app/helpers/groups/group_members_helper.rb @@ -30,7 +30,7 @@ module Groups::GroupMembersHelper can_approve_access_requests: true, # true for CE, overridden in EE placeholder: placeholder_users, available_roles: available_group_roles(group), - reassignment_csv_path: bulk_reassignment_file_group_group_members_path(group) + reassignment_csv_path: group_bulk_reassignment_file_path(group) } end # rubocop:enable Metrics/ParameterLists diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index e5baef96802..fe9d0484c98 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -262,7 +262,6 @@ module MergeRequestsHelper is_signed_in: current_user.present?.to_s, show_export_button: "true", issuable_type: :merge_request, - issuable_count: issuables_count_for_state(:merge_request, params[:state]), email: current_user.present? ? current_user.notification_email_or_default : nil, rss_url: url_for(safe_params.merge(rss_url_options)), emails_help_page_path: help_page_path('development/emails.md', anchor: 'email-namespace'), diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index fd705db5444..3c569975f63 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -93,6 +93,7 @@ module ApplicationSettingImplementation external_pipeline_validation_service_token: nil, external_pipeline_validation_service_url: nil, failed_login_attempts_unlock_period_in_minutes: nil, + fetch_observability_alerts_from_cloud: true, first_day_of_week: 0, floc_enabled: false, gitaly_timeout_default: 55, diff --git a/app/models/integrations/pipelines_email.rb b/app/models/integrations/pipelines_email.rb index bc7821aad9b..b15f96f985a 100644 --- a/app/models/integrations/pipelines_email.rb +++ b/app/models/integrations/pipelines_email.rb @@ -11,19 +11,25 @@ module Integrations field :recipients, type: :textarea, - help: -> { _('Comma-separated list of email addresses.') }, + help: -> { _('Comma-separated list of recipient email addresses.') }, required: true field :notify_only_broken_pipelines, - type: :checkbox + type: :checkbox, + description: -> { _('Send notifications for broken pipelines.') } field :notify_only_default_branch, type: :checkbox, - api_only: true + api_only: true, + description: -> { _('Send notifications for the default branch.') } field :branches_to_be_notified, type: :select, title: -> { s_('Integrations|Branches for which notifications are to be sent') }, + description: -> { + _('Branches to send notifications for. Valid options are `all`, `default`, `protected`, ' \ + 'and `default_and_protected`. The default value is `default`.') + }, choices: branch_choices def initialize_properties diff --git a/app/models/work_item.rb b/app/models/work_item.rb index 74282557afd..19672b97c09 100644 --- a/app/models/work_item.rb +++ b/app/models/work_item.rb @@ -5,7 +5,7 @@ class WorkItem < Issue COMMON_QUICK_ACTIONS_COMMANDS = [ :title, :reopen, :close, :cc, :tableflip, :shrug, :type, :promote_to, :checkin_reminder, - :subscribe, :unsubscribe, :confidential, :award, :react + :subscribe, :unsubscribe, :confidential, :award, :react, :move, :clone ].freeze self.table_name = 'issues' diff --git a/app/models/work_items/parent_link.rb b/app/models/work_items/parent_link.rb index 1f07c00f86e..7ddca882d05 100644 --- a/app/models/work_items/parent_link.rb +++ b/app/models/work_items/parent_link.rb @@ -63,8 +63,11 @@ module WorkItems return unless work_item_parent && work_item if work_item_parent.confidential? && !work_item.confidential? - errors.add :work_item, _("cannot assign a non-confidential work item to a confidential "\ - "parent. Make the work item confidential and try again.") + errors.add :work_item, format( + _("cannot assign a non-confidential %{work_item_type} to a confidential "\ + "parent. Make the %{work_item_type} confidential and try again."), + work_item_type: work_item.work_item_type.name.downcase + ) end end diff --git a/app/services/work_items/parent_links/create_service.rb b/app/services/work_items/parent_links/create_service.rb index cd21f36674d..bbdabc947a0 100644 --- a/app/services/work_items/parent_links/create_service.rb +++ b/app/services/work_items/parent_links/create_service.rb @@ -17,7 +17,7 @@ module WorkItems link.move_to_start end - create_notes_and_resource_event(work_item, link) if link.changed? && link.save + create_notes_and_resource_event(work_item, link) if link.save link end diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 7987d16bfa8..def73f2b1c1 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -4,6 +4,7 @@ @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username) add_page_specific_style 'page_bundles/issuable_list' +- new_lists_enabled = ::Feature.enabled?(:merge_request_dashboard_new_lists, current_user, type: :wip) = render_dashboard_ultimate_trial(current_user) = render_if_exists 'shared/dashboard/saml_reauth_notice', groups_requiring_saml_reauth: user_groups_requiring_reauth @@ -26,14 +27,14 @@ = render 'shared/new_project_item_vue_select' - if merge_request_dashboard_enabled?(current_user) && !current_page?(merge_requests_search_dashboard_path) - #js-merge-request-dashboard{ data: { base_path: merge_requests_dashboard_path, new_lists_enabled: ::Feature.enabled?(:merge_request_dashboard_new_lists, current_user, type: :wip).to_s, merge_requests_search_dashboard_path: merge_requests_search_dashboard_path(assignee_username: current_user.username), initial_data: merge_request_dashboard_data.to_json } } + #js-merge-request-dashboard{ data: { base_path: merge_requests_dashboard_path, new_lists_enabled: new_lists_enabled.to_s, merge_requests_search_dashboard_path: merge_requests_search_dashboard_path(assignee_username: current_user.username), initial_data: merge_request_dashboard_data.to_json } } = gl_loading_icon(size: 'lg') - if !merge_request_dashboard_enabled?(current_user) || current_page?(merge_requests_search_dashboard_path) - if merge_request_dashboard_enabled?(current_user) = gl_tabs_nav do - = gl_tab_link_to _('Needs attention'), merge_requests_dashboard_path - = gl_tab_link_to _('Following'), merge_requests_following_dashboard_path + = gl_tab_link_to new_lists_enabled ? _('Active') : _('Needs attention'), merge_requests_dashboard_path + = gl_tab_link_to new_lists_enabled ? _('Merged') : _('Following'), merge_requests_following_dashboard_path = gl_tab_link_to _('Search'), merge_requests_search_dashboard_path, item_active: true %div{ class: "#{'gl-bg-gray-10' if merge_request_dashboard_enabled?(current_user)}" } diff --git a/app/views/devise/registrations/_signup_box_form.html.haml b/app/views/devise/registrations/_signup_box_form.html.haml index 60b25bd6d4b..17000c166b8 100644 --- a/app/views/devise/registrations/_signup_box_form.html.haml +++ b/app/views/devise/registrations/_signup_box_form.html.haml @@ -48,9 +48,9 @@ pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _('Please create a username with only alphanumeric characters.') - %p.validation-error.gl-text-red-500.gl-field-error-ignore.gl-mt-2.field-validation.hide + %p.validation-error.gl-text-danger.gl-field-error-ignore.gl-mt-2.field-validation.hide = _('Username is already taken.') - %p.validation-success.gl-text-green-600.gl-field-error-ignore.gl-mt-2.field-validation.hide + %p.validation-success.gl-text-success.gl-field-error-ignore.gl-mt-2.field-validation.hide = _('Username is available.') %p.validation-pending.gl-field-error-ignore.gl-mt-2.field-validation.hide = _('Checking username availability...') diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 0df0451bf5c..56a2075d2dc 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,24 +1,25 @@ -- @can_bulk_update = can?(current_user, :admin_merge_request, @group) && @group.licensed_feature_available?(:group_bulk_edit) && issuables_count_for_state(:merge_requests, :all) > 0 - +- has_bulk_update_permission = can?(current_user, :admin_merge_request, @group) && @group.licensed_feature_available?(:group_bulk_edit) - page_title _("Merge requests") - add_page_specific_style 'page_bundles/issuable_list' - if Feature.enabled?(:vue_merge_request_list, @group) .js-merge-request-list-root{ data: group_merge_requests_list_data(@group, current_user) } - - if @can_bulk_update + - if has_bulk_update_permission = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :merge_requests - else + - can_bulk_update = has_bulk_update_permission && issuables_count_for_state(:merge_requests, :all) > 0 + .top-area = render 'shared/issuable/nav', type: :merge_requests, display_count: !@search_timeout_occurred - if current_user .nav-controls - - if @can_bulk_update + - if can_bulk_update = render_if_exists 'projects/merge_requests/bulk_update_button' = render 'shared/new_project_item_vue_select' = render 'shared/issuable/search_bar', type: :merge_requests - - if @can_bulk_update + - if can_bulk_update = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :merge_requests - if @search_timeout_occurred diff --git a/config/application_setting_columns/observability_settings.yml b/config/application_setting_columns/observability_settings.yml new file mode 100644 index 00000000000..ab06178e8e7 --- /dev/null +++ b/config/application_setting_columns/observability_settings.yml @@ -0,0 +1,12 @@ +--- +api_type: +attr: observability_settings +clusterwide: false +column: observability_settings +db_type: jsonb +default: "'{}'::jsonb" +description: Contains application settings for Observability features +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: true diff --git a/config/feature_flags/gitlab_com_derisk/go_get_handle_relative_url.yml b/config/feature_flags/gitlab_com_derisk/go_get_handle_relative_url.yml deleted file mode 100644 index 8eb2d7ebdeb..00000000000 --- a/config/feature_flags/gitlab_com_derisk/go_get_handle_relative_url.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: go_get_handle_relative_url -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/508593 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176097 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/510100 -milestone: '17.8' -group: group::source code -type: gitlab_com_derisk -default_enabled: false diff --git a/config/feature_flags/ops/ci_secure_files_read_only.yml b/config/feature_flags/ops/ci_secure_files_read_only.yml deleted file mode 100644 index b0921be030c..00000000000 --- a/config/feature_flags/ops/ci_secure_files_read_only.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_secure_files_read_only -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84089 -rollout_issue_url: -milestone: '14.10' -type: ops -group: group::incubation -default_enabled: false \ No newline at end of file diff --git a/config/routes/group.rb b/config/routes/group.rb index 15c10aa5f96..b4fdd8ef50c 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -120,7 +120,10 @@ constraints(::Constraints::GroupUrlConstrainer.new) do post :resend_invite, on: :member collection do - get :bulk_reassignment_file + resource :bulk_reassignment_file, only: %i[show create], controller: 'bulk_placeholder_assignments' do + post :authorize + end + delete :leave end end diff --git a/db/migrate/20250109102301_add_o11y_settings_to_application_settings.rb b/db/migrate/20250109102301_add_o11y_settings_to_application_settings.rb new file mode 100644 index 00000000000..3a6c3bad0a4 --- /dev/null +++ b/db/migrate/20250109102301_add_o11y_settings_to_application_settings.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddO11ySettingsToApplicationSettings < Gitlab::Database::Migration[2.2] + enable_lock_retries! + milestone '17.8' + + def change + add_column :application_settings, :observability_settings, :jsonb, default: {}, null: false + end +end diff --git a/db/migrate/20250109102401_add_o11y_settings_hash_constraint.rb b/db/migrate/20250109102401_add_o11y_settings_hash_constraint.rb new file mode 100644 index 00000000000..29f7e4928d6 --- /dev/null +++ b/db/migrate/20250109102401_add_o11y_settings_hash_constraint.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddO11ySettingsHashConstraint < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + milestone '17.8' + + CONSTRAINT_NAME = 'check_application_settings_o11y_settings_is_hash' + + def up + add_check_constraint( + :application_settings, + "(jsonb_typeof(observability_settings) = 'object')", + CONSTRAINT_NAME + ) + end + + def down + remove_check_constraint :application_settings, CONSTRAINT_NAME + end +end diff --git a/db/schema_migrations/20250109102301 b/db/schema_migrations/20250109102301 new file mode 100644 index 00000000000..02b0d9e5da5 --- /dev/null +++ b/db/schema_migrations/20250109102301 @@ -0,0 +1 @@ +ec474fca5d3e528477f838c937960469619ffc22739cca03b3afb9517a950989 \ No newline at end of file diff --git a/db/schema_migrations/20250109102401 b/db/schema_migrations/20250109102401 new file mode 100644 index 00000000000..6c79444de09 --- /dev/null +++ b/db/schema_migrations/20250109102401 @@ -0,0 +1 @@ +06234102c3cab9b43a17fcbf3c8a9772174efa97322a3de9beb2b2b6720f3003 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 2db32795eb0..6f74f6d35fd 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -7452,6 +7452,7 @@ CREATE TABLE application_settings ( elasticsearch_indexed_field_length_limit integer DEFAULT 0 NOT NULL, elasticsearch_indexed_file_size_limit_kb integer DEFAULT 1024 NOT NULL, elasticsearch_max_code_indexing_concurrency integer DEFAULT 30 NOT NULL, + observability_settings jsonb DEFAULT '{}'::jsonb NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), @@ -7508,6 +7509,7 @@ CREATE TABLE application_settings ( CONSTRAINT check_application_settings_elasticsearch_is_hash CHECK ((jsonb_typeof(elasticsearch) = 'object'::text)), CONSTRAINT check_application_settings_importers_is_hash CHECK ((jsonb_typeof(importers) = 'object'::text)), CONSTRAINT check_application_settings_integrations_is_hash CHECK ((jsonb_typeof(integrations) = 'object'::text)), + CONSTRAINT check_application_settings_o11y_settings_is_hash CHECK ((jsonb_typeof(observability_settings) = 'object'::text)), CONSTRAINT check_application_settings_package_registry_is_hash CHECK ((jsonb_typeof(package_registry) = 'object'::text)), CONSTRAINT check_application_settings_rate_limits_is_hash CHECK ((jsonb_typeof(rate_limits) = 'object'::text)), CONSTRAINT check_application_settings_rate_limits_unauth_git_http_is_hash CHECK ((jsonb_typeof(rate_limits_unauthenticated_git_http) = 'object'::text)), diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index d269b4070dd..a034107b3c1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -32488,10 +32488,6 @@ Returns [`ProjectDataTransfer`](#projectdatatransfer). Software dependencies used by the project. -DETAILS: -**Introduced** in GitLab 15.9. -**Status**: Experiment. - Returns [`DependencyConnection`](#dependencyconnection). This field returns a [connection](#connections). It accepts the @@ -35198,6 +35194,7 @@ JSON structure of a file with matches. | `blameUrl` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Blame URL of the file. | | `chunks` **{warning-solid}** | [`[SearchBlobChunk!]`](#searchblobchunk) | **Introduced** in GitLab 17.2. **Status**: Experiment. Maximum matches per file. | | `fileUrl` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. URL of the file. | +| `language` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.8. **Status**: Experiment. Language of the file. | | `matchCount` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Matches per file up to a max of 50 chunks. Default is 3. | | `matchCountTotal` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Total number of matches per file. | | `path` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Path of the file. | @@ -35211,6 +35208,7 @@ JSON structure of each line in a matched chunk. | Name | Type | Description | | ---- | ---- | ----------- | +| `highlights` **{warning-solid}** | [`[[Int!]!]`](#int) | **Introduced** in GitLab 17.8. **Status**: Experiment. Column numbers of the first and last highlighted characters on a line. | | `lineNumber` **{warning-solid}** | [`Int`](#int) | **Introduced** in GitLab 17.2. **Status**: Experiment. Line number of the blob. | | `richText` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Rich text of the blob. | | `text` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.2. **Status**: Experiment. Text content of the blob. | diff --git a/doc/user/application_security/policies/merge_request_approval_policies.md b/doc/user/application_security/policies/merge_request_approval_policies.md index a91749522be..32d14973816 100644 --- a/doc/user/application_security/policies/merge_request_approval_policies.md +++ b/doc/user/application_security/policies/merge_request_approval_policies.md @@ -582,6 +582,7 @@ To resolve these issues: - For new projects, set up and run the necessary security scans on the default branch before creating merge requests. - Consider using scan execution policies or pipeline execution policies to ensure consistent execution of security scans across all branches. - Consider using [`fallback_behavior`](#fallback_behavior) with `open` to prevent invalid or unenforceable rules in a policy from requiring approval. +- Consider using the [`policy tuning`](#policy_tuning) setting `unblock_rules_using_execution_policies` to address scenarios where security scan artifacts are missing, and scan execution policies are enforced. When enabled, this setting makes approval rules optional when scan artifacts are missing from the target branch and a scan is required by a scan execution policy. This feature only works with an existing scan execution policy that has matching scanners. It offers flexibility in the merge request process when certain security scans cannot be performed due to missing artifacts. ### Support request for debugging of merge request approval policy diff --git a/doc/user/application_security/policies/pipeline_execution_policies.md b/doc/user/application_security/policies/pipeline_execution_policies.md index 57102e35b01..47217aabf40 100644 --- a/doc/user/application_security/policies/pipeline_execution_policies.md +++ b/doc/user/application_security/policies/pipeline_execution_policies.md @@ -207,7 +207,7 @@ the only jobs that run are the pipeline execution policy jobs. ### `override_project_ci` -> - Updated handling of workflow rules [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175088) in GitLab 17.8 [with a flag](../../../administration/feature_flags.md) named `policies_always_override_project_ci`. Disabled by default. +> - Updated handling of workflow rules [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175088) in GitLab 17.8 [with a flag](../../../administration/feature_flags.md) named `policies_always_override_project_ci`. Enabled by default. This strategy replaces the project's existing CI/CD configuration with a new one defined by the pipeline execution policy. This strategy is ideal when the entire pipeline needs to be standardized or replaced, like when you want to enforce organization-wide CI/CD standards or compliance requirements in a highly regulated industry. To override the pipeline configuration, define the CI/CD jobs and do not use `include:project`. diff --git a/doc/user/duo_amazon_q/index.md b/doc/user/duo_amazon_q/index.md index 453ede7020f..b4e5e1511b7 100644 --- a/doc/user/duo_amazon_q/index.md +++ b/doc/user/duo_amazon_q/index.md @@ -12,11 +12,10 @@ DETAILS: **Status:** Preview/Beta > - Introduced as [beta](../../policy/development_stages_support.md#beta) in GitLab 17.7 [with a flag](../../administration/feature_flags.md) named `amazon_q_integration`. Disabled by default. +> - Feature flag `amazon_q_integration` removed in GitLab 17.8. -FLAG: -The availability of this feature is controlled by a feature flag. -For more information, see the history. -This feature is Preview/Beta and is available for testing, but not ready for production use. +NOTE: +If you have a Duo Pro or Duo Enterprise add-on, this feature is not available. At Re:Invent 2024, Amazon announced the GitLab Duo with Amazon Q integration. With this integration, you can automate tasks and increase productivity. diff --git a/doc/user/duo_amazon_q/setup.md b/doc/user/duo_amazon_q/setup.md index d876f7aec49..d79dca1998e 100644 --- a/doc/user/duo_amazon_q/setup.md +++ b/doc/user/duo_amazon_q/setup.md @@ -12,11 +12,10 @@ DETAILS: **Status:** Preview/Beta > - Introduced as an [experiment](../../policy/development_stages_support.md#experiment) in GitLab 17.7 [with a flag](../../administration/feature_flags.md) named `amazon_q_integration`. Disabled by default. +> - Feature flag `amazon_q_integration` removed in GitLab 17.8. -FLAG: -The availability of this feature is controlled by a feature flag. -For more information, see the history. -This feature is a Preview/Beta and is available for testing, but not ready for production use. +NOTE: +If you have a Duo Pro or Duo Enterprise add-on, this feature is not available. To use GitLab Duo with Amazon Q, you can [request access to a lab environment](https://about.gitlab.com/partners/technology-partners/aws/#interest). @@ -40,7 +39,6 @@ To set up GitLab Duo with Amazon Q, you must: - With an HTTPS URL that can be accessed by Amazon Q (the SSL certificate must not be self-signed). For more details about SSL, see [Configure SSL for a Linux package installation](https://docs.gitlab.com/omnibus/settings/ssl/). - With an Ultimate subscription that is synchronized with GitLab. (No trial access.) - - With the `amazon_q_integration` [feature flag enabled](../../administration/feature_flags.md). - GitLab Duo features [must be turned on](../gitlab_duo/turn_on_off.md#turn-on-beta-and-experimental-features). (Experimental and beta features are off by default.) diff --git a/doc/user/project/ml/experiment_tracking/mlflow_client.md b/doc/user/project/ml/experiment_tracking/mlflow_client.md index 54984244b4d..d9e29024da9 100644 --- a/doc/user/project/ml/experiment_tracking/mlflow_client.md +++ b/doc/user/project/ml/experiment_tracking/mlflow_client.md @@ -23,7 +23,7 @@ GitLab plays the role of a MLflow server. Running `mlflow server` is not necessa Prerequisites: -- A [personal](../../../../user/profile/personal_access_tokens.md), [project](../../../../user/project/settings/project_access_tokens.md), or [group](../../../../user/group/settings/group_access_tokens.md) access token with at least the Developer role and the `api` permission. +- A [personal](../../../../user/profile/personal_access_tokens.md), [project](../../../../user/project/settings/project_access_tokens.md), or [group](../../../../user/group/settings/group_access_tokens.md) access token with at least the Developer role and the `api` scope. - The project ID. To find the project ID: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Settings > General**. @@ -46,19 +46,19 @@ by selecting the vertical ellipsis (**{ellipsis_v}**). ## Model experiments When running the training code, MLflow client can be used to create experiments, runs, -models, model versions, log parameters, metrics, metadata and artifacts on GitLab. +models, model versions, log parameters, metrics, metadata, and artifacts on GitLab. After experiments are logged, they are listed under `//-/ml/experiments`. -Runs are registered as candidates, which can be explored by selecting an experiment, model, or model version. +Runs are registered and can be explored by selecting an experiment, model, or model version. -### Associating a candidate to a CI/CD job +### Associating a run to a CI/CD job > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119454) in GitLab 16.1. > - [Changed](https://gitlab.com/groups/gitlab-org/-/epics/9423) to beta in GitLab 17.1. If your training code is being run from a CI/CD job, GitLab can use that information to enhance -candidate metadata. To associate a candidate to a CI/CD job: +run metadata. To associate a run to a CI/CD job: 1. In the [Project CI variables](../../../../ci/variables/index.md), include the following variables: - `MLFLOW_TRACKING_URI`: `"/api/v4/projects//ml/mlflow"` @@ -70,7 +70,7 @@ candidate metadata. To associate a candidate to a CI/CD job: import os import mlflow - with mlflow.start_run(run_name=f"Candidate {index}"): + with mlflow.start_run(run_name=f"Run {index}"): # Your training code # Start of snippet to be included @@ -137,7 +137,7 @@ client.delete_registered_model(model_name) ### Logging candidates to a model Every model has an associated experiment with the same name prefixed by `[model]`. -To log a candidate/run to the model, use the experiment passing the correct name: +To log a run to the model, use the experiment passing the correct name: ```python from mlflow import MlflowClient @@ -177,7 +177,7 @@ client.create_model_version(model_name, version, description=description, tags=t **Notes** -- Argument `run_id` is ignored. Every model version behaves as a Candidate/Run. Creating a mode version from a run is not yet supported. +- Argument `run_id` is ignored. Every model version behaves as a run. Creating a mode version from a run is not yet supported. - Argument `source` is ignored. GitLab will create a package location for the model version files. - Argument `run_link` is ignored. - Argument `await_creation_for` is ignored. @@ -240,7 +240,7 @@ model = mlflow.pyfunc.load_model(f"models:/{model_name}/latest") #### Logging metrics and parameters to a model version -Every model version is also a candidate/run, allowing users to log parameters +Every model version is also a run, allowing users to log parameters and metrics. The run ID can either be found at the Model version page in GitLab, or by using the MLflow client: @@ -285,7 +285,7 @@ Artifacts will then be available under `https//-/ml/models/ ::Integrations::SlackSlashCommands.api_arguments, 'packagist' => ::Integrations::Packagist.api_arguments, 'phorge' => ::Integrations::Phorge.api_arguments, - 'pipelines-email' => [ - { - required: true, - name: :recipients, - type: String, - desc: 'Comma-separated list of recipient email addresses' - }, - { - required: false, - name: :notify_only_broken_pipelines, - type: ::Grape::API::Boolean, - desc: 'Notify only broken pipelines' - }, - { - required: false, - name: :notify_only_default_branch, - type: ::Grape::API::Boolean, - desc: 'Send notifications only for the default branch' - }, - { - required: false, - name: :branches_to_be_notified, - type: String, - desc: 'Branches for which notifications are to be sent' - } - ], + 'pipelines-email' => ::Integrations::PipelinesEmail.api_arguments, 'pivotaltracker' => ::Integrations::Pivotaltracker.api_arguments, 'pumble' => ::Integrations::Pumble.api_arguments, 'pushover' => ::Integrations::Pushover.api_arguments, diff --git a/lib/gitlab/kas/client.rb b/lib/gitlab/kas/client.rb index 8fba8e7382e..8d36cc54b4c 100644 --- a/lib/gitlab/kas/client.rb +++ b/lib/gitlab/kas/client.rb @@ -14,6 +14,8 @@ module Gitlab notifications: Gitlab::Agent::Notifications::Rpc::Notifications::Stub }.freeze + AUTOFLOW_CI_VARIABLE_ENV_SCOPE = 'autoflow/internal-use' + ConfigurationError = Class.new(StandardError) def initialize @@ -72,6 +74,9 @@ module Gitlab # We only want to send events if AutoFlow is enabled and no-op otherwise return unless Feature.enabled?(:autoflow_enabled, project) + # retrieve all AutoFlow-relevant variables + variables = project.variables.by_environment_scope(AUTOFLOW_CI_VARIABLE_ENV_SCOPE) + project_proto = Gitlab::Agent::Event::Project.new( id: project.id, full_path: project.full_path @@ -89,7 +94,8 @@ module Gitlab }, text_data: data.to_json ), - flow_project: project_proto + flow_project: project_proto, + variables: variables.to_h { |v| [v.key, v.value] } ) stub_for(:autoflow) diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index 0391762d426..44f7f23137c 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -80,7 +80,7 @@ module Gitlab def get_repo_url(project_full_path) return ssh_url(project_full_path) if Gitlab::CurrentSettings.enabled_git_access_protocol == 'ssh' - build_http_url(project_full_path) + "#{http_url(project_full_path)}.git" end # create_go_get_html_response creates a HTML document for go get with the expected meta tags. @@ -100,11 +100,7 @@ module Gitlab # get_root_path returns a root path based on the instance URL # that includes a relative part of URL if it was set def get_root_path(project_full_path) - if feature_flag_enabled? - http_url(project_full_path).gsub(%r{\Ahttps?://}, '') - else - Gitlab::Utils.append_path(Gitlab.config.gitlab.host, project_full_path) - end + http_url(project_full_path).gsub(%r{\Ahttps?://}, '') end # http_url returns a direct link to the project @@ -112,19 +108,6 @@ module Gitlab Gitlab::Utils.append_path(Gitlab.config.gitlab.url, project_full_path) end - # build_http_url (temporary) constructs a http url - def build_http_url(project_full_path) - if feature_flag_enabled? - "#{http_url(project_full_path)}.git" - else - Gitlab::RepositoryUrlBuilder.build(project_full_path, protocol: :http) - end - end - - def feature_flag_enabled? - Feature.enabled?(:go_get_handle_relative_url, Feature.current_request) - end - # project_for_path searches for a project based on the path_info def project_for_path(path_info) project_path_match = "#{path_info}/".match(PROJECT_PATH_REGEX) diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb index 22198b5909e..30e6ee1c3fe 100644 --- a/lib/gitlab/quick_actions/issue_actions.rb +++ b/lib/gitlab/quick_actions/issue_actions.rb @@ -109,7 +109,8 @@ module Gitlab params 'path/to/project [--with_notes]' types Issue condition do - quick_action_target.persisted? && + quick_action_target.try(:supports_move_and_clone?) && + quick_action_target.persisted? && current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) end command :clone do |params = ''| @@ -144,7 +145,8 @@ module Gitlab params 'path/to/project' types Issue condition do - quick_action_target.persisted? && + quick_action_target.try(:supports_move_and_clone?) && + quick_action_target.persisted? && current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) end command :move do |target_project_path| diff --git a/lib/gitlab/work_items/issuable_links/error_message.rb b/lib/gitlab/work_items/issuable_links/error_message.rb index 19d51969684..6091eff79ee 100644 --- a/lib/gitlab/work_items/issuable_links/error_message.rb +++ b/lib/gitlab/work_items/issuable_links/error_message.rb @@ -38,9 +38,11 @@ module Gitlab issuable: target_type) end + attr_reader :target_type + private - attr_reader :target_type, :container_type + attr_reader :container_type end end end diff --git a/lib/search/empty_search_results.rb b/lib/search/empty_search_results.rb index cf41fb05948..b030c4c9883 100644 --- a/lib/search/empty_search_results.rb +++ b/lib/search/empty_search_results.rb @@ -29,5 +29,13 @@ module Search def failed?(*) error.present? end + + def blobs_count + 0 + end + + def file_count + 0 + end end end diff --git a/lib/tasks/gitlab/tw/codeowners.rake b/lib/tasks/gitlab/tw/codeowners.rake index 2c10ba09c44..f47f5d72b02 100644 --- a/lib/tasks/gitlab/tw/codeowners.rake +++ b/lib/tasks/gitlab/tw/codeowners.rake @@ -25,9 +25,8 @@ namespace :tw do CodeOwnerRule.new('AI Framework', '@sselhorn'), # CodeOwnerRule.new('AI Model Validation', ''), # CodeOwnerRule.new('Analytics Instrumentation', ''), - # CodeOwnerRule.new('Anti-Abuse', ''), CodeOwnerRule.new('Authentication', '@idurham'), - CodeOwnerRule.new('Authorization', '@rlehmann1'), + CodeOwnerRule.new('Authorization', '@idurham'), CodeOwnerRule.new('Cloud Connector', '@jglassman1'), CodeOwnerRule.new('Code Creation', '@jglassman1'), CodeOwnerRule.new('Code Review', '@aqualls'), @@ -96,8 +95,8 @@ namespace :tw do CodeOwnerRule.new('Analytics Instrumentation', '@gitlab-org/analytics-section/product-analytics/engineers/frontend ' \ '@gitlab-org/analytics-section/analytics-instrumentation/engineers'), - CodeOwnerRule.new('Authentication', '@gitlab-org/govern/authentication/approvers'), - CodeOwnerRule.new('Authorization', '@gitlab-org/govern/authorization/approvers'), + CodeOwnerRule.new('Authentication', '@gitlab-org/software-supply-chain-security/authentication/approvers'), + CodeOwnerRule.new('Authorization', '@gitlab-org/software-supply-chain-security/authorization/approvers'), CodeOwnerRule.new('Compliance', '@gitlab-org/govern/security-policies-frontend @gitlab-org/govern/threat-insights-frontend-team ' \ '@gitlab-org/govern/threat-insights-backend-team'), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7942fd0ffd7..337c6c5c03b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13926,7 +13926,7 @@ msgstr "" msgid "Comma-separated list of branches to be automatically inspected. Leave blank to include all branches." msgstr "" -msgid "Comma-separated list of email addresses." +msgid "Comma-separated list of recipient email addresses." msgstr "" msgid "Command" @@ -17857,18 +17857,30 @@ msgstr "" msgid "DastProfiles|/graphql" msgstr "" +msgid "DastProfiles|A URL that is compared to the URL in the browser to determine if authentication has succeeded after the login form is submitted." +msgstr "" + msgid "DastProfiles|A comma-separated list of actions to be run after login but before login verification. Currently supports `click` actions." msgstr "" msgid "DastProfiles|A comma-separated list of selectors representing elements to click on prior to entering the DAST_AUTH_USERNAME and DAST_AUTH_PASSWORD into the login form." msgstr "" +msgid "DastProfiles|A cookie name and value to be added to every request." +msgstr "" + msgid "DastProfiles|A passive scan monitors all HTTP messages (requests and responses) sent to the target. An active scan attacks the target to find potential vulnerabilities." msgstr "" msgid "DastProfiles|A scanner profile defines the configuration details of a security scanner. %{linkStart}Learn more%{linkEnd}." msgstr "" +msgid "DastProfiles|A selector describing an element whose presence is used to determine if authentication has succeeded after the login form is submitted." +msgstr "" + +msgid "DastProfiles|A selector describing the element that is clicked on to submit the username form of a multi-page login process." +msgstr "" + msgid "DastProfiles|A site profile defines the attributes and configuration details of your deployed application, website, or API. %{linkStart}Learn more%{linkEnd}." msgstr "" @@ -17899,9 +17911,15 @@ msgstr "" msgid "DastProfiles|Additional variables" msgstr "" +msgid "DastProfiles|Advertise scan" +msgstr "" + msgid "DastProfiles|After-login actions" msgstr "" +msgid "DastProfiles|Allowed hosts" +msgstr "" + msgid "DastProfiles|Are you sure you want to delete this profile?" msgstr "" @@ -17914,6 +17932,12 @@ msgstr "" msgid "DastProfiles|Authentication URL" msgstr "" +msgid "DastProfiles|Authentication delegation servers" +msgstr "" + +msgid "DastProfiles|Authentication type" +msgstr "" + msgid "DastProfiles|Before-login actions" msgstr "" @@ -17929,7 +17953,10 @@ msgstr "" msgid "DastProfiles|Clear input fields" msgstr "" -msgid "DastProfiles|Comma-separated list of check identifiers to use for the scan. For identifiers, see %{linkStart}vulnerability checks.%{linkEnd}" +msgid "DastProfiles|Comma-separated list of selectors that are ignored when scanning." +msgstr "" + +msgid "DastProfiles|Cookie names" msgstr "" msgid "DastProfiles|Could not create the scanner profile. Please try again." @@ -17968,9 +17995,18 @@ msgstr "" msgid "DastProfiles|DAST profile library" msgstr "" +msgid "DastProfiles|DOM ready timeout" +msgstr "" + +msgid "DastProfiles|DOM stable timeout" +msgstr "" + msgid "DastProfiles|Debug messages" msgstr "" +msgid "DastProfiles|Define how long to wait for updates to the DOM before checking a page is stable. Defaults to `500ms`." +msgstr "" + msgid "DastProfiles|Delete" msgstr "" @@ -17992,12 +18028,21 @@ msgstr "" msgid "DastProfiles|Edit site profile" msgstr "" +msgid "DastProfiles|Element search timeout" +msgstr "" + msgid "DastProfiles|Enable Authentication" msgstr "" msgid "DastProfiles|Enable Basic Authentication" msgstr "" +msgid "DastProfiles|Ensures that the provided paths are always scanned. Set to a comma-separated list of URL paths relative to `DAST_TARGET_URL`." +msgstr "" + +msgid "DastProfiles|Ensures that the provided paths are always scanned. Set to a file path containing a list of URL paths relative to `DAST_TARGET_URL`. The file must be plain text with one path per line." +msgstr "" + msgid "DastProfiles|Enter URLs in a comma-separated list." msgstr "" @@ -18013,7 +18058,10 @@ msgstr "" msgid "DastProfiles|Excluded URLs (optional)" msgstr "" -msgid "DastProfiles|Excluded checks" +msgid "DastProfiles|Excluded elements" +msgstr "" + +msgid "DastProfiles|Excluded hosts" msgstr "" msgid "DastProfiles|Excluded paths" @@ -18022,21 +18070,51 @@ msgstr "" msgid "DastProfiles|Excluded paths (optional)" msgstr "" +msgid "DastProfiles|Extract element timeout" +msgstr "" + msgid "DastProfiles|Field must not be blank" msgstr "" +msgid "DastProfiles|First submit field" +msgstr "" + msgid "DastProfiles|Hide debug messages" msgstr "" +msgid "DastProfiles|Hostnames included in this variable are accessed, not attacked, and not reported against." +msgstr "" + +msgid "DastProfiles|Hostnames included in this variable are considered excluded and connections are forcibly dropped." +msgstr "" + +msgid "DastProfiles|Hostnames included in this variable are considered in scope when crawled. By default the `DAST_TARGET_URL` hostname is included in the allowed hosts list. Headers set using `DAST_REQUEST_HEADERS` are added to every request made to these hostnames." +msgstr "" + +msgid "DastProfiles|Ignored hosts" +msgstr "" + msgid "DastProfiles|Include debug messages in the DAST console output." msgstr "" +msgid "DastProfiles|Loading element" +msgstr "" + msgid "DastProfiles|Manage %{profileType} profiles" msgstr "" msgid "DastProfiles|Manage profiles" msgstr "" +msgid "DastProfiles|Maximum action count" +msgstr "" + +msgid "DastProfiles|Maximum action depth" +msgstr "" + +msgid "DastProfiles|Maximum response size (MB)" +msgstr "" + msgid "DastProfiles|Minimum = 0 (no timeout enabled), Maximum = 2880 minutes" msgstr "" @@ -18076,9 +18154,30 @@ msgstr "" msgid "DastProfiles|Not Validated" msgstr "" +msgid "DastProfiles|Number of workers that passive scan in parallel. Defaults to the number of available CPUs." +msgstr "" + +msgid "DastProfiles|PKCS12 certificate" +msgstr "" + +msgid "DastProfiles|PKCS12 password" +msgstr "" + +msgid "DastProfiles|Page ready timeout" +msgstr "" + +msgid "DastProfiles|Page ready timeout (after action)" +msgstr "" + +msgid "DastProfiles|Page ready timeout (after navigation)" +msgstr "" + msgid "DastProfiles|Passive" msgstr "" +msgid "DastProfiles|Passive scan worker count" +msgstr "" + msgid "DastProfiles|Password" msgstr "" @@ -18094,6 +18193,12 @@ msgstr "" msgid "DastProfiles|Profile name" msgstr "" +msgid "DastProfiles|Ready element" +msgstr "" + +msgid "DastProfiles|Request cookies" +msgstr "" + msgid "DastProfiles|Request headers" msgstr "" @@ -18145,6 +18250,24 @@ msgstr "" msgid "DastProfiles|Select site profile" msgstr "" +msgid "DastProfiles|Selector that when detected as visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_PAGE_IS_LOADING_ELEMENT`." +msgstr "" + +msgid "DastProfiles|Selector that, when no longer visible on the page, indicates to the analyzer that the page has finished loading and the scan can continue. Cannot be used with `DAST_PAGE_IS_READY_ELEMENT`." +msgstr "" + +msgid "DastProfiles|Set to `false` to disable caching. Default: `true`. **Note:** Disabling cache can cause OOM events or DAST job timeouts." +msgstr "" + +msgid "DastProfiles|Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. Default: `false`." +msgstr "" + +msgid "DastProfiles|Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`." +msgstr "" + +msgid "DastProfiles|Set to a comma-separated list of cookie names to specify which cookies are used for authentication." +msgstr "" + msgid "DastProfiles|Show debug messages" msgstr "" @@ -18160,30 +18283,93 @@ msgstr "" msgid "DastProfiles|Site type" msgstr "" +msgid "DastProfiles|Skip target check" +msgstr "" + msgid "DastProfiles|Submit button" msgstr "" msgid "DastProfiles|Submit button (optional)" msgstr "" +msgid "DastProfiles|Success URL" +msgstr "" + +msgid "DastProfiles|Success element" +msgstr "" + +msgid "DastProfiles|Success without login form" +msgstr "" + msgid "DastProfiles|Target URL" msgstr "" +msgid "DastProfiles|Target check timeout" +msgstr "" + +msgid "DastProfiles|Target paths" +msgstr "" + +msgid "DastProfiles|Target paths file" +msgstr "" + msgid "DastProfiles|Target timeout" msgstr "" +msgid "DastProfiles|The PKCS12 certificate used for sites that require Mutual TLS. Must be encoded as base64 text." +msgstr "" + +msgid "DastProfiles|The authentication type to use." +msgstr "" + +msgid "DastProfiles|The maximum amount of time to allow the browser to extract newly found elements or navigations. Defaults to `5s`." +msgstr "" + +msgid "DastProfiles|The maximum amount of time to allow the browser to search for new elements or user actions. Defaults to `3s`." +msgstr "" + +msgid "DastProfiles|The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis after a navigation completes. Defaults to `6s`." +msgstr "" + +msgid "DastProfiles|The maximum amount of time to wait for a browser to consider a page loaded and ready for analysis. Defaults to `7s`." +msgstr "" + +msgid "DastProfiles|The maximum amount of time to wait for a browser to navigate from one page to another. Defaults to `15s`." +msgstr "" + +msgid "DastProfiles|The maximum amount of time to wait for an element before determining it is ready for analysis. Defaults to `300ms`." +msgstr "" + msgid "DastProfiles|The maximum amount of time to wait for the active scan phase of the scan to complete. Defaults to 3h." msgstr "" +msgid "DastProfiles|The maximum amount of time to wait for the crawl phase of the scan to complete. Defaults to `24h`." +msgstr "" + +msgid "DastProfiles|The maximum number of actions that the crawler performs. Example actions include selecting a link, or filling out a form. Defaults to `10000`." +msgstr "" + +msgid "DastProfiles|The maximum number of chained actions that the crawler takes. For example, `Click, Form Fill, Click` is a depth of three. Defaults to `10`." +msgstr "" + +msgid "DastProfiles|The maximum number of concurrent browser instances to use. For instance runners on GitLab.com, we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but are likely to produce little benefit after five to seven instances. The default value is dynamic, equal to the number of usable logical CPUs." +msgstr "" + msgid "DastProfiles|The maximum number of minutes allowed for the crawler to traverse the site." msgstr "" msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request." msgstr "" +msgid "DastProfiles|The maximum size of a HTTP response body. Responses with bodies larger than this are blocked by the browser. Defaults to `10` MB." +msgstr "" + msgid "DastProfiles|The number of active checks to run in parallel. Defaults to 3." msgstr "" +msgid "DastProfiles|The password of the certificate used in `DAST_PKCS12_CERTIFICATE_BASE64`. Create sensitive %{linkStart}custom CI/CI variables%{linkEnd} using the GitLab UI." +msgstr "" + msgid "DastProfiles|This profile is currently being used in a policy." msgstr "" @@ -18193,6 +18379,12 @@ msgstr "" msgid "DastProfiles|This site profile is currently being used by a policy. To make edits you must remove it from the active policy." msgstr "" +msgid "DastProfiles|Time limit in seconds to wait for target availability. Default: `60s`." +msgstr "" + +msgid "DastProfiles|Timeout" +msgstr "" + msgid "DastProfiles|URL" msgstr "" @@ -18202,6 +18394,9 @@ msgstr "" msgid "DastProfiles|Update variable" msgstr "" +msgid "DastProfiles|Use cache" +msgstr "" + msgid "DastProfiles|Username" msgstr "" @@ -18226,12 +18421,21 @@ msgstr "" msgid "DastProfiles|Variable" msgstr "" +msgid "DastProfiles|Verifies successful authentication by checking for the absence of a login form after the login form has been submitted. This success check is enabled by default." +msgstr "" + msgid "DastProfiles|Website" msgstr "" msgid "DastProfiles|What does each method do?" msgstr "" +msgid "DastProfiles|Which servers should be allowed for integrated authentication and delegation." +msgstr "" + +msgid "DastProfiles|Worker count" +msgstr "" + msgid "DastProfiles|You can either choose a passive scan or validate the target site from the site profile management page. %{docsLinkStart}Learn more about site validation.%{docsLinkEnd}" msgstr "" @@ -20355,6 +20559,9 @@ msgstr "" msgid "Disable What's new" msgstr "" +msgid "Disable fetching cloud alerts if this GitLab instance is isolated from the public internet, or does not use observe.gitlab.com for Observability features." +msgstr "" + msgid "Disable for this project" msgstr "" @@ -24034,6 +24241,9 @@ msgstr "" msgid "Fetch and check out this merge request's feature branch:" msgstr "" +msgid "Fetch cloud observability alerts" +msgstr "" + msgid "Fetching incoming email" msgstr "" @@ -52459,6 +52669,9 @@ msgstr "" msgid "Send notifications for broken pipelines." msgstr "" +msgid "Send notifications for the default branch." +msgstr "" + msgid "Send report" msgstr "" @@ -61517,6 +61730,9 @@ msgstr "" msgid "UserMapping|Source name" msgstr "" +msgid "UserMapping|The file is being processed and you will receive an email when completed." +msgstr "" + msgid "UserMapping|The invitation could not be accepted." msgstr "" @@ -61547,6 +61763,9 @@ msgstr "" msgid "UserMapping|You might have already accepted or rejected the reassignment, or the assignment might have been canceled." msgstr "" +msgid "UserMapping|You must upload a CSV file with a .csv file extension." +msgstr "" + msgid "UserProfile|%{count} %{file}" msgstr "" @@ -66407,7 +66626,7 @@ msgstr "" msgid "cannot assign a linked work item as a parent" msgstr "" -msgid "cannot assign a non-confidential work item to a confidential parent. Make the work item confidential and try again." +msgid "cannot assign a non-confidential %{work_item_type} to a confidential parent. Make the %{work_item_type} confidential and try again." msgstr "" msgid "cannot be a date in the past" diff --git a/package.json b/package.json index 197a5d39195..f1b2d21adbc 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "apollo-upload-client": "15.0.0", "apollo3-cache-persist": "^0.14.1", "autoprefixer": "^10.4.8", - "autosize": "^5.0.1", + "autosize": "^6.0.1", "axios": "^0.24.0", "babel-loader": "^8.4.1", "babel-plugin-lodash": "^3.3.4", @@ -129,7 +129,7 @@ "colord": "^2.9.3", "compression-webpack-plugin": "^5.0.2", "copy-webpack-plugin": "^6.4.1", - "core-js": "^3.39.0", + "core-js": "^3.40.0", "cron-validator": "^1.1.1", "cronstrue": "^1.122.0", "cropperjs": "^1.6.1", diff --git a/patches/autosize+6.0.1.patch b/patches/autosize+6.0.1.patch new file mode 100644 index 00000000000..555ea88faa2 --- /dev/null +++ b/patches/autosize+6.0.1.patch @@ -0,0 +1,38 @@ +diff --git a/node_modules/autosize/dist/autosize.esm.js b/node_modules/autosize/dist/autosize.esm.js +index 224b143..b95fb84 100644 +--- a/node_modules/autosize/dist/autosize.esm.js ++++ b/node_modules/autosize/dist/autosize.esm.js +@@ -1 +1 @@ +-var e=new Map;function t(t){var o=e.get(t);o&&o.destroy()}function o(t){var o=e.get(t);o&&o.update()}var r=null;"undefined"==typeof window?((r=function(e){return e}).destroy=function(e){return e},r.update=function(e){return e}):((r=function(t,o){return t&&Array.prototype.forEach.call(t.length?t:[t],function(t){return function(t){if(t&&t.nodeName&&"TEXTAREA"===t.nodeName&&!e.has(t)){var o,r=null,n=window.getComputedStyle(t),i=(o=t.value,function(){a({testForHeightReduction:""===o||!t.value.startsWith(o),restoreTextAlign:null}),o=t.value}),l=function(o){t.removeEventListener("autosize:destroy",l),t.removeEventListener("autosize:update",s),t.removeEventListener("input",i),window.removeEventListener("resize",s),Object.keys(o).forEach(function(e){return t.style[e]=o[e]}),e.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,textAlign:t.style.textAlign,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener("autosize:destroy",l),t.addEventListener("autosize:update",s),t.addEventListener("input",i),window.addEventListener("resize",s),t.style.overflowX="hidden",t.style.wordWrap="break-word",e.set(t,{destroy:l,update:s}),s()}function a(e){var o,i,l=e.restoreTextAlign,s=void 0===l?null:l,d=e.testForHeightReduction,u=void 0===d||d,c=n.overflowY;if(0!==t.scrollHeight&&("vertical"===n.resize?t.style.resize="none":"both"===n.resize&&(t.style.resize="horizontal"),u&&(o=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push([e.parentNode,e.parentNode.scrollTop]),e=e.parentNode;return function(){return t.forEach(function(e){var t=e[0],o=e[1];t.style.scrollBehavior="auto",t.scrollTop=o,t.style.scrollBehavior=null})}}(t),t.style.height=""),i="content-box"===n.boxSizing?t.scrollHeight-(parseFloat(n.paddingTop)+parseFloat(n.paddingBottom)):t.scrollHeight+parseFloat(n.borderTopWidth)+parseFloat(n.borderBottomWidth),"none"!==n.maxHeight&&i>parseFloat(n.maxHeight)?("hidden"===n.overflowY&&(t.style.overflow="scroll"),i=parseFloat(n.maxHeight)):"hidden"!==n.overflowY&&(t.style.overflow="hidden"),t.style.height=i+"px",s&&(t.style.textAlign=s),o&&o(),r!==i&&(t.dispatchEvent(new Event("autosize:resized",{bubbles:!0})),r=i),c!==n.overflow&&!s)){var v=n.textAlign;"hidden"===n.overflow&&(t.style.textAlign="start"===v?"end":"start"),a({restoreTextAlign:v,testForHeightReduction:!0})}}function s(){a({testForHeightReduction:!0,restoreTextAlign:null})}}(t)}),t}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],t),e},r.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e});var n=r;export default n; ++var e=new Map;function t(t){var o=e.get(t);o&&o.destroy()}function o(t){var o=e.get(t);o&&o.update()}var r=null;"undefined"==typeof window?((r=function(e){return e}).destroy=function(e){return e},r.update=function(e){return e}):((r=function(t,o){return t&&Array.prototype.forEach.call(t.length?t:[t],function(t){return function(t){if(t&&t.nodeName&&"TEXTAREA"===t.nodeName&&!e.has(t)){var o,r=null,n=window.getComputedStyle(t),i=(o=t.value,function(){s({testForHeightReduction:""===o||!t.value.startsWith(o),restoreTextAlign:null}),o=t.value}),l=function(o){t.removeEventListener("autosize:destroy",l),t.removeEventListener("autosize:update",a),t.removeEventListener("input",i),window.removeEventListener("resize",a),Object.keys(o).forEach(function(e){return t.style[e]=o[e]}),e.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,textAlign:t.style.textAlign,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener("autosize:destroy",l),t.addEventListener("autosize:update",a),t.addEventListener("input",i),window.addEventListener("resize",a),t.style.overflowX="hidden",t.style.wordWrap="break-word",e.set(t,{destroy:l,update:a}),a()}function s(e){var o,i,l=e.restoreTextAlign,a=void 0===l?null:l,d=e.testForHeightReduction,u=void 0===d||d,c=n.overflowY;if(0!==t.scrollHeight&&("vertical"===n.resize?t.style.resize="none":"both"===n.resize&&(t.style.resize="horizontal"),u&&(o=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push([e.parentNode,e.parentNode.scrollTop]),e=e.parentNode;return function(){return t.forEach(function(e){var t=e[0],o=e[1];t.scrollTop!==o&&(t.style.scrollBehavior="auto",t.scrollTop=o,t.style.scrollBehavior=null)})}}(t),t.style.height=""),i="content-box"===n.boxSizing?t.scrollHeight-(parseFloat(n.paddingTop)+parseFloat(n.paddingBottom)):t.scrollHeight+parseFloat(n.borderTopWidth)+parseFloat(n.borderBottomWidth),"none"!==n.maxHeight&&i>parseFloat(n.maxHeight)?("hidden"===n.overflowY&&(t.style.overflow="scroll"),i=parseFloat(n.maxHeight)):"hidden"!==n.overflowY&&(t.style.overflow="hidden"),t.style.height=i+"px",a&&(t.style.textAlign=a),o&&o(),r!==i&&(t.dispatchEvent(new Event("autosize:resized",{bubbles:!0})),r=i),c!==n.overflow&&!a)){var p=n.textAlign;"hidden"===n.overflow&&(t.style.textAlign="start"===p?"end":"start"),s({restoreTextAlign:p,testForHeightReduction:!0})}}function a(){s({testForHeightReduction:!0,restoreTextAlign:null})}}(t)}),t}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],t),e},r.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e});var n=r;export default n; +diff --git a/node_modules/autosize/dist/autosize.js b/node_modules/autosize/dist/autosize.js +index e227c1a..4be6b3e 100644 +--- a/node_modules/autosize/dist/autosize.js ++++ b/node_modules/autosize/dist/autosize.js +@@ -24,6 +24,7 @@ + return arr.forEach(function (_ref) { + var node = _ref[0], + scrollTop = _ref[1]; ++ if (node.scrollTop === scrollTop) return; + node.style.scrollBehavior = 'auto'; + node.scrollTop = scrollTop; + node.style.scrollBehavior = null; +diff --git a/node_modules/autosize/dist/autosize.min.js b/node_modules/autosize/dist/autosize.min.js +index 1b24155..ccb470d 100644 +--- a/node_modules/autosize/dist/autosize.min.js ++++ b/node_modules/autosize/dist/autosize.min.js +@@ -1 +1 @@ +-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e||self).autosize=t()}(this,function(){var e=new Map;function t(t){var o=e.get(t);o&&o.destroy()}function o(t){var o=e.get(t);o&&o.update()}var r=null;return"undefined"==typeof window?((r=function(e){return e}).destroy=function(e){return e},r.update=function(e){return e}):((r=function(t,o){return t&&Array.prototype.forEach.call(t.length?t:[t],function(t){return function(t){if(t&&t.nodeName&&"TEXTAREA"===t.nodeName&&!e.has(t)){var o,r=null,n=window.getComputedStyle(t),i=(o=t.value,function(){s({testForHeightReduction:""===o||!t.value.startsWith(o),restoreTextAlign:null}),o=t.value}),l=function(o){t.removeEventListener("autosize:destroy",l),t.removeEventListener("autosize:update",a),t.removeEventListener("input",i),window.removeEventListener("resize",a),Object.keys(o).forEach(function(e){return t.style[e]=o[e]}),e.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,textAlign:t.style.textAlign,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener("autosize:destroy",l),t.addEventListener("autosize:update",a),t.addEventListener("input",i),window.addEventListener("resize",a),t.style.overflowX="hidden",t.style.wordWrap="break-word",e.set(t,{destroy:l,update:a}),a()}function s(e){var o,i,l=e.restoreTextAlign,a=void 0===l?null:l,d=e.testForHeightReduction,u=void 0===d||d,f=n.overflowY;if(0!==t.scrollHeight&&("vertical"===n.resize?t.style.resize="none":"both"===n.resize&&(t.style.resize="horizontal"),u&&(o=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push([e.parentNode,e.parentNode.scrollTop]),e=e.parentNode;return function(){return t.forEach(function(e){var t=e[0],o=e[1];t.style.scrollBehavior="auto",t.scrollTop=o,t.style.scrollBehavior=null})}}(t),t.style.height=""),i="content-box"===n.boxSizing?t.scrollHeight-(parseFloat(n.paddingTop)+parseFloat(n.paddingBottom)):t.scrollHeight+parseFloat(n.borderTopWidth)+parseFloat(n.borderBottomWidth),"none"!==n.maxHeight&&i>parseFloat(n.maxHeight)?("hidden"===n.overflowY&&(t.style.overflow="scroll"),i=parseFloat(n.maxHeight)):"hidden"!==n.overflowY&&(t.style.overflow="hidden"),t.style.height=i+"px",a&&(t.style.textAlign=a),o&&o(),r!==i&&(t.dispatchEvent(new Event("autosize:resized",{bubbles:!0})),r=i),f!==n.overflow&&!a)){var c=n.textAlign;"hidden"===n.overflow&&(t.style.textAlign="start"===c?"end":"start"),s({restoreTextAlign:c,testForHeightReduction:!0})}}function a(){s({testForHeightReduction:!0,restoreTextAlign:null})}}(t)}),t}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],t),e},r.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e}),r}); ++!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e||self).autosize=t()}(this,function(){var e=new Map;function t(t){var o=e.get(t);o&&o.destroy()}function o(t){var o=e.get(t);o&&o.update()}var r=null;return"undefined"==typeof window?((r=function(e){return e}).destroy=function(e){return e},r.update=function(e){return e}):((r=function(t,o){return t&&Array.prototype.forEach.call(t.length?t:[t],function(t){return function(t){if(t&&t.nodeName&&"TEXTAREA"===t.nodeName&&!e.has(t)){var o,r=null,n=window.getComputedStyle(t),i=(o=t.value,function(){s({testForHeightReduction:""===o||!t.value.startsWith(o),restoreTextAlign:null}),o=t.value}),l=function(o){t.removeEventListener("autosize:destroy",l),t.removeEventListener("autosize:update",a),t.removeEventListener("input",i),window.removeEventListener("resize",a),Object.keys(o).forEach(function(e){return t.style[e]=o[e]}),e.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,textAlign:t.style.textAlign,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener("autosize:destroy",l),t.addEventListener("autosize:update",a),t.addEventListener("input",i),window.addEventListener("resize",a),t.style.overflowX="hidden",t.style.wordWrap="break-word",e.set(t,{destroy:l,update:a}),a()}function s(e){var o,i,l=e.restoreTextAlign,a=void 0===l?null:l,d=e.testForHeightReduction,u=void 0===d||d,f=n.overflowY;if(0!==t.scrollHeight&&("vertical"===n.resize?t.style.resize="none":"both"===n.resize&&(t.style.resize="horizontal"),u&&(o=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push([e.parentNode,e.parentNode.scrollTop]),e=e.parentNode;return function(){return t.forEach(function(e){var t=e[0],o=e[1];t.scrollTop!==o&&(t.style.scrollBehavior="auto",t.scrollTop=o,t.style.scrollBehavior=null)})}}(t),t.style.height=""),i="content-box"===n.boxSizing?t.scrollHeight-(parseFloat(n.paddingTop)+parseFloat(n.paddingBottom)):t.scrollHeight+parseFloat(n.borderTopWidth)+parseFloat(n.borderBottomWidth),"none"!==n.maxHeight&&i>parseFloat(n.maxHeight)?("hidden"===n.overflowY&&(t.style.overflow="scroll"),i=parseFloat(n.maxHeight)):"hidden"!==n.overflowY&&(t.style.overflow="hidden"),t.style.height=i+"px",a&&(t.style.textAlign=a),o&&o(),r!==i&&(t.dispatchEvent(new Event("autosize:resized",{bubbles:!0})),r=i),f!==n.overflow&&!a)){var c=n.textAlign;"hidden"===n.overflow&&(t.style.textAlign="start"===c?"end":"start"),s({restoreTextAlign:c,testForHeightReduction:!0})}}function a(){s({testForHeightReduction:!0,restoreTextAlign:null})}}(t)}),t}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],t),e},r.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e}),r}); +diff --git a/node_modules/autosize/src/autosize.js b/node_modules/autosize/src/autosize.js +index c3f1a13..02a627a 100644 +--- a/node_modules/autosize/src/autosize.js ++++ b/node_modules/autosize/src/autosize.js +@@ -16,6 +16,7 @@ function assign(ta) { + } + + return ()=> arr.forEach(([node, scrollTop]) => { ++ if (node.scrollTop === scrollTop) return; + node.style.scrollBehavior = 'auto'; + node.scrollTop = scrollTop; + node.style.scrollBehavior = null; diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt index 93471c6b6f4..589d7e64fa9 100644 --- a/scripts/frontend/quarantined_vue3_specs.txt +++ b/scripts/frontend/quarantined_vue3_specs.txt @@ -45,7 +45,6 @@ ee/spec/frontend/geo_sites/components/details/secondary_site/geo_site_replicatio ee/spec/frontend/geo_sites/index_spec.js ee/spec/frontend/groups/components/invite_members_spec.js ee/spec/frontend/groups/settings/components/comma_separated_list_token_selector_spec.js -ee/spec/frontend/insights/insights_router_spec.js ee/spec/frontend/issues_analytics/components/issues_analytics_table_spec.js ee/spec/frontend/iterations/components/iteration_form_spec.js ee/spec/frontend/iterations/components/iteration_report_issues_spec.js diff --git a/spec/features/projects/settings/secure_files_spec.rb b/spec/features/projects/settings/secure_files_spec.rb index c9412824a1c..c524ad0a6fc 100644 --- a/spec/features/projects/settings/secure_files_spec.rb +++ b/spec/features/projects/settings/secure_files_spec.rb @@ -7,7 +7,6 @@ RSpec.describe 'Secure Files', :js, feature_category: :secrets_management do let(:user) { create(:user) } before do - stub_feature_flags(ci_secure_files_read_only: false) project.add_maintainer(user) sign_in(user) end diff --git a/spec/fixtures/import/user_mapping/user_mapping_upload.csv b/spec/fixtures/import/user_mapping/user_mapping_upload.csv new file mode 100644 index 00000000000..14b1d11f6d1 --- /dev/null +++ b/spec/fixtures/import/user_mapping/user_mapping_upload.csv @@ -0,0 +1,7 @@ +Source host,Import type,Source user identifier,Source user name,Source username,GitLab username,GitLab public email +example.com,github,alice_1,Alice Alison,alice,alice-gl,alice@example.com +example.com,github,bob_1,Bob Bobson,bob,,bob@example.com +example.com,github,carl_1,Carl Calson,carl,carl-gl, +example.com,github,denise_1,Denise Denison,denise,denise-gl,denise@example.com +example.com,github,eric_1,Eric Ericson,eric,eric-gl,eric@example.com +example.com,github,frank_1,Secretive Frank,frank,,frank-secret@example.com \ No newline at end of file diff --git a/spec/frontend/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js index 737654b1406..77c8d78a7f9 100644 --- a/spec/frontend/diffs/components/diff_stats_spec.js +++ b/spec/frontend/diffs/components/diff_stats_spec.js @@ -63,7 +63,7 @@ describe('diff_stats', () => { it("renders the bytes changes instead of line changes when the file isn't diffable", () => { const content = getBytesContainer(); - expect(content.classes('gl-text-green-600')).toBe(true); + expect(content.classes('gl-text-success')).toBe(true); expect(content.text()).toBe('+1.00 KiB (+100%)'); }); }); diff --git a/spec/frontend/diffs/utils/diff_file_spec.js b/spec/frontend/diffs/utils/diff_file_spec.js index 8631bd044d8..6a85f16acdf 100644 --- a/spec/frontend/diffs/utils/diff_file_spec.js +++ b/spec/frontend/diffs/utils/diff_file_spec.js @@ -218,7 +218,7 @@ describe('diff_file utilities', () => { { changed: 1024, percent: 100, - classes: 'gl-text-green-600', + classes: 'gl-text-success', sign: '+', text: '+1.00 KiB (+100%)', valid: true, @@ -234,7 +234,7 @@ describe('diff_file utilities', () => { { changed: -1024, percent: -100, - classes: 'gl-text-red-500', + classes: 'gl-text-danger', sign: '', text: '-1.00 KiB (-100%)', valid: true, diff --git a/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js b/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js index 5d7c3e2ab1d..9bb15851b4e 100644 --- a/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js +++ b/spec/frontend/members/placeholders/components/csv_upload_modal_spec.js @@ -3,7 +3,11 @@ import MockAdapter from 'axios-mock-adapter'; import { GlModal } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; import { createAlert } from '~/alert'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { + HTTP_STATUS_OK, + HTTP_STATUS_INTERNAL_SERVER_ERROR, + HTTP_STATUS_UNPROCESSABLE_ENTITY, +} from '~/lib/utils/http_status'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import CsvUploadModal from '~/members/placeholders/components/csv_upload_modal.vue'; import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; @@ -106,14 +110,45 @@ describe('CsvUploadModal', () => { expect(axios.post).toHaveBeenCalledWith(MOCK_REASSIGNMENT_CSV_PATH, expectedFormData); }); - describe('when the request fails', () => { - beforeEach(() => { + describe('when the request succeeds', () => { + it('displays the message from the response', async () => { + const mockMessage = 'file is being processed'; + mockAxios .onPost(MOCK_REASSIGNMENT_CSV_PATH) - .reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, { error: new Error('error uploading CSV') }); + .reply(HTTP_STATUS_OK, { message: mockMessage }); + + findGlModal().vm.$emit('primary'); + await waitForPromises(); + + expect(createAlert).toHaveBeenCalledWith({ + message: mockMessage, + variant: 'success', + }); + }); + }); + + describe('when the request fails', () => { + it('displays the message from the response if present', async () => { + const mockMessage = 'file too large'; + + mockAxios + .onPost(MOCK_REASSIGNMENT_CSV_PATH) + .reply(HTTP_STATUS_UNPROCESSABLE_ENTITY, { message: mockMessage }); + + findGlModal().vm.$emit('primary'); + await waitForPromises(); + + expect(createAlert).toHaveBeenCalledWith({ + message: mockMessage, + }); }); it('display an alert error message', async () => { + mockAxios + .onPost(MOCK_REASSIGNMENT_CSV_PATH) + .reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, { error: new Error('error uploading CSV') }); + findGlModal().vm.$emit('primary'); await waitForPromises(); diff --git a/spec/frontend/merge_request_dashboard/components/__snapshots__/merge_request_spec.js.snap b/spec/frontend/merge_request_dashboard/components/__snapshots__/merge_request_spec.js.snap index 9645dea7541..a91632607aa 100644 --- a/spec/frontend/merge_request_dashboard/components/__snapshots__/merge_request_spec.js.snap +++ b/spec/frontend/merge_request_dashboard/components/__snapshots__/merge_request_spec.js.snap @@ -84,7 +84,7 @@ exports[`Merge request dashboard merge request component renders template 1`] =
    + @@ -94,7 +94,7 @@ exports[`Merge request dashboard merge request component renders template 1`] =
    βˆ’ @@ -167,6 +167,7 @@ exports[`Merge request dashboard merge request component when newListsEnabled is > { lists: lists || [ [ { + id: 'assigned', title: 'Assigned merge requests', query: 'assignedMergeRequests', variables: { state: 'opened' }, @@ -120,6 +121,7 @@ describe('Merge requests app component', () => { [ [ { + id: 'assigned', title: 'Assigned merge requests', query: 'assignedMergeRequests', variables: { state: 'opened' }, @@ -140,6 +142,7 @@ describe('Merge requests app component', () => { [ [ { + id: 'assigned', title: 'Assigned merge requests', query: 'assignedMergeRequests', variables: { state: 'opened' }, @@ -147,6 +150,7 @@ describe('Merge requests app component', () => { ], [ { + id: 'reviewer', title: 'Assigned merge requests', query: 'assignedMergeRequests', variables: { state: 'opened' }, diff --git a/spec/frontend/merge_request_dashboard/components/merge_request_spec.js b/spec/frontend/merge_request_dashboard/components/merge_request_spec.js index 54be1d26950..72d22f4b8f8 100644 --- a/spec/frontend/merge_request_dashboard/components/merge_request_spec.js +++ b/spec/frontend/merge_request_dashboard/components/merge_request_spec.js @@ -11,6 +11,8 @@ Vue.use(VueApollo); describe('Merge request dashboard merge request component', () => { let wrapper; + const findBrokenBadge = () => wrapper.findByTestId('mr-broken-badge'); + function createComponent(mergeRequest = {}, newListsEnabled = false) { const mockApollo = createMockApollo(); @@ -22,6 +24,7 @@ describe('Merge request dashboard merge request component', () => { propsData: { listId: 'returned_to_you', mergeRequest: { + state: 'opened', reference: '!123456', title: 'Merge request title', author: { @@ -117,5 +120,16 @@ describe('Merge request dashboard merge request component', () => { expect(wrapper.findComponent(StatusBadge).exists()).toBe(true); }); + + it.each` + state | exists | test + ${'opened'} | ${true} | ${'renders'} + ${'closed'} | ${false} | ${'does not render'} + ${'merged'} | ${false} | ${'does not render'} + `('$test broken badge when state is $state', ({ state, exists }) => { + createComponent({ state }, true); + + expect(findBrokenBadge().exists()).toBe(exists); + }); }); }); diff --git a/spec/frontend/merge_request_dashboard/mock_data.js b/spec/frontend/merge_request_dashboard/mock_data.js index 8763a3552ce..c850a16228a 100644 --- a/spec/frontend/merge_request_dashboard/mock_data.js +++ b/spec/frontend/merge_request_dashboard/mock_data.js @@ -5,6 +5,7 @@ export function createMockMergeRequest(mergeRequest = {}) { title: 'Title', webUrl: '/', draft: false, + state: 'opened', author: { id: 1, avatarUrl: '/', diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap index 609f01dfbae..b8246a6fb4f 100644 --- a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap +++ b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap @@ -40,7 +40,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] = id="reference-0" placeholder="Describe what your snippet does or how to use it…" rows="3" - style="overflow-x: hidden; word-wrap: break-word; overflow-y: hidden;" + style="overflow-x: hidden; word-wrap: break-word;" />
    { }); it.each` - index | expectedDate - ${0} | ${'2024-12-18T14:24:00.000Z'} - ${1} | ${'2024-12-18T17:24:00.000Z'} - ${2} | ${'2024-12-19T08:00:00.000Z'} + index | expectedDate | expectedTrackingLabel + ${0} | ${'2024-12-18T14:24:00.000Z'} | ${'snooze_for_one_hour'} + ${1} | ${'2024-12-18T17:24:00.000Z'} | ${'snooze_until_later_today'} + ${2} | ${'2024-12-19T08:00:00.000Z'} | ${'snooze_until_tomorrow'} `( 'triggers the snooze action with snoozeUntil = $expectedDate when clicking option #$index', - ({ index, expectedDate }) => { + ({ index, expectedDate, expectedTrackingLabel }) => { createComponent({ props: { isSnoozed: false, isPending: true } }); - + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); getPredefinedSnoozingOption(index).action(); expect(snoozeTodoMutationSuccessHandler).toHaveBeenCalledWith({ snoozeUntil: new Date(expectedDate), todoId: mockTodo.id, }); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_todo_item_action', { + label: expectedTrackingLabel, + }); + + unmockTracking(); }, ); @@ -265,13 +271,18 @@ describe('ToggleSnoozedStatus', () => { props: { isSnoozed: true, isPending: true }, unSnoozeTodoMutationHandler: jest.fn().mockRejectedValue(), }); - + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); findUnSnoozeButton().vm.$emit('click'); await waitForPromises(); expect(mockToastShow).toHaveBeenCalledWith('Failed to un-snooze todo. Try again later.', { variant: 'danger', }); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_todo_item_action', { + label: 'remove_snooze', + }); + + unmockTracking(); }); it('has a tooltip attached', () => { diff --git a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js index e2c3fc89525..eee576f8b79 100644 --- a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js @@ -79,7 +79,7 @@ describe('Diff Stats Dropdown', () => { expect(fileText).toContain(mockFiles[1].name); expect(fileText).toContain(mockFiles[1].path); expect(fileData.findComponent(GlIcon).props('name')).toEqual(mockFiles[1].icon); - expect(fileData.findComponent(GlIcon).classes()).toContain('gl-text-red-500'); + expect(fileData.findComponent(GlIcon).classes()).toContain('gl-text-danger'); expect(fileData.find('a').attributes('href')).toEqual(mockFiles[1].href); }); diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js index 039c9b69ed9..c17d20d4eef 100644 --- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js +++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js @@ -260,13 +260,6 @@ describe('vue_shared/component/markdown/markdown_editor', () => { }); describe('autosize', () => { - it('autosizes the textarea when the value changes', async () => { - buildWrapper(); - await findTextarea().setValue('Lots of newlines\n\n\n\n\n\n\nMore content\n\n\nand newlines'); - await nextTick(); - expect(Autosize.update).toHaveBeenCalled(); - }); - it('autosizes the textarea when the value changes from outside the component', async () => { buildWrapper(); wrapper.setProps({ value: 'Lots of newlines\n\n\n\n\n\n\nMore content\n\n\nand newlines' }); diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 6d408862f9b..26dbbc25ca1 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -342,7 +342,6 @@ RSpec.describe MergeRequestsHelper, feature_category: :code_review_workflow do new_merge_request_path: project_new_merge_request_path(project), show_export_button: 'true', issuable_type: :merge_request, - issuable_count: 5, email: current_user.notification_email_or_default, export_csv_path: '/csv-url', rss_url: '/rss-url', @@ -455,7 +454,6 @@ RSpec.describe MergeRequestsHelper, feature_category: :code_review_workflow do is_public_visibility_restricted: 'false', is_signed_in: 'true', issuable_type: :merge_request, - issuable_count: 5, email: current_user.notification_email_or_default, rss_url: "/rss-url", releases_endpoint: group_releases_path(group, format: :json), diff --git a/spec/lib/gitlab/kas/client_spec.rb b/spec/lib/gitlab/kas/client_spec.rb index ef88a159a99..07277f0f926 100644 --- a/spec/lib/gitlab/kas/client_spec.rb +++ b/spec/lib/gitlab/kas/client_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Kas::Client do +RSpec.describe Gitlab::Kas::Client, feature_category: :deployment_management do let_it_be(:project) { create(:project) } let_it_be(:agent) { create(:cluster_agent, project: project) } @@ -146,6 +146,9 @@ RSpec.describe Gitlab::Kas::Client do end context 'when autoflow_enabled FF is enabled' do + let_it_be(:autoflow_var1) { create(:ci_variable, project: project, key: 'test_key_1', value: 'test-value-1', environment_scope: 'autoflow/internal-use') } + let_it_be(:autoflow_var2) { create(:ci_variable, project: project, key: 'test_key_2', value: 'test-value-2', environment_scope: 'autoflow/internal-use') } + let_it_be(:other_var) { create(:ci_variable, project: project, key: 'test_key_3', value: 'test-value-3') } let(:stub) { instance_double(Gitlab::Agent::AutoFlow::Rpc::AutoFlow::Stub) } let(:request) { instance_double(Gitlab::Agent::AutoFlow::Rpc::CloudEventRequest) } let(:event_param) { instance_double(Gitlab::Agent::Event::CloudEvent) } @@ -175,7 +178,14 @@ RSpec.describe Gitlab::Kas::Client do .and_return(event_param) expect(Gitlab::Agent::AutoFlow::Rpc::CloudEventRequest).to receive(:new) - .with(event: event_param, flow_project: project_param) + .with( + event: event_param, + flow_project: project_param, + variables: { + "test_key_1" => "test-value-1", + "test_key_2" => "test-value-2" + } + ) .and_return(request) expect(stub).to receive(:cloud_event) diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 0760a01ebae..08efbab9442 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -64,16 +64,6 @@ RSpec.describe Gitlab::Middleware::Go, feature_category: :source_code_management it 'returns the full project path' do expect_response_with_path(go, enabled_protocol, project.full_path, url_based: true) end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(go_get_handle_relative_url: false) - end - - it 'returns the full project path' do - expect_response_with_path(go, enabled_protocol, project.full_path, url_based: false) - end - end end context 'when the project is private' do diff --git a/spec/lib/search/empty_search_results_spec.rb b/spec/lib/search/empty_search_results_spec.rb index 6631ba77359..cbaf0aa2348 100644 --- a/spec/lib/search/empty_search_results_spec.rb +++ b/spec/lib/search/empty_search_results_spec.rb @@ -17,6 +17,18 @@ RSpec.describe ::Search::EmptySearchResults, feature_category: :global_search do end end + describe '#blobs_count' do + it 'returns a zero' do + expect(results.blobs_count).to eq(0) + end + end + + describe '#file_count' do + it 'returns a zero' do + expect(results.file_count).to eq(0) + end + end + describe '#highlight_map' do it 'returns an empty hash' do expect(results.highlight_map).to eq({}) diff --git a/spec/models/integrations/pipelines_email_spec.rb b/spec/models/integrations/pipelines_email_spec.rb index 7e80defcb87..6bdb661305a 100644 --- a/spec/models/integrations/pipelines_email_spec.rb +++ b/spec/models/integrations/pipelines_email_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Integrations::PipelinesEmail, :mailer do +RSpec.describe Integrations::PipelinesEmail, :mailer, feature_category: :integrations do let(:pipeline) do create(:ci_pipeline, :failed, project: project, diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb index a97bbc08c69..8d4b19251e6 100644 --- a/spec/models/work_item_spec.rb +++ b/spec/models/work_item_spec.rb @@ -263,7 +263,7 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do it 'returns quick action commands supported for all work items' do is_expected.to include(:title, :reopen, :close, :cc, :tableflip, :shrug, :type, :promote_to, :checkin_reminder, - :subscribe, :unsubscribe, :confidential, :award) + :subscribe, :unsubscribe, :confidential, :award, :move, :clone) end it 'omits quick action commands from assignees widget' do diff --git a/spec/models/work_items/parent_link_spec.rb b/spec/models/work_items/parent_link_spec.rb index 6ce49c0bac1..47ed9d67300 100644 --- a/spec/models/work_items/parent_link_spec.rb +++ b/spec/models/work_items/parent_link_spec.rb @@ -250,6 +250,23 @@ RSpec.describe WorkItems::ParentLink, feature_category: :portfolio_management do expect(link.valid?).to eq(valid) end end + + context 'when parent is confidential' do + before do + issue.confidential = true + task1.confidential = false + end + + it 'sets the correct error message' do + link = build(:parent_link, work_item_parent: issue, work_item: task1) + + link.valid? + + expect(link.errors[:work_item]).to include( + 'cannot assign a non-confidential task to a confidential parent. ' \ + 'Make the task confidential and try again.') + end + end end end end diff --git a/spec/requests/api/ci/secure_files_spec.rb b/spec/requests/api/ci/secure_files_spec.rb index b973d0b5acc..46e45eaf4ac 100644 --- a/spec/requests/api/ci/secure_files_spec.rb +++ b/spec/requests/api/ci/secure_files_spec.rb @@ -5,7 +5,6 @@ require 'spec_helper' RSpec.describe API::Ci::SecureFiles, feature_category: :mobile_devops do before do stub_ci_secure_file_object_storage - stub_feature_flags(ci_secure_files_read_only: false) end let_it_be(:maintainer) { create(:user) } @@ -24,63 +23,6 @@ RSpec.describe API::Ci::SecureFiles, feature_category: :mobile_devops do end describe 'GET /projects/:id/secure_files' do - context 'ci_secure_files_read_only feature flag' do - context 'when the flag is enabled' do - before do - stub_feature_flags(ci_secure_files_read_only: true) - end - - it 'returns a 503 when attempting to upload a file' do - stub_feature_flags(ci_secure_files_read_only: true) - - expect do - post api("/projects/#{project.id}/secure_files", maintainer), params: file_params - end.not_to change { project.secure_files.count } - - expect(response).to have_gitlab_http_status(:service_unavailable) - end - - it 'returns a 200 when downloading a file' do - stub_feature_flags(ci_secure_files_read_only: true) - - get api("/projects/#{project.id}/secure_files", developer) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a(Array) - end - end - - context 'when the feature is disabled at the instance level' do - before do - stub_config(ci_secure_files: { enabled: false }) - end - - it 'returns a 403 when attempting to upload a file' do - expect do - post api("/projects/#{project.id}/secure_files", maintainer), params: file_params - end.not_to change { project.secure_files.count } - - expect(response).to have_gitlab_http_status(:forbidden) - end - - it 'returns a 403 when downloading a file' do - get api("/projects/#{project.id}/secure_files", developer) - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - - context 'when the flag is disabled' do - it 'returns a 201 when uploading a file when the ci_secure_files_read_only feature flag is disabled' do - expect do - post api("/projects/#{project.id}/secure_files", maintainer), params: file_params - end.to change { project.secure_files.count }.by(1) - - expect(response).to have_gitlab_http_status(:created) - end - end - end - context 'authenticated user with admin permissions' do it 'returns project secure files' do get api("/projects/#{project.id}/secure_files", maintainer) diff --git a/spec/requests/groups/bulk_placeholder_assignments_controller_spec.rb b/spec/requests/groups/bulk_placeholder_assignments_controller_spec.rb new file mode 100644 index 00000000000..55a89037614 --- /dev/null +++ b/spec/requests/groups/bulk_placeholder_assignments_controller_spec.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Groups::BulkPlaceholderAssignmentsController, feature_category: :importers do + include WorkhorseHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :public, owners: user) } + let(:file) { fixture_file_upload('spec/fixtures/import/user_mapping/user_mapping_upload.csv') } + + describe 'GET /groups/*group_id/-/group_members/bulk_reassignment_file' do + subject(:request) do + get group_bulk_reassignment_file_path(group_id: group) + end + + context 'when not signed in' do + it 'forbids access to the endpoint' do + request + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when signed in' do + before do + sign_in(user) + end + + it 'responds with CSV data' do + request + + expect(response).to have_gitlab_http_status(:success) + end + + context 'and the user is not a group owner' do + let_it_be(:group) { create(:group, :public) } + + it 'forbids access to the endpoint' do + request + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'and the CSV is not generated properly' do + before do + allow_next_instance_of(Import::SourceUsers::GenerateCsvService) do |service| + allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'my error message')) + end + end + + it 'redirects with an error' do + request + + expect(response).to be_redirect + expect(flash[:alert]).to eq('my error message') + end + end + + context 'when :importer_user_mapping_reassignment_csv is disabled' do + before do + stub_feature_flags(importer_user_mapping_reassignment_csv: false) + end + + it 'responds with 404' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end + + describe 'POST /groups/*group_id/-/group_members/bulk_reassignment_file' do + let(:file) { fixture_file_upload('spec/fixtures/import/user_mapping/user_mapping_upload.csv') } + + subject(:request) do + workhorse_post_with_file( + group_bulk_reassignment_file_path(group_id: group, format: :json), + file_key: :file, + params: { file: file } + ) + end + + context 'when signed in' do + before do + sign_in(user) + end + + it 'responds with success' do + request + + expect(response).to have_gitlab_http_status(:ok) + end + + context 'and the user is not a group owner' do + let_it_be(:group) { create(:group, :public) } + + it 'forbids access to the endpoint' do + request + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'and the file is not a CSV' do + let(:file) { fixture_file_upload('spec/fixtures/dk.png') } + + it 'returns unprocessable_entity' do + request + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end + end + + context 'when :importer_user_mapping_reassignment_csv is disabled' do + before do + stub_feature_flags(importer_user_mapping_reassignment_csv: false) + end + + it 'responds with 404' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + context 'when not signed in' do + it 'forbids access to the endpoint' do + request + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + + describe 'POST /groups/*group_id/-/group_members/bulk_reassignment_file/authorize' do + include_context 'workhorse headers' + + subject(:request) do + post authorize_group_bulk_reassignment_file_path(group_id: group, format: :json), + params: { file: file }, + headers: workhorse_headers + end + + before do + sign_in(user) + end + + it 'responds with success' do + request + + expect(response).to have_gitlab_http_status(:ok) + end + end +end diff --git a/spec/requests/groups/group_members_controller_spec.rb b/spec/requests/groups/group_members_controller_spec.rb index 1e51a3598ca..991520c135d 100644 --- a/spec/requests/groups/group_members_controller_spec.rb +++ b/spec/requests/groups/group_members_controller_spec.rb @@ -30,73 +30,4 @@ RSpec.describe Groups::GroupMembersController, feature_category: :groups_and_pro it_behaves_like 'request_accessable' end - - describe 'GET /groups/*group_id/-/group_members/bulk_reassignment_file' do - let_it_be(:membershipable) do - create(:group, :public).tap do |group| - group.add_owner(user) - end - end - - subject(:request) do - get bulk_reassignment_file_group_group_members_path(group_id: membershipable) - end - - context 'when not signed in' do - it 'forbids access to the endpoint' do - request - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - - context 'when signed in' do - before do - sign_in(user) - end - - it 'responds with CSV data' do - request - - expect(response).to have_gitlab_http_status(:success) - end - - context 'and the user is not a group owner' do - let_it_be(:membershipable) { create(:group, :public) } - - it 'forbids access to the endpoint' do - request - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - - context 'and the CSV is not generated properly' do - before do - allow_next_instance_of(Import::SourceUsers::GenerateCsvService) do |service| - allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'my error message')) - end - end - - it 'redirects with an error' do - request - - expect(response).to be_redirect - expect(flash[:alert]).to eq('my error message') - end - end - - context 'when :importer_user_mapping_reassignment_csv is disabled' do - before do - stub_feature_flags(importer_user_mapping_reassignment_csv: false) - end - - it 'responds with 404' do - request - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - end end diff --git a/spec/services/issues/clone_service_spec.rb b/spec/services/issues/clone_service_spec.rb index 4666e199c29..e26cce23689 100644 --- a/spec/services/issues/clone_service_spec.rb +++ b/spec/services/issues/clone_service_spec.rb @@ -51,6 +51,27 @@ RSpec.describe Issues::CloneService, feature_category: :team_planning do end end + # We will use this service in order to clone WorkItem to a new project. As WorkItem inherits from Issue, there + # should not be any problem with passing a WorkItem instead of an Issue to this service. + # Adding a small test case to cover this. + context "when we pass a work_item" do + include_context 'user can clone issue' + + subject(:clone) { clone_service.execute(original_work_item, new_project) } + + context "work item is of issue type" do + let_it_be_with_reload(:original_work_item) { create(:work_item, :issue, project: old_project, author: author) } + + it { expect { clone }.to change { new_project.issues.count }.by(1) } + end + + context "work item is of task type" do + let_it_be_with_reload(:original_work_item) { create(:work_item, :task, project: old_project, author: author) } + + it { expect { clone }.to raise_error(described_class::CloneError) } + end + end + context 'generic issue' do let!(:new_issue) { clone_service.execute(old_issue, new_project, with_notes: with_notes) } diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 63e907b5333..f22785e1b82 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -44,6 +44,27 @@ RSpec.describe Issues::MoveService, feature_category: :team_planning do let!(:new_issue) { move_service.execute(old_issue, new_project) } end + # We will use this service in order to move WorkItem to a new project. As WorkItem inherits from Issue, there + # should not be any problem with passing a WorkItem instead of an Issue to this service. + # Adding a small test case to cover this. + context "when we pass a work_item" do + include_context 'user can move issue' + + subject(:move) { move_service.execute(original_work_item, new_project) } + + context "work item is of issue type" do + let_it_be_with_reload(:original_work_item) { create(:work_item, :issue, project: old_project, author: author) } + + it { expect { move }.to change { new_project.issues.count }.by(1) } + end + + context "work item is of task type" do + let_it_be_with_reload(:original_work_item) { create(:work_item, :task, project: old_project, author: author) } + + it { expect { move }.to raise_error(described_class::MoveError) } + end + end + context 'when issue creation fails' do include_context 'user can move issue' diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 620a0cf733b..2c61336317a 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -654,6 +654,43 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d expect(message).to eq(_("Failed to move this issue because target project doesn't exist.")) end + + context "when we pass a work_item" do + let(:work_item) { create(:work_item, :issue) } + let(:move_command) { "/move #{project.full_path}" } + + it '/move execution method message' do + _, _, message = service.execute(move_command, work_item) + + expect(message).to eq("Moved this issue to #{project.full_path}.") + end + end + end + + describe 'clone issue command' do + it 'returns the clone issue message' do + _, _, message = service.execute("/clone #{project.full_path}", issue) + translated_string = _("Cloned this issue to %{project_full_path}.") + formatted_message = format(translated_string, project_full_path: project.full_path.to_s) + + expect(message).to eq(formatted_message) + end + + it 'returns clone issue failure message when the referenced issue is not found' do + _, _, message = service.execute('/clone invalid', issue) + + expect(message).to eq(_("Failed to clone this issue because target project doesn't exist.")) + end + + context "when we pass a work_item" do + let(:work_item) { create(:work_item, :issue, project: project) } + + it '/clone execution method message' do + _, _, message = service.execute("/clone #{project.full_path}", work_item) + + expect(message).to eq("Cloned this issue to #{project.full_path}.") + end + end end shared_examples 'confidential command' do @@ -3533,6 +3570,37 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d expect(explanations).to eq([_("Moves this issue to test/project.")]) end + + context "when work item type is an issue" do + let(:move_command) { "/move test/project" } + let(:work_item) { create(:work_item, :issue, project: project) } + + it "/move is available" do + _, explanations = service.explain(move_command, work_item) + + expect(explanations).to match_array(["Moves this issue to test/project."]) + end + end + end + + describe 'clone issue to another project command' do + let(:content) { '/clone test/project' } + + it 'includes the project name' do + _, explanations = service.explain(content, issue) + + expect(explanations).to match_array([_("Clones this issue, without comments, to test/project.")]) + end + + context "when work item type is an issue" do + let(:work_item) { create(:work_item, :issue, project: project) } + + it "/clone is available" do + _, explanations = service.explain("/clone test/project", work_item) + + expect(explanations).to match_array(["Clones this issue, without comments, to test/project."]) + end + end end describe 'tag a commit' do @@ -3952,5 +4020,25 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d ) end end + + context 'when target is a work item type of issue' do + let(:target) { create(:work_item, :issue) } + + context "when work_item supports move and clone commands" do + it 'does recognize the actions' do + expect(service.available_commands(target).pluck(:name)).to include(:move, :clone) + end + end + + context "when work_item does not support move and clone commands" do + before do + allow(target).to receive(:supports_move_and_clone?).and_return(false) + end + + it 'does not recognize the action' do + expect(service.available_commands(target).pluck(:name)).not_to include(:move, :clone) + end + end + end end end diff --git a/workhorse/_support/lint_last_known_acceptable.txt b/workhorse/_support/lint_last_known_acceptable.txt index cc8bd1b2dfc..3c379d394dd 100644 --- a/workhorse/_support/lint_last_known_acceptable.txt +++ b/workhorse/_support/lint_last_known_acceptable.txt @@ -88,9 +88,9 @@ internal/upload/destination/objectstore/upload_strategy.go:29: internal/upload/d internal/upload/destination/objectstore/uploader.go:5:2: G501: Blocklisted import crypto/md5: weak cryptographic primitive (gosec) internal/upload/destination/objectstore/uploader.go:95:12: G401: Use of weak cryptographic primitive (gosec) internal/upload/exif/exif.go:103:10: G204: Subprocess launched with variable (gosec) -internal/upstream/routes.go:170:74: `(*upstream).wsRoute` - `matchers` always receives `nil` (unparam) -internal/upstream/routes.go:230: Function 'configureRoutes' is too long (339 > 60) (funlen) -internal/upstream/routes.go:485: internal/upstream/routes.go:485: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: We should probably not return a HT..." (godox) +internal/upstream/routes.go:171:74: `(*upstream).wsRoute` - `matchers` always receives `nil` (unparam) +internal/upstream/routes.go:231: Function 'configureRoutes' is too long (340 > 60) (funlen) +internal/upstream/routes.go:487: internal/upstream/routes.go:487: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: We should probably not return a HT..." (godox) internal/upstream/upstream.go:116: internal/upstream/upstream.go:116: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "TODO: move to LabKit https://gitlab.com/..." (godox) internal/zipartifacts/metadata.go:118:54: G115: integer overflow conversion int -> uint32 (gosec) internal/zipartifacts/open_archive.go:78:28: response body must be closed (bodyclose) diff --git a/workhorse/cmd/gitlab-workhorse/upload_test.go b/workhorse/cmd/gitlab-workhorse/upload_test.go index c1fc2f6fb95..3229b2f08de 100644 --- a/workhorse/cmd/gitlab-workhorse/upload_test.go +++ b/workhorse/cmd/gitlab-workhorse/upload_test.go @@ -189,6 +189,7 @@ func TestAcceleratedUpload(t *testing.T) { {"POST", "/api/v4/projects/2412/packages/helm/api/stable/charts", true}, {"POST", "/api/v4/projects/group%2Fproject/packages/helm/api/stable/charts", true}, {"POST", "/api/v4/projects/group%2Fsubgroup%2Fproject/packages/helm/api/stable/charts", true}, + {"POST", "/groups/my-group/-/group_members/bulk_reassignment_file", true}, } allowedHashFunctions := map[string][]string{ diff --git a/workhorse/internal/upstream/routes.go b/workhorse/internal/upstream/routes.go index d9b0de104b2..5d8f6d22e96 100644 --- a/workhorse/internal/upstream/routes.go +++ b/workhorse/internal/upstream/routes.go @@ -60,6 +60,7 @@ const ( gitProjectPattern = `^/.+\.git/` geoGitProjectPattern = `^/[^-].+\.git/` // Prevent matching routes like /-/push_from_secondary projectPattern = `^/([^/]+/){1,}[^/]+/` + groupPattern = `^/groups/([^/]+/){0,}[^/]+/` apiProjectPattern = apiPattern + `v4/projects/[^/]+` // API: Projects can be encoded via group%2Fsubgroup%2Fproject apiGroupPattern = apiPattern + `v4/groups/[^/]+` apiTopicPattern = apiPattern + `v4/topics` @@ -390,7 +391,8 @@ func configureRoutes(u *upstream) { newRoute(apiPattern+`v4/projects/import`, "api_projects_import", railsBackend), mimeMultipartUploader), u.route("POST", newRoute(apiPattern+`v4/projects/import-relation`, "api_projects_import_relation", railsBackend), mimeMultipartUploader), - + u.route("POST", + newRoute(groupPattern+`-/group_members/bulk_reassignment_file`, "group_placeholder_assignment", railsBackend), mimeMultipartUploader), // Project Import via UI upload acceleration u.route("POST", newRoute(importPattern+`gitlab_project`, "import_gitlab_project", railsBackend), mimeMultipartUploader), diff --git a/yarn.lock b/yarn.lock index b194f19e608..187d1148fbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1408,8 +1408,7 @@ resolved "https://registry.yarnpkg.com/@gitlab/fonts/-/fonts-1.3.0.tgz#df89c1bb6714e4a8a5d3272568aa4de7fb337267" integrity sha512-DoMUIN3DqjEn7wvcxBg/b7Ite5fTdF5EmuOZoBRo2j0UBGweDXmNBi+9HrTZs4cBU660dOxcf1hATFcG3npbPg== -"@gitlab/noop@^1.0.0", jackspeak@^2.3.5, "jackspeak@npm:@gitlab/noop@1.0.0": - name jackspeak +"@gitlab/noop@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.0.tgz#b1ecb8ae6b2abf9b2e28927e4fbb05b7a1b2704b" integrity sha512-nOltttik5o2BjBo8LnyeTFzHoLpMY/XcCVOC+lm9ZwU+ivEam8wafacMF0KTbRn1KVrIoHYdo70QnqS+vJiOVw== @@ -4530,10 +4529,10 @@ autoprefixer@^10.4.8: picocolors "^1.0.0" postcss-value-parser "^4.2.0" -autosize@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/autosize/-/autosize-5.0.1.tgz#ed269b0fa9b7eb47627048a1bb3299e99e003a0f" - integrity sha512-UIWUlE4TOVPNNj2jjrU39wI4hEYbneUypEqcyRmRFIx5CC2gNdg3rQr+Zh7/3h6egbBvm33TDQjNQKtj9Tk1HA== +autosize@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/autosize/-/autosize-6.0.1.tgz#64ee78dd7029be959eddd3afbbd33235b957e10f" + integrity sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ== available-typed-arrays@^1.0.7: version "1.0.7" @@ -5562,10 +5561,10 @@ core-js-pure@^3.30.2: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.35.0.tgz#4660033304a050215ae82e476bd2513a419fbb34" integrity sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew== -core-js@^3.29.1, core-js@^3.39.0, core-js@^3.6.5: - version "3.39.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.39.0.tgz#57f7647f4d2d030c32a72ea23a0555b2eaa30f83" - integrity sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g== +core-js@^3.29.1, core-js@^3.40.0, core-js@^3.6.5: + version "3.40.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.40.0.tgz#2773f6b06877d8eda102fc42f828176437062476" + integrity sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ== core-util-is@~1.0.0: version "1.0.3" @@ -9278,6 +9277,11 @@ iterall@^1.2.1: resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== +jackspeak@^2.3.5, "jackspeak@npm:@gitlab/noop@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.0.tgz#b1ecb8ae6b2abf9b2e28927e4fbb05b7a1b2704b" + integrity sha512-nOltttik5o2BjBo8LnyeTFzHoLpMY/XcCVOC+lm9ZwU+ivEam8wafacMF0KTbRn1KVrIoHYdo70QnqS+vJiOVw== + jed@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"