diff options
Diffstat (limited to 'app/helpers')
36 files changed, 502 insertions, 212 deletions
diff --git a/app/helpers/admin/background_migrations_helper.rb b/app/helpers/admin/background_migrations_helper.rb new file mode 100644 index 00000000000..698d81cc8a2 --- /dev/null +++ b/app/helpers/admin/background_migrations_helper.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Admin + module BackgroundMigrationsHelper + def batched_migration_status_badge_class_name(migration) + class_names = { + 'active' => 'badge-info', + 'paused' => 'badge-warning', + 'failed' => 'badge-danger', + 'finished' => 'badge-success' + } + + class_names[migration.status] + end + + # The extra logic here is needed because total_tuple_count is just + # an estimate and completed_rows also does not account for last jobs + # whose batch size is likely larger than the actual number of rows processed + def batched_migration_progress(migration, completed_rows) + return 100 if migration.finished? + return 0 unless completed_rows.to_i > 0 + return unless migration.total_tuple_count.to_i > 0 + + [100 * completed_rows / migration.total_tuple_count, 99].min + end + end +end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 0e3dff27da9..efdad22fa54 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -338,6 +338,8 @@ module ApplicationSettingsHelper :version_check_enabled, :web_ide_clientside_preview_enabled, :diff_max_patch_bytes, + :diff_max_files, + :diff_max_lines, :commit_email_hostname, :protected_ci_variables, :local_markdown_version, @@ -368,7 +370,9 @@ module ApplicationSettingsHelper :container_registry_cleanup_tags_service_max_list_size, :keep_latest_artifact, :whats_new_variant - ] + ].tap do |settings| + settings << :deactivate_dormant_users unless Gitlab.com? + end end def external_authorization_service_attributes diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index 439628f40c6..14783882f5e 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -44,7 +44,7 @@ module ClustersHelper base_domain: cluster.base_domain, application_ingress_external_ip: cluster.application_ingress_external_ip, auto_devops_help_path: help_page_path('topics/autodevops/index'), - external_endpoint_help_path: help_page_path('user/clusters/applications.md', anchor: 'pointing-your-dns-at-the-external-endpoint') + external_endpoint_help_path: help_page_path('user/project/clusters/index.md', anchor: 'base-domain') } end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 9b952ad127e..a7696ba4ea7 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -137,8 +137,6 @@ module CommitsHelper end def cherry_pick_projects_data(project) - return [] unless Feature.enabled?(:pick_into_project, project, default_enabled: :yaml) - [project, project.forked_from_project].compact.map do |project| { id: project.id.to_s, @@ -160,8 +158,8 @@ module CommitsHelper commit.author, ref, { - merge_request: merge_request, - pipeline_status: commit.status_for(ref), + merge_request: merge_request&.cache_key, + pipeline_status: commit.status_for(ref)&.cache_key, xhr: request.xhr?, controller: controller.controller_path, path: @path # referred to in #link_to_browse_code diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index c2f7fa2074c..0092743f96e 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -102,7 +102,7 @@ module DropdownsHelper def dropdown_filter(placeholder, search_id: nil) content_tag :div, class: "dropdown-input" do - filter_output = search_field_tag search_id, nil, data: { qa_selector: "dropdown_input_field" }, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off' + filter_output = search_field_tag search_id, nil, data: { qa_selector: "dropdown_input_field" }, id: nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off' filter_output << sprite_icon('search', css_class: 'dropdown-input-search') filter_output << sprite_icon('close', size: 16, css_class: 'dropdown-input-clear js-dropdown-input-clear') diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index 594c6fedef1..5927c82abe9 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -36,7 +36,7 @@ module EnvironmentsHelper "environment_name": environment.name, "environments_path": api_v4_projects_environments_path(id: project.id), "environment_id": environment.id, - "cluster_applications_documentation_path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'), + "cluster_applications_documentation_path" => help_page_path('user/clusters/integrations.md', anchor: 'elastic-stack-cluster-integration'), "clusters_path": project_clusters_path(project, format: :json) } end @@ -74,7 +74,6 @@ module EnvironmentsHelper 'metrics_dashboard_base_path' => metrics_dashboard_base_path(environment, project), 'current_environment_name' => environment.name, 'has_metrics' => "#{environment.has_metrics?}", - 'prometheus_status' => "#{environment.prometheus_status}", 'environment_state' => "#{environment.state}" } end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 03c3ee3363d..cd70c6f3962 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -157,7 +157,7 @@ module EventsHelper project_commit_url(event.project, id: event.commit_to) end - else + elsif event.ref_name project_commits_url(event.project, event.ref_name) end diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb index cf3e99eee49..9b4d0c0b9b3 100644 --- a/app/helpers/form_helper.rb +++ b/app/helpers/form_helper.rb @@ -2,19 +2,29 @@ module FormHelper def form_errors(model, type: 'form', truncate: []) - return unless model.errors.any? + errors = model.errors + + return unless errors.any? + + headline = n_( + 'The %{type} contains the following error:', + 'The %{type} contains the following errors:', + errors.count + ) % { type: type } - headline = n_('The %{type} contains the following error:', 'The %{type} contains the following errors:', model.errors.count) % { type: type } truncate = Array.wrap(truncate) - content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do - content_tag(:h4, headline) << - content_tag(:ul) do - messages = model.errors.map do |attribute, message| - message = html_escape_once(model.errors.full_message(attribute, message)).html_safe - message = content_tag(:span, message, class: 'str-truncated-100') if truncate.include?(attribute) + tag.div(class: 'alert alert-danger', id: 'error_explanation') do + tag.h4(headline) << + tag.ul do + messages = errors.map do |error| + attribute = error.attribute + message = error.message + + message = html_escape_once(errors.full_message(attribute, message)).html_safe + message = tag.span(message, class: 'str-truncated-100') if truncate.include?(attribute) - content_tag(:li, message) + tag.li(message) end messages.join.html_safe diff --git a/app/helpers/gitlab_script_tag_helper.rb b/app/helpers/gitlab_script_tag_helper.rb index 467f3f7305b..f784bb69dd8 100644 --- a/app/helpers/gitlab_script_tag_helper.rb +++ b/app/helpers/gitlab_script_tag_helper.rb @@ -21,4 +21,12 @@ module GitlabScriptTagHelper super end + + def preload_link_tag(source, options = {}) + # Chrome requires a nonce, see https://gitlab.com/gitlab-org/gitlab/-/issues/331810#note_584964908 + # It's likely to be a browser bug, but we need to work around it anyway + options[:nonce] = content_security_policy_nonce + + super + end end diff --git a/app/helpers/gitpod_helper.rb b/app/helpers/gitpod_helper.rb index 875a44c51bb..726c852fcdd 100644 --- a/app/helpers/gitpod_helper.rb +++ b/app/helpers/gitpod_helper.rb @@ -2,6 +2,6 @@ module GitpodHelper def gitpod_enable_description - s_('Enable %{linkStart}Gitpod%{linkEnd} integration to launch a development environment in your browser directly from GitLab.') + s_('Users can launch a development environment from a GitLab browser tab when the %{linkStart}Gitpod%{linkEnd} integration is enabled.') end end diff --git a/app/helpers/groups/group_members_helper.rb b/app/helpers/groups/group_members_helper.rb index 79191616c8f..c4d920dc317 100644 --- a/app/helpers/groups/group_members_helper.rb +++ b/app/helpers/groups/group_members_helper.rb @@ -13,12 +13,15 @@ module Groups::GroupMembersHelper render 'shared/members/invite_member', submit_url: group_group_members_path(group), access_levels: group.access_level_roles, default_access_level: default_access_level end - def group_members_list_data_json(group, members, pagination = {}) - group_members_list_data(group, members, pagination).to_json - end - - def group_group_links_list_data_json(group) - group_group_links_list_data(group).to_json + def group_members_app_data_json(group, members:, invited:, access_requests:) + { + user: group_members_list_data(group, members, { param_name: :page, params: { invited_members_page: nil, search_invited: nil } }), + group: group_group_links_list_data(group), + invite: group_members_list_data(group, invited.nil? ? [] : invited, { param_name: :invited_members_page, params: { page: nil } }), + access_request: group_members_list_data(group, access_requests.nil? ? [] : access_requests), + source_id: group.id, + can_manage_members: can?(current_user, :admin_group_member, group) + }.to_json end private @@ -32,13 +35,11 @@ module Groups::GroupMembersHelper end # Overridden in `ee/app/helpers/ee/groups/group_members_helper.rb` - def group_members_list_data(group, members, pagination) + def group_members_list_data(group, members, pagination = {}) { members: group_members_serialized(group, members), pagination: members_pagination_data(members, pagination), - member_path: group_group_member_path(group, ':id'), - source_id: group.id, - can_manage_members: can?(current_user, :admin_group_member, group) + member_path: group_group_member_path(group, ':id') } end @@ -48,8 +49,7 @@ module Groups::GroupMembersHelper { members: group_group_links_serialized(group_links), pagination: members_pagination_data(group_links), - member_path: group_group_link_path(group, ':id'), - source_id: group.id + member_path: group_group_link_path(group, ':id') } end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 8f647a49a64..26a5df321cd 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -3,14 +3,16 @@ module GroupsHelper def group_overview_nav_link_paths %w[ - groups#show - groups#details groups#activity groups#subgroups ].tap do |paths| - break paths if Feature.disabled?(:sidebar_refactor, current_user, default_enabled: :yaml) + extra_routes = if sidebar_refactor_disabled? + ['groups#show', 'groups#details'] + else + ['labels#index', 'group_members#index'] + end - paths.concat(['labels#index', 'group_members#index']) + paths.concat(extra_routes) end end @@ -43,7 +45,7 @@ module GroupsHelper end def group_information_title(group) - if Feature.enabled?(:sidebar_refactor, current_user) + if Feature.enabled?(:sidebar_refactor, current_user, default_enabled: :yaml) group.subgroup? ? _('Subgroup information') : _('Group information') else group.subgroup? ? _('Subgroup overview') : _('Group overview') @@ -75,6 +77,10 @@ module GroupsHelper can?(current_user, :change_share_with_group_lock, group) end + def can_change_prevent_sharing_groups_outside_hierarchy?(group) + can?(current_user, :change_prevent_sharing_groups_outside_hierarchy, group) + end + def can_disable_group_emails?(group) can?(current_user, :set_emails_disabled, group) && !group.parent&.emails_disabled? end @@ -86,14 +92,6 @@ module GroupsHelper .count end - def group_open_merge_requests_count(group) - if Feature.enabled?(:cached_sidebar_merge_requests_count, group, default_enabled: :yaml) - cached_issuables_count(@group, type: :merge_requests) - else - number_with_delimiter(group_merge_requests_count(state: 'opened')) - end - end - def group_merge_requests_count(state:) MergeRequestsFinder .new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true) @@ -194,6 +192,14 @@ module GroupsHelper end end + def link_to_group(group) + link_to(group.name, group_path(group)) + end + + def prevent_sharing_groups_outside_hierarchy_help_text(group) + s_("GroupSettings|This setting is only available on the top-level group and it applies to all subgroups. Groups that have already been shared with a group outside %{group} will still be shared, and this access will have to be revoked manually.").html_safe % { group: link_to_group(group) } + end + def parent_group_options(current_group) exclude_groups = current_group.self_and_descendants.pluck_primary_key exclude_groups << current_group.parent_id if current_group.parent_id diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index d1c84bd4141..b92e418006b 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -14,6 +14,7 @@ module IdeHelper 'render-whitespace-in-code': current_user.render_whitespace_in_code.to_s, 'codesandbox-bundler-url': Gitlab::CurrentSettings.web_ide_clientside_preview_bundler_url, 'branch-name' => @branch, + 'default-branch' => @project && @project.default_branch, 'file-path' => @path, 'merge-request' => @merge_request, 'fork-info' => @fork_info&.to_json, diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb index 889c058cb21..3c290701a5f 100644 --- a/app/helpers/invite_members_helper.rb +++ b/app/helpers/invite_members_helper.rb @@ -8,7 +8,7 @@ module InviteMembersHelper end def can_invite_group_for_project?(project) - Feature.enabled?(:invite_members_group_modal, project.group) && project.allowed_to_share_with_group? + Feature.enabled?(:invite_members_group_modal, project.group) && can_manage_project_members?(project) && project.allowed_to_share_with_group? end def directly_invite_members? @@ -21,17 +21,6 @@ module InviteMembersHelper experiment_enabled?(:invite_members_empty_group_version_a) && Ability.allowed?(current_user, :admin_group_member, group) end - def dropdown_invite_members_link(form_model) - link_to invite_members_url(form_model), - data: { - 'track-event': 'click_link', - 'track-label': tracking_label, - 'track-property': experiment_tracking_category_and_group(:invite_members_new_dropdown) - } do - invite_member_link_content - end - end - def invite_accepted_notice(member) case member.source when Project @@ -43,22 +32,11 @@ module InviteMembersHelper end end - private - - def invite_members_url(form_model) - case form_model - when Project - project_project_members_path(form_model) - when Group - group_group_members_path(form_model) + def group_select_data(group) + if group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy + { groups_filter: 'descendant_groups', parent_id: group.root_ancestor.id } + else + {} end end - - def invite_member_link_content - text = s_('InviteMember|Invite members') - - return text unless experiment_enabled?(:invite_members_new_dropdown) - - "#{text} #{emoji_icon('shaking_hands', 'aria-hidden': true, class: 'gl-font-base gl-vertical-align-baseline')}".html_safe - end end diff --git a/app/helpers/issuables_description_templates_helper.rb b/app/helpers/issuables_description_templates_helper.rb index 5f69098de56..6cafde65c5c 100644 --- a/app/helpers/issuables_description_templates_helper.rb +++ b/app/helpers/issuables_description_templates_helper.rb @@ -29,17 +29,12 @@ module IssuablesDescriptionTemplatesHelper def issuable_templates(project, issuable_type) @template_types ||= {} @template_types[project.id] ||= {} - @template_types[project.id][issuable_type] ||= TemplateFinder.all_template_names_hash_or_array(project, issuable_type) + @template_types[project.id][issuable_type] ||= TemplateFinder.all_template_names(project, issuable_type.pluralize) end def issuable_templates_names(issuable) all_templates = issuable_templates(ref_project, issuable.to_ability_name) - - if ref_project.inherited_issuable_templates_enabled? - all_templates.values.flatten.map { |tpl| tpl[:name] if tpl[:project_id] == ref_project.id }.compact.uniq - else - all_templates.map { |template| template[:name] } - end + all_templates.values.flatten.map { |tpl| tpl[:name] if tpl[:project_id] == ref_project.id }.compact.uniq end def selected_template(issuable) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index c662dabe453..c40feb42eea 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -220,6 +220,10 @@ module IssuablesHelper @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project) end + def issuable_project_reference(issuable) + "#{issuable.project.full_name} #{issuable.to_reference}" + end + def issuable_initial_data(issuable) data = { endpoint: issuable_path(issuable), diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 1449725fb2b..91920277c50 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -192,15 +192,15 @@ module IssuesHelper empty_state_svg_path: image_path('illustrations/issues.svg'), endpoint: expose_path(api_v4_projects_issues_path(id: project.id)), export_csv_path: export_csv_project_issues_path(project), - has_issues: project_issues(project).exists?.to_s, + has_project_issues: project_issues(project).exists?.to_s, import_csv_issues_path: import_csv_namespace_project_issues_path, initial_email: project.new_issuable_address(current_user, 'issue'), is_signed_in: current_user.present?.to_s, issues_path: project_issues_path(project), - jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'), + jira_integration_path: help_page_url('integration/jira/', anchor: 'view-jira-issues'), markdown_help_path: help_page_path('user/markdown'), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), - new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }), + new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }), project_import_jira_path: project_import_jira_path(project), project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json), project_milestones_path: project_milestones_path(project, format: :json), diff --git a/app/helpers/keyset_helper.rb b/app/helpers/keyset_helper.rb new file mode 100644 index 00000000000..e7f6f884091 --- /dev/null +++ b/app/helpers/keyset_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module KeysetHelper + def keyset_paginate(paginator, without_first_and_last_pages: false) + page_params = params.to_unsafe_h + + render('kaminari/gitlab/keyset_paginator', { + paginator: paginator, + without_first_and_last_pages: without_first_and_last_pages, + page_params: page_params + }) + end +end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 514f5fafd65..81059834136 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -80,12 +80,6 @@ module MergeRequestsHelper diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff.id, start_sha: start_sha) end - def version_index(merge_request_diff) - return if @merge_request_diffs.empty? - - @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff) - end - def merge_params(merge_request) { auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb new file mode 100644 index 00000000000..b952aeacb13 --- /dev/null +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -0,0 +1,188 @@ +# frozen_string_literal: true + +module Nav + module NewDropdownHelper + def new_dropdown_view_model(group:, project:) + return unless current_user + + menu_sections = [] + + if group&.persisted? + menu_sections.push(group_menu_section(group)) + elsif project&.persisted? + menu_sections.push(project_menu_section(project)) + end + + menu_sections.push(general_menu_section) + + { + title: _("New..."), + menu_sections: menu_sections.select { |x| x.fetch(:menu_items).any? } + } + end + + def new_repo_experiment_text + experiment(:new_repo, user: current_user) do |e| + e.use { _('New project') } + e.try { _('New project/repository') } + end.run + end + + private + + def group_menu_section(group) + menu_items = [] + + if can?(current_user, :create_projects, group) + menu_items.push( + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'new_project', + title: new_repo_experiment_text, + href: new_project_path(namespace_id: group.id), + data: { track_experiment: 'new_repo', track_event: 'click_link_new_project_group', track_label: 'plus_menu_dropdown' } + ) + ) + end + + if can?(current_user, :create_subgroup, group) + menu_items.push( + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'new_subgroup', + title: _('New subgroup'), + href: new_group_path(parent_id: group.id), + data: { track_event: 'click_link_new_subgroup', track_label: 'plus_menu_dropdown' } + ) + ) + end + + menu_items.push(create_epic_menu_item(group)) + + if Gitlab::Experimentation.active?(:invite_members_new_dropdown) && can?(current_user, :admin_group_member, group) + menu_items.push( + invite_members_menu_item( + href: group_group_members_path(group) + ) + ) + end + + { + title: _('This group'), + menu_items: menu_items.compact + } + end + + def project_menu_section(project) + menu_items = [] + merge_project = merge_request_source_project_for_project(project) + + if show_new_issue_link?(project) + menu_items.push( + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'new_issue', + title: _('New issue'), + href: new_project_issue_path(project), + data: { track_event: 'click_link_new_issue', track_label: 'plus_menu_dropdown', qa_selector: 'new_issue_link' } + ) + ) + end + + if merge_project + menu_items.push( + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'new_mr', + title: _('New merge request'), + href: project_new_merge_request_path(merge_project), + data: { track_event: 'click_link_new_mr', track_label: 'plus_menu_dropdown' } + ) + ) + end + + if can?(current_user, :create_snippet, project) + menu_items.push( + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'new_snippet', + title: _('New snippet'), + href: new_project_snippet_path(project), + data: { track_event: 'click_link_new_snippet_project', track_label: 'plus_menu_dropdown' } + ) + ) + end + + if Gitlab::Experimentation.active?(:invite_members_new_dropdown) && can_import_members? + menu_items.push( + invite_members_menu_item( + href: project_project_members_path(project) + ) + ) + end + + { + title: _('This project'), + menu_items: menu_items + } + end + + def general_menu_section + menu_items = [] + + if current_user.can_create_project? + menu_items.push( + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'general_new_project', + title: new_repo_experiment_text, + href: new_project_path, + data: { track_experiment: 'new_repo', track_event: 'click_link_new_project', track_label: 'plus_menu_dropdown', qa_selector: 'global_new_project_link' } + ) + ) + end + + if current_user.can_create_group? + menu_items.push( + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'general_new_group', + title: _('New group'), + href: new_group_path, + data: { track_event: 'click_link_new_group', track_label: 'plus_menu_dropdown' } + ) + ) + end + + if current_user.can?(:create_snippet) + menu_items.push( + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'general_new_snippet', + title: _('New snippet'), + href: new_snippet_path, + data: { track_event: 'click_link_new_snippet_parent', track_label: 'plus_menu_dropdown', qa_selector: 'global_new_snippet_link' } + ) + ) + end + + { + title: _('GitLab'), + menu_items: menu_items + } + end + + def invite_members_menu_item(href:) + ::Gitlab::Nav::TopNavMenuItem.build( + id: 'invite', + title: s_('InviteMember|Invite members'), + emoji: ('shaking_hands' if experiment_enabled?(:invite_members_new_dropdown)), + href: href, + data: { + track_event: 'click_link', + track_label: tracking_label, + track_property: experiment_tracking_category_and_group(:invite_members_new_dropdown) + } + ) + end + + # Overridden in EE + def create_epic_menu_item(group) + nil + end + end +end + +Nav::NewDropdownHelper.prepend_mod diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index 159b7ca87f9..b8ddb932b73 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -4,52 +4,90 @@ module Nav module TopNavHelper PROJECTS_VIEW = :projects GROUPS_VIEW = :groups + NEW_VIEW = :new + SEARCH_VIEW = :search def top_nav_view_model(project:, group:) builder = ::Gitlab::Nav::TopNavViewModelBuilder.new - if current_user - build_view_model(builder: builder, project: project, group: group) - else - build_anonymous_view_model(builder: builder) + build_base_view_model(builder: builder, project: project, group: group) + + builder.build + end + + def top_nav_responsive_view_model(project:, group:) + builder = ::Gitlab::Nav::TopNavViewModelBuilder.new + + build_base_view_model(builder: builder, project: project, group: group) + + new_view_model = new_dropdown_view_model(project: project, group: group) + + if new_view_model + builder.add_view(NEW_VIEW, new_view_model) + end + + if top_nav_show_search + builder.add_view(SEARCH_VIEW, ::Gitlab::Nav::TopNavMenuItem.build(**top_nav_search_menu_item_attrs)) end builder.build end + def top_nav_show_search + header_link?(:search) + end + + def top_nav_search_menu_item_attrs + { + id: 'search', + title: _('Search'), + icon: 'search', + href: search_context.search_url + } + end + private + def build_base_view_model(builder:, project:, group:) + if current_user + build_view_model(builder: builder, project: project, group: group) + else + build_anonymous_view_model(builder: builder) + end + end + def build_anonymous_view_model(builder:) # These come from `app/views/layouts/nav/_explore.html.ham` if explore_nav_link?(:projects) - builder.add_primary_menu_item( - **projects_menu_item_attrs.merge( - { - active: active_nav_link?(path: %w[dashboard#show root#show projects#trending projects#starred projects#index]), - href: explore_root_path - }) + builder.add_primary_menu_item_with_shortcut( + href: explore_root_path, + active: nav == 'project' || active_nav_link?(path: %w[dashboard#show root#show projects#trending projects#starred projects#index]), + **projects_menu_item_attrs ) end if explore_nav_link?(:groups) - builder.add_primary_menu_item( - **groups_menu_item_attrs.merge( - { - active: active_nav_link?(controller: [:groups, 'groups/milestones', 'groups/group_members']), - href: explore_groups_path - }) + builder.add_primary_menu_item_with_shortcut( + href: explore_groups_path, + active: nav == 'group' || active_nav_link?(controller: [:groups, 'groups/milestones', 'groups/group_members']), + **groups_menu_item_attrs ) end if explore_nav_link?(:snippets) - builder.add_primary_menu_item( - **snippets_menu_item_attrs.merge( - { - active: active_nav_link?(controller: :snippets), - href: explore_snippets_path - }) + builder.add_primary_menu_item_with_shortcut( + active: active_nav_link?(controller: :snippets), + href: explore_snippets_path, + **snippets_menu_item_attrs ) end + + builder.add_secondary_menu_item( + id: 'help', + title: _('Help'), + icon: 'question-o', + href: help_path + ) end def build_view_model(builder:, project:, group:) @@ -57,13 +95,13 @@ module Nav if dashboard_nav_link?(:projects) current_item = project ? current_project(project: project) : {} - builder.add_primary_menu_item( - **projects_menu_item_attrs.merge({ - active: active_nav_link?(path: %w[root#index projects#trending projects#starred dashboard/projects#index]), - css_class: 'qa-projects-dropdown', - data: { track_label: "projects_dropdown", track_event: "click_dropdown", track_experiment: "new_repo" }, - view: PROJECTS_VIEW - }) + builder.add_primary_menu_item_with_shortcut( + active: nav == 'project' || active_nav_link?(path: %w[root#index projects#trending projects#starred dashboard/projects#index]), + css_class: 'qa-projects-dropdown', + data: { track_label: "projects_dropdown", track_event: "click_dropdown", track_experiment: "new_repo" }, + view: PROJECTS_VIEW, + shortcut_href: dashboard_projects_path, + **projects_menu_item_attrs ) builder.add_view(PROJECTS_VIEW, container_view_props(namespace: 'projects', current_item: current_item, submenu: projects_submenu)) end @@ -71,46 +109,47 @@ module Nav if dashboard_nav_link?(:groups) current_item = group ? current_group(group: group) : {} - builder.add_primary_menu_item( - **groups_menu_item_attrs.merge({ - active: active_nav_link?(path: %w[dashboard/groups explore/groups]), - css_class: 'qa-groups-dropdown', - data: { track_label: "groups_dropdown", track_event: "click_dropdown" }, - view: GROUPS_VIEW - }) + builder.add_primary_menu_item_with_shortcut( + active: nav == 'group' || active_nav_link?(path: %w[dashboard/groups explore/groups]), + css_class: 'qa-groups-dropdown', + data: { track_label: "groups_dropdown", track_event: "click_dropdown" }, + view: GROUPS_VIEW, + shortcut_href: dashboard_groups_path, + **groups_menu_item_attrs ) builder.add_view(GROUPS_VIEW, container_view_props(namespace: 'groups', current_item: current_item, submenu: groups_submenu)) end if dashboard_nav_link?(:milestones) - builder.add_primary_menu_item( + builder.add_primary_menu_item_with_shortcut( id: 'milestones', title: 'Milestones', + href: dashboard_milestones_path, active: active_nav_link?(controller: 'dashboard/milestones'), icon: 'clock', data: { qa_selector: 'milestones_link' }, - href: dashboard_milestones_path + shortcut_class: 'dashboard-shortcuts-milestones' ) end if dashboard_nav_link?(:snippets) - builder.add_primary_menu_item( - **snippets_menu_item_attrs.merge({ - active: active_nav_link?(controller: 'dashboard/snippets'), - data: { qa_selector: 'snippets_link' }, - href: dashboard_snippets_path - }) + builder.add_primary_menu_item_with_shortcut( + active: active_nav_link?(controller: 'dashboard/snippets'), + data: { qa_selector: 'snippets_link' }, + href: dashboard_snippets_path, + **snippets_menu_item_attrs ) end if dashboard_nav_link?(:activity) - builder.add_primary_menu_item( + builder.add_primary_menu_item_with_shortcut( id: 'activity', title: 'Activity', + href: activity_dashboard_path, active: active_nav_link?(path: 'dashboard#activity'), icon: 'history', data: { qa_selector: 'activity_link' }, - href: activity_dashboard_path + shortcut_class: 'dashboard-shortcuts-activity' ) end @@ -137,7 +176,7 @@ module Nav active: active_nav_link?(controller: 'admin/sessions'), icon: 'lock-open', href: destroy_admin_session_path, - method: :post + data: { method: 'post' } ) elsif current_user.admin? builder.add_secondary_menu_item( @@ -165,7 +204,8 @@ module Nav { id: 'project', title: _('Projects'), - icon: 'project' + icon: 'project', + shortcut_class: 'dashboard-shortcuts-projects' } end @@ -173,7 +213,8 @@ module Nav { id: 'groups', title: 'Groups', - icon: 'group' + icon: 'group', + shortcut_class: 'dashboard-shortcuts-groups' } end @@ -181,7 +222,8 @@ module Nav { id: 'snippets', title: _('Snippets'), - icon: 'snippet' + icon: 'snippet', + shortcut_class: 'dashboard-shortcuts-snippets' } end @@ -234,7 +276,11 @@ module Nav builder = ::Gitlab::Nav::TopNavMenuBuilder.new builder.add_primary_menu_item(id: 'your', title: _('Your groups'), href: dashboard_groups_path) builder.add_primary_menu_item(id: 'explore', title: _('Explore groups'), href: explore_groups_path) - builder.add_secondary_menu_item(id: 'create', title: _('Create group'), href: new_group_path(anchor: 'create-group-pane')) + + if current_user.can_create_group? + builder.add_secondary_menu_item(id: 'create', title: _('Create group'), href: new_group_path) + end + builder.build end end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index aab1a44bdfb..b5171dfbebd 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -12,7 +12,6 @@ module NavHelper def page_with_sidebar_class class_name = page_gutter_class class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar - class_name << 'sidebar-refactoring' if Feature.enabled?(:sidebar_refactor, current_user) class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar class_name -= ['right-sidebar-expanded'] if defined?(@right_sidebar) && !@right_sidebar @@ -61,7 +60,7 @@ module NavHelper end def admin_monitoring_nav_links - %w(system_info background_jobs health_check requests_profiles) + %w(system_info background_migrations background_jobs health_check requests_profiles) end def admin_analytics_nav_links diff --git a/app/helpers/notify_helper.rb b/app/helpers/notify_helper.rb index 38c98776fdf..c0ba93f4a30 100644 --- a/app/helpers/notify_helper.rb +++ b/app/helpers/notify_helper.rb @@ -9,29 +9,15 @@ module NotifyHelper link_to(entity.to_reference(full: full), issue_url(entity, *args)) end - def invited_role_description(role_name) - case role_name - when "Guest" - s_("InviteEmail|As a guest, you can view projects, leave comments, and create issues.") - when "Reporter" - s_("InviteEmail|As a reporter, you can view projects and reports, and leave comments on issues.") - when "Developer" - s_("InviteEmail|As a developer, you have full access to projects, so you can take an idea from concept to production.") - when "Maintainer" - s_("InviteEmail|As a maintainer, you have full access to projects. You can push commits to the default branch and deploy to production.") - when "Owner" - s_("InviteEmail|As an owner, you have full access to projects and can manage access to the group, including inviting new members.") - when "Minimal Access" - s_("InviteEmail|As a user with minimal access, you can view the high-level group from the UI and API.") - end - end - def invited_to_description(source) - case source - when "project" - s_('InviteEmail|Projects can be used to host your code, track issues, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD.') - when "group" - s_('InviteEmail|Groups assemble related projects together and grant members access to several projects at once.') - end + default_description = + case source + when Project + s_('InviteEmail|Projects are used to host and collaborate on code, track issues, and continuously build, test, and deploy your app with built-in GitLab CI/CD.') + when Group + s_('InviteEmail|Groups assemble related projects together and grant members access to several projects at once.') + end + + (source.description || default_description).truncate(200, separator: ' ') end end diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb index df07baa2d03..fb410c46128 100644 --- a/app/helpers/operations_helper.rb +++ b/app/helpers/operations_helper.rb @@ -10,6 +10,9 @@ module OperationsHelper end def alerts_settings_data(disabled: false) + setting = project_incident_management_setting + templates = setting.available_issue_templates.map { |t| { key: t.key, name: t.name } } + { 'prometheus_activated' => prometheus_service.manual_configuration?.to_s, 'prometheus_form_path' => scoped_integration_path(prometheus_service), @@ -21,21 +24,22 @@ module OperationsHelper 'alerts_usage_url' => project_alert_management_index_path(@project), 'disabled' => disabled.to_s, 'project_path' => @project.full_path, - 'multi_integrations' => 'false' + 'multi_integrations' => 'false', + 'templates' => templates.to_json, + 'create_issue' => setting.create_issue.to_s, + 'issue_template_key' => setting.issue_template_key.to_s, + 'send_email' => setting.send_email.to_s, + 'auto_close_incident' => setting.auto_close_incident.to_s, + 'pagerduty_reset_key_path' => reset_pagerduty_token_project_settings_operations_path(@project), + 'operations_settings_endpoint' => project_settings_operations_path(@project) } end def operations_settings_data setting = project_incident_management_setting - templates = setting.available_issue_templates.map { |t| { key: t.key, name: t.name } } { operations_settings_endpoint: project_settings_operations_path(@project), - templates: templates.to_json, - create_issue: setting.create_issue.to_s, - issue_template_key: setting.issue_template_key.to_s, - send_email: setting.send_email.to_s, - auto_close_incident: setting.auto_close_incident.to_s, pagerduty_active: setting.pagerduty_active.to_s, pagerduty_token: setting.pagerduty_token.to_s, pagerduty_webhook_url: project_incidents_integrations_pagerduty_url(@project, token: setting.pagerduty_token), diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index 04465f7798c..fe41c041b4f 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -53,4 +53,14 @@ module PackagesHelper category = args.delete(:category) || self.class.name ::Gitlab::Tracking.event(category, event_name.to_s, **args) end + + def show_cleanup_policy_on_alert(project) + Gitlab.com? && + Gitlab.config.registry.enabled && + project.container_registry_enabled && + !Gitlab::CurrentSettings.container_expiration_policies_enable_historic_entries && + Feature.enabled?(:container_expiration_policies_historic_entry, project) && + project.container_expiration_policy.nil? && + project.container_repositories.exists? + end end diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index d851ed3db8f..76f2ee0981b 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -66,6 +66,10 @@ module PreferencesHelper @user_application_theme_css_filename ||= Gitlab::Themes.for_user(current_user).css_filename end + def user_theme_primary_color + Gitlab::Themes.for_user(current_user).primary_color + end + def user_color_scheme Gitlab::ColorSchemes.for_user(current_user).css_class end @@ -83,13 +87,17 @@ module PreferencesHelper def integration_views [].tap do |views| - views << { name: 'gitpod', message: gitpod_enable_description, message_url: 'https://gitpod.io/', help_link: help_page_path('integration/gitpod.md') } if Gitlab::CurrentSettings.gitpod_enabled + views << { name: 'gitpod', message: gitpod_enable_description, message_url: gitpod_url_placeholder, help_link: help_page_path('integration/gitpod.md') } if Gitlab::CurrentSettings.gitpod_enabled views << { name: 'sourcegraph', message: sourcegraph_url_message, message_url: Gitlab::CurrentSettings.sourcegraph_url, help_link: help_page_path('user/profile/preferences.md', anchor: 'sourcegraph') } if Gitlab::Sourcegraph.feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled end end private + def gitpod_url_placeholder + Gitlab::CurrentSettings.gitpod_url.presence || 'https://gitpod.io/' + end + # Ensure that anyone adding new options updates `DASHBOARD_CHOICES` too def validate_dashboard_choices!(user_dashboards) if user_dashboards.size != localized_dashboard_choices.size diff --git a/app/helpers/projects/project_members_helper.rb b/app/helpers/projects/project_members_helper.rb index fa68bbad135..0871d5638b8 100644 --- a/app/helpers/projects/project_members_helper.rb +++ b/app/helpers/projects/project_members_helper.rb @@ -27,12 +27,15 @@ module Projects::ProjectMembersHelper project.group.has_owner?(current_user) end - def project_members_list_data_json(project, members, pagination = {}) - project_members_list_data(project, members, pagination).to_json - end - - def project_group_links_list_data_json(project, group_links) - project_group_links_list_data(project, group_links).to_json + def project_members_app_data_json(project, members:, group_links:, invited:, access_requests:) + { + user: project_members_list_data(project, members, { param_name: :page, params: { search_groups: nil } }), + group: project_group_links_list_data(project, group_links), + invite: project_members_list_data(project, invited.nil? ? [] : invited), + access_request: project_members_list_data(project, access_requests.nil? ? [] : access_requests), + source_id: project.id, + can_manage_members: can_manage_project_members?(project) + }.to_json end private @@ -45,13 +48,11 @@ module Projects::ProjectMembersHelper GroupLink::ProjectGroupLinkSerializer.new.represent(group_links, { current_user: current_user }) end - def project_members_list_data(project, members, pagination) + def project_members_list_data(project, members, pagination = {}) { members: project_members_serialized(project, members), pagination: members_pagination_data(members, pagination), - member_path: project_project_member_path(project, ':id'), - source_id: project.id, - can_manage_members: can_manage_project_members?(project) + member_path: project_project_member_path(project, ':id') } end @@ -59,9 +60,7 @@ module Projects::ProjectMembersHelper { members: project_group_links_serialized(group_links), pagination: members_pagination_data(group_links), - member_path: project_group_link_path(project, ':id'), - source_id: project.id, - can_manage_members: can_manage_project_members?(project) + member_path: project_group_link_path(project, ':id') } end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index f2a50ce1325..8800bd0643c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -612,12 +612,12 @@ module ProjectsHelper end def settings_container_registry_expiration_policy_available?(project) - Feature.disabled?(:sidebar_refactor, current_user) && + Feature.disabled?(:sidebar_refactor, current_user, default_enabled: :yaml) && can_destroy_container_registry_image?(current_user, project) end def settings_packages_and_registries_enabled?(project) - Feature.enabled?(:sidebar_refactor, current_user) && + Feature.enabled?(:sidebar_refactor, current_user, default_enabled: :yaml) && can_destroy_container_registry_image?(current_user, project) end diff --git a/app/helpers/registrations_helper.rb b/app/helpers/registrations_helper.rb index 79f0a66f995..24131e32c6c 100644 --- a/app/helpers/registrations_helper.rb +++ b/app/helpers/registrations_helper.rb @@ -7,6 +7,16 @@ module RegistrationsHelper devise_mapping.omniauthable? && button_based_providers_enabled? end + + def signup_username_data_attributes + { + min_length: User::MIN_USERNAME_LENGTH, + min_length_message: s_('SignUp|Username is too short (minimum is %{min_length} characters).') % { min_length: User::MIN_USERNAME_LENGTH }, + max_length: User::MAX_USERNAME_LENGTH, + max_length_message: s_('SignUp|Username is too long (maximum is %{max_length} characters).') % { max_length: User::MAX_USERNAME_LENGTH }, + qa_selector: 'new_user_username_field' + } + end end RegistrationsHelper.prepend_mod_with('RegistrationsHelper') diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 1f4c98d6f28..e07ee22339a 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -9,7 +9,8 @@ module SearchHelper :repository_ref, :snippets, :sort, - :force_search_results + :force_search_results, + :project_ids ].freeze def search_autocomplete_opts(term) diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index 3d3ab3a6972..83000189ab3 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -107,7 +107,7 @@ module ServicesHelper reset_path: scoped_reset_integration_path(integration, group: group) } - if integration.is_a?(JiraService) + if integration.is_a?(Integrations::Jira) form_data[:jira_issue_transition_automatic] = integration.jira_issue_transition_automatic form_data[:jira_issue_transition_id] = integration.jira_issue_transition_id end diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 0fc306a3f2e..39ad8ed8a0f 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -20,6 +20,14 @@ module SidebarsHelper Sidebars::Projects::Context.new(**context_data) end + def sidebar_refactor_enabled? + Feature.enabled?(:sidebar_refactor, current_user, default_enabled: :yaml) + end + + def sidebar_refactor_disabled? + !sidebar_refactor_enabled? + end + private def sidebar_project_tracking_attrs diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 1d3242ca44a..e64e1c935dd 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -123,7 +123,21 @@ module TabHelper def route_matches_pages?(pages) Array(pages).compact.any? do |single_page| - current_page?(single_page) + # We need to distinguish between Hash argument and other types of + # arguments (for example String) in order to fix deprecation kwargs + # warning. + # + # This can be refactored to + # + # current_page?(single_page) + # + # When we migrate to Ruby 3 or the Rails version contains the following: + # https://github.com/rails/rails/commit/81d90d81d0ee1fc1a649ab705119a71f2d04c8a2 + if single_page.is_a?(Hash) + current_page?(**single_page) + else + current_page?(single_page) + end end end diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index bfc8803f514..86289ec8ed2 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -15,16 +15,6 @@ module TagsHelper project_tags_path(@project, @id, options) end - def tag_list(project) - html = [] - - project.tag_list.each do |tag| - html << link_to(tag, tag_path(tag)) - end - - html.join.html_safe - end - def protected_tag?(project, tag) ProtectedTag.protected?(project, tag.name) end diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index 23db3be631c..c44da915105 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -1,22 +1,16 @@ # frozen_string_literal: true module UserCalloutsHelper - ADMIN_INTEGRATIONS_MOVED = 'admin_integrations_moved' GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration' GCP_SIGNUP_OFFER = 'gcp_signup_offer' SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed' SERVICE_TEMPLATES_DEPRECATED_CALLOUT = 'service_templates_deprecated_callout' TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight' - WEBHOOKS_MOVED = 'webhooks_moved' CUSTOMIZE_HOMEPAGE = 'customize_homepage' FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version' REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout' UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout' - def show_admin_integrations_moved? - !user_dismissed?(ADMIN_INTEGRATIONS_MOVED) - end - def show_gke_cluster_integration_callout?(project) active_nav_link?(controller: sidebar_operations_paths) && can?(current_user, :create_cluster, project) && @@ -48,10 +42,6 @@ module UserCalloutsHelper !user_dismissed?(SERVICE_TEMPLATES_DEPRECATED_CALLOUT) end - def show_webhooks_moved_alert? - !user_dismissed?(WEBHOOKS_MOVED) - end - def show_customize_homepage_banner? !user_dismissed?(CUSTOMIZE_HOMEPAGE) end diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb index 6f94c241914..f8d7264d4cc 100644 --- a/app/helpers/version_check_helper.rb +++ b/app/helpers/version_check_helper.rb @@ -19,7 +19,7 @@ module VersionCheckHelper end def source_host_url - Gitlab::COM_URL + Gitlab::Saas.com_url end def source_code_group |