diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index d9aa1be0b1e..7bfb5fe7841 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -ac989862106589558866e01fc5d77ad7326c99e4 +a6c5964bb455f77a0c79898f0b99c4c7df3aee3a diff --git a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue index dbc4565b19d..ebcc4b85ac4 100644 --- a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue +++ b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue @@ -191,7 +191,7 @@ export default {
- + {{ $options.i18n.uploadingLabel }} diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js index 6216ab5401d..359a276aa74 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -1,5 +1,3 @@ -import { __ } from '~/locale'; - export default class FilteredSearchTokenKeys { constructor(tokenKeys = [], alternativeTokenKeys = [], conditions = []) { this.tokenKeys = tokenKeys; @@ -76,24 +74,6 @@ export default class FilteredSearchTokenKeys { ); } - addExtraTokensForIssues() { - const confidentialToken = { - formattedKey: __('Confidential'), - key: 'confidential', - type: 'string', - param: '', - symbol: '', - icon: 'eye-slash', - tag: __('Yes or No'), - lowercaseValueOnSubmit: true, - uppercaseTokenName: false, - capitalizeTokenValue: true, - }; - - this.tokenKeys.push(confidentialToken); - this.tokenKeysWithAlternative.push(confidentialToken); - } - removeTokensForKeys(...keys) { const keysSet = new Set(keys); diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index 7ca3f20ec1c..3885be1dadc 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -129,5 +129,8 @@ "VulnerabilityLocationGeneric", "VulnerabilityLocationSast", "VulnerabilityLocationSecretDetection" + ], + "WorkItemWidget": [ + "WorkItemWidgetDescription" ] } diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 725c38defc3..912a4ea2c11 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -1,26 +1,3 @@ -import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; -import { initBulkUpdateSidebar } from '~/issuable/bulk_update_sidebar'; import { mountIssuesListApp } from '~/issues/list'; -import initManualOrdering from '~/issues/manual_ordering'; -import { FILTERED_SEARCH } from '~/filtered_search/constants'; -import initFilteredSearch from '~/pages/search/init_filtered_search'; -import projectSelect from '~/project_select'; -if (gon.features?.vueIssuesList) { - mountIssuesListApp(); -} else { - const ISSUE_BULK_UPDATE_PREFIX = 'issue_'; - - IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); - IssuableFilteredSearchTokenKeys.removeTokensForKeys('release'); - initBulkUpdateSidebar(ISSUE_BULK_UPDATE_PREFIX); - - initFilteredSearch({ - page: FILTERED_SEARCH.ISSUES, - isGroupDecendent: true, - useDefaultState: true, - filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, - }); - projectSelect(); - initManualOrdering(); -} +mountIssuesListApp(); diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index 44b1d5277d1..b320d8a61c2 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -1,34 +1,6 @@ -import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; -import { initCsvImportExportButtons, initIssuableByEmail } from '~/issuable'; -import { initBulkUpdateSidebar, initIssueStatusSelect } from '~/issuable/bulk_update_sidebar'; import { mountIssuesListApp, mountJiraIssuesListApp } from '~/issues/list'; -import initManualOrdering from '~/issues/manual_ordering'; -import { FILTERED_SEARCH } from '~/filtered_search/constants'; -import { ISSUABLE_INDEX } from '~/issuable/constants'; -import initFilteredSearch from '~/pages/search/init_filtered_search'; -import UsersSelect from '~/users_select'; - -if (gon.features?.vueIssuesList) { - mountIssuesListApp(); -} else { - IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); - - initFilteredSearch({ - page: FILTERED_SEARCH.ISSUES, - filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, - useDefaultState: true, - }); - - initBulkUpdateSidebar(ISSUABLE_INDEX.ISSUE); - initIssueStatusSelect(); - new UsersSelect(); // eslint-disable-line no-new - - initCsvImportExportButtons(); - initIssuableByEmail(); - initManualOrdering(); -} - -new ShortcutsNavigation(); // eslint-disable-line no-new +mountIssuesListApp(); mountJiraIssuesListApp(); +new ShortcutsNavigation(); // eslint-disable-line no-new diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index ae3b6125bde..a04fd09aa22 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -8,7 +8,7 @@ class Clusters::ClustersController < Clusters::BaseController before_action :cluster, only: [:cluster_status, :show, :update, :destroy, :clear_cache] before_action :user_cluster, only: [:connect] before_action :authorize_read_cluster!, only: [:show, :index] - before_action :authorize_create_cluster!, only: [:connect, :authorize_aws_role] + before_action :authorize_create_cluster!, only: [:connect] before_action :authorize_update_cluster!, only: [:update] before_action :update_applications_status, only: [:cluster_status] before_action :ensure_feature_enabled!, except: [:index, :new_cluster_docs] @@ -16,15 +16,6 @@ class Clusters::ClustersController < Clusters::BaseController helper_method :token_in_session STATUS_POLLING_INTERVAL = 10_000 - AWS_CSP_DOMAINS = %w[https://ec2.ap-east-1.amazonaws.com https://ec2.ap-northeast-1.amazonaws.com https://ec2.ap-northeast-2.amazonaws.com https://ec2.ap-northeast-3.amazonaws.com https://ec2.ap-south-1.amazonaws.com https://ec2.ap-southeast-1.amazonaws.com https://ec2.ap-southeast-2.amazonaws.com https://ec2.ca-central-1.amazonaws.com https://ec2.eu-central-1.amazonaws.com https://ec2.eu-north-1.amazonaws.com https://ec2.eu-west-1.amazonaws.com https://ec2.eu-west-2.amazonaws.com https://ec2.eu-west-3.amazonaws.com https://ec2.me-south-1.amazonaws.com https://ec2.sa-east-1.amazonaws.com https://ec2.us-east-1.amazonaws.com https://ec2.us-east-2.amazonaws.com https://ec2.us-west-1.amazonaws.com https://ec2.us-west-2.amazonaws.com https://ec2.af-south-1.amazonaws.com https://iam.amazonaws.com].freeze - - content_security_policy do |p| - next if p.directives.blank? - - default_connect_src = p.directives['connect-src'] || p.directives['default-src'] - connect_src_values = Array.wrap(default_connect_src) | AWS_CSP_DOMAINS - p.connect_src(*connect_src_values) - end def index @clusters = cluster_list @@ -95,19 +86,6 @@ class Clusters::ClustersController < Clusters::BaseController redirect_to clusterable.index_path, status: :found end - def create_aws - @aws_cluster = ::Clusters::CreateService - .new(current_user, create_aws_cluster_params) - .execute - .present(current_user: current_user) - - if @aws_cluster.persisted? - head :created, location: @aws_cluster.show_path - else - render status: :unprocessable_entity, json: @aws_cluster.errors - end - end - def create_user @user_cluster = ::Clusters::CreateService .new(current_user, create_user_cluster_params) @@ -117,23 +95,10 @@ class Clusters::ClustersController < Clusters::BaseController if @user_cluster.persisted? redirect_to @user_cluster.show_path else - generate_gcp_authorize_url - validate_gcp_token - gcp_cluster - render :connect end end - def authorize_aws_role - response = Clusters::Aws::AuthorizeRoleService.new( - current_user, - params: aws_role_params - ).execute - - render json: response.body, status: response.status - end - def clear_cache cluster.delete_cached_resources! @@ -204,27 +169,6 @@ class Clusters::ClustersController < Clusters::BaseController end end - def create_aws_cluster_params - params.require(:cluster).permit( - *base_permitted_cluster_params, - :name, - provider_aws_attributes: [ - :kubernetes_version, - :key_name, - :role_arn, - :region, - :vpc_id, - :instance_type, - :num_nodes, - :security_group_id, - subnet_ids: [] - ]).merge( - provider_type: :aws, - platform_type: :kubernetes, - clusterable: clusterable.__subject__ - ) - end - def create_user_cluster_params params.require(:cluster).permit( *base_permitted_cluster_params, @@ -242,29 +186,6 @@ class Clusters::ClustersController < Clusters::BaseController ) end - def aws_role_params - params.require(:cluster).permit(:role_arn, :region) - end - - def generate_gcp_authorize_url - connect_path = clusterable.connect_path().to_s - error_path = @project ? project_clusters_path(@project) : connect_path - - state = generate_session_key_redirect(connect_path, error_path) - - @authorize_url = GoogleApi::CloudPlatform::Client.new( - nil, callback_google_api_auth_url, - state: state).authorize_url - rescue GoogleApi::Auth::ConfigMissingError - # no-op - end - - def gcp_cluster - cluster = Clusters::BuildService.new(clusterable.__subject__).execute - cluster.build_provider_gcp - @gcp_cluster = cluster.present(current_user: current_user) - end - def proxyable cluster.cluster end @@ -295,11 +216,6 @@ class Clusters::ClustersController < Clusters::BaseController @user_cluster = cluster.present(current_user: current_user) end - def validate_gcp_token - @valid_gcp_token = GoogleApi::CloudPlatform::Client.new(token_in_session, nil) - .validate_token(expires_at_in_session) - end - def token_in_session session[GoogleApi::CloudPlatform::Client.session_key_for_token] end @@ -309,26 +225,6 @@ class Clusters::ClustersController < Clusters::BaseController session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] end - def generate_session_key_redirect(uri, error_uri) - GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key| - session[key] = uri - session[:error_uri] = error_uri - end - end - - ## - # Unfortunately the EC2 API doesn't provide a list of - # possible instance types. There is a workaround, using - # the Pricing API, but instead of requiring the - # user to grant extra permissions for this we use the - # values that validate the CloudFormation template. - def load_instance_types - stack_template = File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml')) - instance_types = YAML.safe_load(stack_template).dig('Parameters', 'NodeInstanceType', 'AllowedValues') - - instance_types.map { |type| Hash(name: type, value: type) } - end - def update_applications_status @cluster.applications.each(&:schedule_status_update) end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 8e395a6f446..fd3bcc67b12 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -30,10 +30,6 @@ class GroupsController < Groups::ApplicationController before_action :user_actions, only: [:show] - before_action do - push_frontend_feature_flag(:vue_issues_list, @group) - end - before_action :check_export_rate_limit!, only: [:export, :download_export] before_action :track_experiment_event, only: [:new] @@ -212,7 +208,7 @@ class GroupsController < Groups::ApplicationController end def issues - return super if !html_request? || Feature.disabled?(:vue_issues_list, group) + return super unless html_request? @has_issues = IssuesFinder.new(current_user, group_id: group.id, include_subgroups: true).execute .non_archived diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 60b8e45f5be..f4125fd0a15 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -84,7 +84,7 @@ class Projects::CommitsController < Projects::ApplicationController @commits.each(&:lazy_author) # preload authors - @commits = @commits.with_latest_pipeline(@ref) + @commits = @commits.with_markdown_cache.with_latest_pipeline(@ref) @commits = set_commits_for_rendering(@commits) end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 3763fcf6c4d..acca85c0bf9 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -23,7 +23,7 @@ class Projects::IssuesController < Projects::ApplicationController after_action :log_issue_show, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) } before_action :set_issuables_index, if: ->(c) { - SET_ISSUABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) && !vue_issues_list? + SET_ISSUABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) && !index_html_request? } # Allow write(create) issue @@ -39,7 +39,6 @@ class Projects::IssuesController < Projects::ApplicationController before_action :authorize_download_code!, only: [:related_branches] before_action do - push_frontend_feature_flag(:vue_issues_list, project&.group) push_frontend_feature_flag(:contacts_autocomplete, project&.group) push_frontend_feature_flag(:incident_timeline, project) end @@ -82,7 +81,7 @@ class Projects::IssuesController < Projects::ApplicationController attr_accessor :vulnerability_id def index - if vue_issues_list? + if index_html_request? set_sort_order else @issues = @issuables @@ -258,10 +257,8 @@ class Projects::IssuesController < Projects::ApplicationController protected - def vue_issues_list? - action_name.to_sym == :index && - html_request? && - Feature.enabled?(:vue_issues_list, project&.group) + def index_html_request? + action_name.to_sym == :index && html_request? end def sorting_field diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index ee50327be8f..491074f39f9 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -27,14 +27,7 @@ module Projects ).to_json end - if current_user.ci_owned_runners_cross_joins_fix_enabled? - render - else - # @assignable_runners is using ci_owned_runners - ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do - render - end - end + render end def update diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb index cd784d54959..18b9bfd1c9a 100644 --- a/app/graphql/types/work_item_type.rb +++ b/app/graphql/types/work_item_type.rb @@ -18,6 +18,8 @@ module Types description: 'State of the work item.' field :title, GraphQL::Types::String, null: false, description: 'Title of the work item.' + field :widgets, [Types::WorkItems::WidgetInterface], null: true, + description: 'Collection of widgets that belong to the work item.' field :work_item_type, Types::WorkItems::TypeType, null: false, description: 'Type assigned to the work item.' diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb new file mode 100644 index 00000000000..67765c5b432 --- /dev/null +++ b/app/graphql/types/work_items/widget_interface.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module WidgetInterface + include Types::BaseInterface + + graphql_name 'WorkItemWidget' + + field :type, ::Types::WorkItems::WidgetTypeEnum, null: true, + description: 'Widget type.' + + def self.resolve_type(object, context) + case object + when ::WorkItems::Widgets::Description + ::Types::WorkItems::Widgets::DescriptionType + else + raise "Unknown GraphQL type for widget #{object}" + end + end + + orphan_types ::Types::WorkItems::Widgets::DescriptionType + end + end +end diff --git a/app/graphql/types/work_items/widget_type_enum.rb b/app/graphql/types/work_items/widget_type_enum.rb new file mode 100644 index 00000000000..4e5933bff86 --- /dev/null +++ b/app/graphql/types/work_items/widget_type_enum.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + module WorkItems + class WidgetTypeEnum < BaseEnum + graphql_name 'WorkItemWidgetType' + description 'Type of a work item widget' + + ::WorkItems::Type.available_widgets.each do |widget| + value widget.type.to_s.upcase, value: widget.type, description: "#{widget.type.to_s.titleize} widget." + end + end + end +end diff --git a/app/graphql/types/work_items/widgets/description_type.rb b/app/graphql/types/work_items/widgets/description_type.rb new file mode 100644 index 00000000000..79192d7c3d4 --- /dev/null +++ b/app/graphql/types/work_items/widgets/description_type.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module Widgets + # Disabling widget level authorization as it might be too granular + # and we already authorize the parent work item + # rubocop:disable Graphql/AuthorizeTypes + class DescriptionType < BaseObject + graphql_name 'WorkItemWidgetDescription' + description 'Represents a description widget' + + implements Types::WorkItems::WidgetInterface + + field :description, GraphQL::Types::String, null: true, + description: 'Description of the work item.' + + markdown_field :description_html, null: true do |resolved_object| + resolved_object.work_item + end + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 8aae4441852..982398e846b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1650,33 +1650,15 @@ class User < ApplicationRecord def ci_owned_runners @ci_owned_runners ||= begin - if ci_owned_runners_cross_joins_fix_enabled? - Ci::Runner - .from_union([ci_owned_project_runners_from_project_members, - ci_owned_project_runners_from_group_members, - ci_owned_group_runners]) - else - Ci::Runner - .from_union([ci_legacy_owned_project_runners, ci_legacy_owned_group_runners]) - .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') - end + Ci::Runner + .from_union([ci_owned_project_runners_from_project_members, + ci_owned_project_runners_from_group_members, + ci_owned_group_runners]) end end def owns_runner?(runner) - if ci_owned_runners_cross_joins_fix_enabled? - ci_owned_runners.exists?(runner.id) - else - ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do - ci_owned_runners.exists?(runner.id) - end - end - end - - def ci_owned_runners_cross_joins_fix_enabled? - strong_memoize(:ci_owned_runners_cross_joins_fix_enabled) do - Feature.enabled?(:ci_owned_runners_cross_joins_fix, self) - end + ci_owned_runners.exists?(runner.id) end def notification_email_for(notification_group) @@ -2258,20 +2240,6 @@ class User < ApplicationRecord ::Gitlab::Auth::Ldap::Access.allowed?(self) end - def ci_legacy_owned_project_runners - Ci::RunnerProject - .select('ci_runners.*') - .joins(:runner) - .where(project: authorized_projects(Gitlab::Access::MAINTAINER)) - end - - def ci_legacy_owned_group_runners - Ci::RunnerNamespace - .select('ci_runners.*') - .joins(:runner) - .where(namespace_id: owned_groups.self_and_descendant_ids) - end - def ci_owned_project_runners_from_project_members project_ids = project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id) diff --git a/app/models/work_item.rb b/app/models/work_item.rb index 84646daf59b..381b5650167 100644 --- a/app/models/work_item.rb +++ b/app/models/work_item.rb @@ -15,6 +15,12 @@ class WorkItem < Issue 'issue' end + def widgets + work_item_type.widgets.map do |widget_class| + widget_class.new(self) + end + end + private def record_create_action diff --git a/app/models/work_items/type.rb b/app/models/work_items/type.rb index 0d390fa131d..f708a30f128 100644 --- a/app/models/work_items/type.rb +++ b/app/models/work_items/type.rb @@ -20,6 +20,14 @@ module WorkItems task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 } }.freeze + WIDGETS_FOR_TYPE = { + issue: [Widgets::Description], + incident: [Widgets::Description], + test_case: [Widgets::Description], + requirement: [Widgets::Description], + task: [Widgets::Description] + }.freeze + cache_markdown_field :description, pipeline: :single_line enum base_type: BASE_TYPES.transform_values { |value| value[:enum_value] } @@ -40,6 +48,10 @@ module WorkItems scope :order_by_name_asc, -> { order(arel_table[:name].lower.asc) } scope :by_type, ->(base_type) { where(base_type: base_type) } + def self.available_widgets + WIDGETS_FOR_TYPE.values.flatten.uniq + end + def self.default_by_type(type) found_type = find_by(namespace_id: nil, base_type: type) return found_type if found_type @@ -60,6 +72,10 @@ module WorkItems namespace.blank? end + def widgets + WIDGETS_FOR_TYPE[base_type.to_sym] + end + private def strip_whitespace diff --git a/app/models/work_items/widgets/base.rb b/app/models/work_items/widgets/base.rb new file mode 100644 index 00000000000..9d1e48690e0 --- /dev/null +++ b/app/models/work_items/widgets/base.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module WorkItems + module Widgets + class Base + def self.type + name.demodulize.underscore.to_sym + end + + def type + self.class.type + end + + def initialize(work_item) + @work_item = work_item + end + + attr_reader :work_item + end + end +end diff --git a/app/models/work_items/widgets/description.rb b/app/models/work_items/widgets/description.rb new file mode 100644 index 00000000000..1e84d172bef --- /dev/null +++ b/app/models/work_items/widgets/description.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module WorkItems + module Widgets + class Description < Base + delegate :description, to: :work_item + end + end +end diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 209faa937dc..925e7d46f14 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,28 +1,8 @@ -- @can_bulk_update = can?(current_user, :admin_issue, @group) && @group.licensed_feature_available?(:group_bulk_edit) - -- page_title _("Issues") +- page_title _('Issues') - add_page_specific_style 'page_bundles/issues_list' = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues") -- if Feature.enabled?(:vue_issues_list, @group) - .js-issues-list{ data: group_issues_list_data(@group, current_user) } - - if @can_bulk_update - = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues -- else - .top-area - = render 'shared/issuable/nav', type: :issues - .nav-controls - = render 'shared/issuable/feed_buttons' - - - if @can_bulk_update - = render_if_exists 'shared/issuable/bulk_update_button', type: :issues - - = render 'shared/new_project_item_select', path: 'issues/new', label: _("issue"), type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true - - = render 'shared/issuable/search_bar', type: :issues - - - if @can_bulk_update - = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues - - = render 'shared/issues', project_select_button: true +.js-issues-list{ data: group_issues_list_data(@group, current_user) } +- if can?(current_user, :admin_issue, @group) && @group.licensed_feature_available?(:group_bulk_edit) + = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index fe2be0f73c9..b730eb5072e 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,10 +1,5 @@ -- @can_bulk_update = can?(current_user, :admin_issue, @project) - -- page_title _("Issues") -- new_issue_email = @project.new_issuable_address(current_user, 'issue') +- page_title _('Issues') - add_page_specific_style 'page_bundles/issues_list' -- issuable_type = 'issue' - = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues") @@ -13,24 +8,6 @@ issues_path: project_issues_path(@project), project_path: @project.full_path } } -- if Feature.enabled?(:vue_issues_list, @project&.group) - .js-issues-list{ data: project_issues_list_data(@project, current_user) } - - if @can_bulk_update - = render 'shared/issuable/bulk_update_sidebar', type: :issues -- elsif project_issues(@project).exists? - .top-area - = render 'shared/issuable/nav', type: :issues - = render "projects/issues/nav_btns" - = render 'shared/issuable/search_bar', type: :issues - - - if @can_bulk_update - = render 'shared/issuable/bulk_update_sidebar', type: :issues - - .issues-holder - = render 'issues' - - if new_issue_email - .gl-text-center.gl-pt-5.gl-pb-7 - .js-issuable-by-email{ data: { initial_email: new_issue_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } } -- else - - new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project) - = render 'shared/empty_states/issues', new_project_issue_button_path: new_project_issue_button_path, show_import_button: true +.js-issues-list{ data: project_issues_list_data(@project, current_user) } +- if can?(current_user, :admin_issue, @project) + = render 'shared/issuable/bulk_update_sidebar', type: :issues diff --git a/config/feature_flags/development/ci_owned_runners_cross_joins_fix.yml b/config/feature_flags/development/ci_owned_runners_cross_joins_fix.yml deleted file mode 100644 index 07ed7f5b252..00000000000 --- a/config/feature_flags/development/ci_owned_runners_cross_joins_fix.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_owned_runners_cross_joins_fix -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78216 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350322 -milestone: '14.8' -type: development -group: group::pipeline execution -default_enabled: true diff --git a/config/feature_flags/development/vue_issues_list.yml b/config/feature_flags/development/vue_issues_list.yml deleted file mode 100644 index b85c75105f7..00000000000 --- a/config/feature_flags/development/vue_issues_list.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: vue_issues_list -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55699 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323743 -milestone: '13.10' -type: development -group: group::project management -default_enabled: true diff --git a/config/routes.rb b/config/routes.rb index 30fdaca78aa..2eb7ece9a95 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -247,8 +247,6 @@ Rails.application.routes.draw do get :connect get :new_cluster_docs post :create_user - post :create_aws - post :authorize_aws_role end resource :integration, controller: 'clusters/integrations', only: [] do diff --git a/db/post_migrate/20220502015011_clean_up_fix_merge_request_diff_commit_users.rb b/db/post_migrate/20220502015011_clean_up_fix_merge_request_diff_commit_users.rb index 582a0f5cf37..a3e59b38975 100644 --- a/db/post_migrate/20220502015011_clean_up_fix_merge_request_diff_commit_users.rb +++ b/db/post_migrate/20220502015011_clean_up_fix_merge_request_diff_commit_users.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CleanUpFixMergeRequestDiffCommitUsers < Gitlab::Database::Migration[1.0] +class CleanUpFixMergeRequestDiffCommitUsers < Gitlab::Database::Migration[2.0] disable_ddl_transaction! MIGRATION_CLASS = 'FixMergeRequestDiffCommitUsers' diff --git a/doc/administration/geo/index.md b/doc/administration/geo/index.md index fc9df4c9039..685a0f6319d 100644 --- a/doc/administration/geo/index.md +++ b/doc/administration/geo/index.md @@ -53,8 +53,7 @@ Geo provides: ### Gitaly Cluster Geo should not be confused with [Gitaly Cluster](../gitaly/praefect.md). For more information about -the difference between Geo and Gitaly Cluster, see -[How does Gitaly Cluster compare to Geo?](../gitaly/faq.md#how-does-gitaly-cluster-compare-to-geo). +the difference between Geo and Gitaly Cluster, see [Comparison to Geo](../gitaly/index.md#comparison-to-geo). ## How it works diff --git a/doc/administration/gitaly/faq.md b/doc/administration/gitaly/faq.md index a5c2c7d1469..f6571295200 100644 --- a/doc/administration/gitaly/faq.md +++ b/doc/administration/gitaly/faq.md @@ -1,90 +1,6 @@ --- -stage: Create -group: Gitaly -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +redirect_to: 'index.md' +remove_date: '2022-08-24' --- -# Frequently asked questions **(FREE SELF)** - -The following are answers to frequently asked questions about Gitaly and Gitaly Cluster. For -troubleshooting information, see [Troubleshooting Gitaly and Gitaly Cluster](troubleshooting.md). - -## How does Gitaly Cluster compare to Geo? - -Gitaly Cluster and [Geo](../geo/index.md) both provide redundancy. However the redundancy of: - -- Gitaly Cluster provides fault tolerance for data storage and is invisible to the user. Users are - not aware when Gitaly Cluster is used. -- Geo provides [replication](../geo/index.md) and [disaster recovery](../geo/disaster_recovery/index.md) for - an entire instance of GitLab. Users know when they are using Geo for - [replication](../geo/index.md). Geo [replicates multiple data types](../geo/replication/datatypes.md#limitations-on-replicationverification), - including Git data. - -The following table outlines the major differences between Gitaly Cluster and Geo: - -| Tool | Nodes | Locations | Latency tolerance | Failover | Consistency | Provides redundancy for | -|:---------------|:---------|:----------|:------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------|:--------------------------------------|:------------------------| -| Gitaly Cluster | Multiple | Single | [Less than 1 second, ideally single-digit milliseconds](praefect.md#network-latency-and-connectivity) | [Automatic](praefect.md#automatic-failover-and-primary-election-strategies) | [Strong](index.md#strong-consistency) | Data storage in Git | -| Geo | Multiple | Multiple | Up to one minute | [Manual](../geo/disaster_recovery/index.md) | Eventual | Entire GitLab instance | - -For more information, see: - -- Geo [use cases](../geo/index.md#use-cases). -- Geo [architecture](../geo/index.md#architecture). - -## Are there instructions for migrating to Gitaly Cluster? - -Yes! For more information, see [Migrating to Gitaly Cluster](index.md#migrating-to-gitaly-cluster). - -## What are some repository storage recommendations? - -The size of the required storage can vary between instances and depends on the set -[replication factor](index.md#replication-factor). You might want to include implementing -repository storage redundancy. - -For a replication factor: - -- Of `1`: NFS, Gitaly, and Gitaly Cluster have roughly the same storage requirements. -- More than `1`: The amount of required storage is `used space * replication factor`. `used space` - should include any planned future growth. - -## What are some Praefect database storage requirements? - -The requirements are relatively low because the database contains only metadata of: - -- Where repositories are located. -- Some queued work. - -It depends on the number of repositories, but a useful minimum is 5-10 GB, similar to the main -GitLab application database. - -## Can the GitLab application database and the Praefect database be on the same servers? - -Yes, however Praefect should have it's own database server when using Omnibus GitLab PostgreSQL. If -there is a failover, Praefect isn't aware and starts to fail as the database it's trying to use would -either: - -- Be unavailable. -- In read-only mode. - -A future solution may allow for Praefect and Omnibus GitLab databases on the same PostgreSQL server. -For more information, see the relevant: - -- [Omnibus GitLab issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5919). -- [Gitaly issue](https://gitlab.com/gitlab-org/gitaly/-/issues/3398). - -## Is PgBouncer required for the Praefect database? - -No, because the number of connections Praefect makes is low. You can use the same PgBouncer instance -for both the GitLab application database and the Praefect database if you wish. - -## Are there any special considerations for Gitaly Cluster when PostgreSQL is upgraded? - -There are no special requirements. Gitaly Cluster requires PostgreSQL version 11 or later. - -## Praefect database tables are empty? - -These tables are created per the [specific configuration section](praefect.md#postgresql). - -If you find you have an empty Praefect database table, see the -[relevant troubleshooting section](troubleshooting.md#relation-does-not-exist-errors). +This document was moved to [another location](index.md). diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index ee6224dc5ea..401b37dcd5a 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -206,6 +206,29 @@ WARNING: If complete cluster failure occurs, disaster recovery plans should be executed. These can affect the RPO and RTO discussed above. +### Comparison to Geo + +Gitaly Cluster and [Geo](../geo/index.md) both provide redundancy. However the redundancy of: + +- Gitaly Cluster provides fault tolerance for data storage and is invisible to the user. Users are + not aware when Gitaly Cluster is used. +- Geo provides [replication](../geo/index.md) and [disaster recovery](../geo/disaster_recovery/index.md) for + an entire instance of GitLab. Users know when they are using Geo for + [replication](../geo/index.md). Geo [replicates multiple data types](../geo/replication/datatypes.md#limitations-on-replicationverification), + including Git data. + +The following table outlines the major differences between Gitaly Cluster and Geo: + +| Tool | Nodes | Locations | Latency tolerance | Failover | Consistency | Provides redundancy for | +|:---------------|:---------|:----------|:------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------|:--------------------------------------|:------------------------| +| Gitaly Cluster | Multiple | Single | [Less than 1 second, ideally single-digit milliseconds](praefect.md#network-latency-and-connectivity) | [Automatic](praefect.md#automatic-failover-and-primary-election-strategies) | [Strong](index.md#strong-consistency) | Data storage in Git | +| Geo | Multiple | Multiple | Up to one minute | [Manual](../geo/disaster_recovery/index.md) | Eventual | Entire GitLab instance | + +For more information, see: + +- Geo [use cases](../geo/index.md#use-cases). +- Geo [architecture](../geo/index.md#architecture). + ### Virtual storage Virtual storage makes it viable to have a single repository storage in GitLab to simplify repository @@ -501,7 +524,7 @@ See the [Before deploying Gitaly Cluster](#before-deploying-gitaly-cluster) sect for migrating to Gitaly Cluster involves: 1. Create the required storage. Refer to - [repository storage recommendations](faq.md#what-are-some-repository-storage-recommendations). + [repository storage recommendations](praefect.md#repository-storage-recommendations). 1. Create and configure [Gitaly Cluster](praefect.md). 1. [Move the repositories](../operations/moving_repositories.md#move-repositories). To migrate to Gitaly Cluster, existing repositories stored outside Gitaly Cluster must be moved. There is no diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index ccbaceca2f3..395b6c161e2 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -54,7 +54,7 @@ Achieving acceptable latency between Gitaly nodes: are designed for this type of synchronization. Latency of less than 2ms should be sufficient for Gitaly Cluster. If you can't provide low network latencies for replication (for example, between distant locations), consider Geo. For -more information, see [How does Gitaly Cluster compare to Geo](faq.md#how-does-gitaly-cluster-compare-to-geo). +more information, see [Comparison to Geo](index.md#comparison-to-geo). Gitaly Cluster [components](index.md#components) communicate with each other over many routes. Your firewall rules must allow the following for Gitaly Cluster to function properly: @@ -74,6 +74,16 @@ Gitaly does not directly connect to Praefect. However, requests from Gitaly to t load balancer may still be blocked unless firewalls on the Praefect nodes allow traffic from the Gitaly nodes. +### Praefect database storage + +The requirements are relatively low because the database contains only metadata of: + +- Where repositories are located. +- Some queued work. + +It depends on the number of repositories, but a useful minimum is 5-10 GB, similar to the main +GitLab application database. + ## Setup Instructions If you [installed](https://about.gitlab.com/install/) GitLab using the Omnibus GitLab package @@ -168,6 +178,18 @@ The following options are available: - Use a cloud-managed PostgreSQL service. AWS [Relational Database Service](https://aws.amazon.com/rds/) is recommended. +Setting up PostgreSQL creates empty Praefect tables. For more information, see the +[relevant troubleshooting section](troubleshooting.md#relation-does-not-exist-errors). + +#### Running GitLab and Praefect databases on the same server + +The GitLab application database and the Praefect database can be run on the same server. However, Praefect should have +its own database server when using Omnibus GitLab PostgreSQL. If there is a failover, Praefect isn't aware and starts to +fail as the database it's trying to use would either: + +- Be unavailable. +- In read-only mode. + #### Manual database setup To complete this section you need: @@ -278,8 +300,12 @@ reads distribution caching is enabled by configuration #### Use PgBouncer To reduce PostgreSQL resource consumption, we recommend setting up and configuring -[PgBouncer](https://www.pgbouncer.org/) in front of the PostgreSQL instance. To do -this, you must point Praefect to PgBouncer by setting database parameters on Praefect configuration: +[PgBouncer](https://www.pgbouncer.org/) in front of the PostgreSQL instance. However, PgBouncer isn't required because +Praefect makes a low number of connections. If you choose to use PgBouncer, you can use the same PgBouncer instance for +both the GitLab application database and the Praefect database. + +To configure PgBouncer in front of the PostgreSQL instance, you must point Praefect to PgBouncer by setting database +parameters on Praefect configuration: ```ruby praefect['database_host'] = PGBOUNCER_HOST @@ -1221,6 +1247,18 @@ You can configure: If `default_replication_factor` is unset, the repositories are always replicated on every node defined in `virtual_storages`. If a new node is introduced to the virtual storage, both new and existing repositories are replicated to the node automatically. +### Repository storage recommendations + +The size of the required storage can vary between instances and depends on the set +[replication factor](index.md#replication-factor). You might want to include implementing +repository storage redundancy. + +For a replication factor: + +- Of `1`: NFS, Gitaly, and Gitaly Cluster have roughly the same storage requirements. +- More than `1`: The amount of required storage is `used space * replication factor`. `used space` + should include any planned future growth. + ## Repository verification > [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/4080) in GitLab 15.0. diff --git a/doc/administration/gitaly/troubleshooting.md b/doc/administration/gitaly/troubleshooting.md index c79ed1d1707..da3882658e5 100644 --- a/doc/administration/gitaly/troubleshooting.md +++ b/doc/administration/gitaly/troubleshooting.md @@ -8,9 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w Refer to the information below when troubleshooting Gitaly and Gitaly Cluster. -Before troubleshooting, see the Gitaly and Gitaly Cluster -[frequently asked questions](faq.md). - ## Troubleshoot Gitaly The following sections provide possible solutions to Gitaly errors. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 6c260036931..0d957ed2d53 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -17868,6 +17868,7 @@ Represents vulnerability letter grades with associated projects. | `title` | [`String!`](#string) | Title of the work item. | | `titleHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `title`. | | `userPermissions` | [`WorkItemPermissions!`](#workitempermissions) | Permissions for the current user on the resource. | +| `widgets` | [`[WorkItemWidget!]`](#workitemwidget) | Collection of widgets that belong to the work item. | | `workItemType` | [`WorkItemType!`](#workitemtype) | Type assigned to the work item. | ### `WorkItemPermissions` @@ -17892,6 +17893,18 @@ Check permissions for the current user on a work item. | `id` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of the work item type. | | `name` | [`String!`](#string) | Name of the work item type. | +### `WorkItemWidgetDescription` + +Represents a description widget. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `description` | [`String`](#string) | Description of the work item. | +| `descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. | +| `type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | + ## Enumeration types Also called _Enums_, enumeration types are a special kind of scalar that @@ -19636,6 +19649,14 @@ Values for work item state events. | `CLOSE` | Closes the work item. | | `REOPEN` | Reopens the work item. | +### `WorkItemWidgetType` + +Type of a work item widget. + +| Value | Description | +| ----- | ----------- | +| `DESCRIPTION` | Description widget. | + ## Scalar types Scalar values are atomic values, and do not have fields of their own. @@ -20831,6 +20852,18 @@ four standard [pagination arguments](#connection-pagination-arguments): | `state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. | | `type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. | +#### `WorkItemWidget` + +Implementations: + +- [`WorkItemWidgetDescription`](#workitemwidgetdescription) + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | + ## Input types Types that may be used as arguments (all scalar types may also diff --git a/doc/development/database/migrations_for_multiple_databases.md b/doc/development/database/migrations_for_multiple_databases.md index 68410535d57..023b28ce6a6 100644 --- a/doc/development/database/migrations_for_multiple_databases.md +++ b/doc/development/database/migrations_for_multiple_databases.md @@ -13,11 +13,6 @@ for [the decomposed GitLab application using multiple databases](https://gitlab. Learn more about general multiple databases support in a [separate document](multiple_databases.md). -WARNING: -If you experience any issues using `Gitlab::Database::Migration[2.0]`, -you can temporarily revert back to the previous behavior by changing the version to `Gitlab::Database::Migration[1.0]`. -Please report any issues with `Gitlab::Database::Migration[2.0]` in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/358430). - The design for multiple databases (except for the Geo database) assumes that all decomposed databases have **the same structure** (for example, schema), but **the data is different** in each database. This means that some tables do not contain data on each database. diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 9cec9cb3316..41e33583a9a 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -42,6 +42,7 @@ in the search field in the upper right corner: > - Filtering by iterations was moved from GitLab Ultimate to GitLab Premium in 13.9. > - Filtering by type was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322755) in GitLab 13.10 [with a flag](../../administration/feature_flags.md) named `vue_issues_list`. Disabled by default. > - Filtering by type was [enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/322755) in GitLab 14.10. +> - Filtering by type is generally available in GitLab 15.1. [Feature flag `vue_issues_list`](https://gitlab.com/gitlab-org/gitlab/-/issues/359966) removed. > - Filtering by attention request was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/343528) in GitLab 14.10 [with a flag](../../administration/feature_flags.md) named `mr_attention_requests`. Disabled by default. Follow these steps to filter the **Issues** and **Merge requests** list pages in projects and diff --git a/lib/gitlab/graphql/markdown_field.rb b/lib/gitlab/graphql/markdown_field.rb index 6188d860aba..43dddf4c4bc 100644 --- a/lib/gitlab/graphql/markdown_field.rb +++ b/lib/gitlab/graphql/markdown_field.rb @@ -22,8 +22,10 @@ module Gitlab field name, GraphQL::Types::String, **kwargs define_method resolver_method do + markdown_object = block_given? ? yield(object) : object + # We need to `dup` the context so the MarkdownHelper doesn't modify it - ::MarkupHelper.markdown_field(object, method_name.to_sym, context.to_h.dup) + ::MarkupHelper.markdown_field(markdown_object, method_name.to_sym, context.to_h.dup) end end end diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb index 0d495fc661e..7d162c6e48f 100644 --- a/qa/qa/page/project/issue/index.rb +++ b/qa/qa/page/project/issue/index.rb @@ -79,12 +79,6 @@ module QA def has_no_issue?(issue) has_no_element? :issuable_container, issuable_title: issue.title end - - def wait_for_vue_issues_list_ff - Support::Retrier.retry_until(max_duration: 60, reload_page: page, retry_on_exception: true, sleep_interval: 5) do - find_element(:closed_issuables_tab) - end - end end end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index 84e1332cc8a..b8f1824126d 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -4,7 +4,6 @@ module QA RSpec.describe( 'Plan', :smoke, - feature_flag: { name: 'vue_issues_list', scope: :group }, quarantine: { issue: 'https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7099', type: :investigating, only: { subdomain: 'pre' } } ) do describe 'Issue creation' do @@ -12,8 +11,6 @@ module QA let(:closed_issue) { Resource::Issue.fabricate_via_api! { |issue| issue.project = project } } before do - Runtime::Feature.enable(:vue_issues_list, group: project.group) - Flow::Login.sign_in end @@ -26,9 +23,6 @@ module QA Page::Project::Menu.perform(&:click_issues) - # TODO: Remove this method when the `Runtime::Feature.enable` method call is removed - Page::Project::Issue::Index.perform(&:wait_for_vue_issues_list_ff) - Page::Project::Issue::Index.perform do |index| expect(index).to have_issue(issue) end @@ -49,9 +43,6 @@ module QA Page::Project::Menu.perform(&:click_issues) - # TODO: Remove this method when the `Runtime::Feature.enable` method call is removed - Page::Project::Issue::Index.perform(&:wait_for_vue_issues_list_ff) - Page::Project::Issue::Index.perform do |index| expect(index).not_to have_issue(closed_issue) diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index ca2b50b529c..c432adb6ae3 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -210,63 +210,6 @@ RSpec.describe Admin::ClustersController do end end - describe 'POST authorize AWS role for EKS cluster' do - let!(:role) { create(:aws_role, user: admin) } - - let(:role_arn) { 'arn:new-role' } - let(:params) do - { - cluster: { - role_arn: role_arn - } - } - end - - def go - post :authorize_aws_role, params: params - end - - include_examples ':certificate_based_clusters feature flag controller responses' do - let(:subject) { go } - end - - before do - allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .and_return(double(execute: double)) - end - - it 'updates the associated role with the supplied ARN' do - go - - expect(response).to have_gitlab_http_status(:ok) - expect(role.reload.role_arn).to eq(role_arn) - end - - context 'supplied role is invalid' do - let(:role_arn) { 'invalid-role' } - - it 'does not update the associated role' do - expect { go }.not_to change { role.role_arn } - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - end - end - - describe 'security' do - before do - allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service| - response = double(status: :ok, body: double) - - allow(service).to receive(:execute).and_return(response) - end - end - - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } - end - end - describe 'DELETE clear cluster cache' do let(:cluster) { create(:cluster, :instance) } let!(:kubernetes_namespace) do diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index 4b82c5ceb1c..eb3fe4bc330 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -262,142 +262,6 @@ RSpec.describe Groups::ClustersController do end end - describe 'POST #create_aws' do - let(:params) do - { - cluster: { - name: 'new-cluster', - provider_aws_attributes: { - key_name: 'key', - role_arn: 'arn:role', - region: 'region', - vpc_id: 'vpc', - instance_type: 'instance type', - num_nodes: 3, - security_group_id: 'security group', - subnet_ids: %w(subnet1 subnet2) - } - } - } - end - - def post_create_aws - post :create_aws, params: params.merge(group_id: group) - end - - include_examples ':certificate_based_clusters feature flag controller responses' do - let(:subject) { post_create_aws } - end - - it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { post_create_aws }.to change { Clusters::Cluster.count } - .and change { Clusters::Providers::Aws.count } - - cluster = group.clusters.first - - expect(response).to have_gitlab_http_status(:created) - expect(response.location).to eq(group_cluster_path(group, cluster)) - expect(cluster).to be_aws - expect(cluster).to be_kubernetes - end - - context 'params are invalid' do - let(:params) do - { - cluster: { name: '' } - } - end - - it 'does not create a cluster' do - expect { post_create_aws }.not_to change { Clusters::Cluster.count } - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(response.media_type).to eq('application/json') - expect(response.body).to include('is invalid') - end - end - - describe 'security' do - before do - allow(WaitForClusterCreationWorker).to receive(:perform_in) - end - - it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { expect { post_create_aws }.to be_allowed_for(:admin) } - it('is denied for admin when admin mode is disabled') { expect { post_create_aws }.to be_denied_for(:admin) } - it { expect { post_create_aws }.to be_allowed_for(:owner).of(group) } - it { expect { post_create_aws }.to be_allowed_for(:maintainer).of(group) } - it { expect { post_create_aws }.to be_denied_for(:developer).of(group) } - it { expect { post_create_aws }.to be_denied_for(:reporter).of(group) } - it { expect { post_create_aws }.to be_denied_for(:guest).of(group) } - it { expect { post_create_aws }.to be_denied_for(:user) } - it { expect { post_create_aws }.to be_denied_for(:external) } - end - end - - describe 'POST authorize AWS role for EKS cluster' do - let!(:role) { create(:aws_role, user: user) } - - let(:role_arn) { 'arn:new-role' } - let(:params) do - { - cluster: { - role_arn: role_arn - } - } - end - - def go - post :authorize_aws_role, params: params.merge(group_id: group) - end - - include_examples ':certificate_based_clusters feature flag controller responses' do - let(:subject) { go } - end - - before do - allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .and_return(double(execute: double)) - end - - it 'updates the associated role with the supplied ARN' do - go - - expect(response).to have_gitlab_http_status(:ok) - expect(role.reload.role_arn).to eq(role_arn) - end - - context 'supplied role is invalid' do - let(:role_arn) { 'invalid-role' } - - it 'does not update the associated role' do - expect { go }.not_to change { role.role_arn } - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - end - end - - describe 'security' do - before do - allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service| - response = double(status: :ok, body: double) - - allow(service).to receive(:execute).and_return(response) - end - end - - it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { expect { go }.to be_allowed_for(:admin) } - it('is denied for admin when admin mode is disabled') { expect { go }.to be_denied_for(:admin) } - it { expect { go }.to be_allowed_for(:owner).of(group) } - it { expect { go }.to be_allowed_for(:maintainer).of(group) } - it { expect { go }.to be_denied_for(:developer).of(group) } - it { expect { go }.to be_denied_for(:reporter).of(group) } - it { expect { go }.to be_denied_for(:guest).of(group) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } - end - end - describe 'DELETE clear cluster cache' do let(:cluster) { create(:cluster, :group, groups: [group]) } let!(:kubernetes_namespace) do diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 01420e30d24..d45ea268e64 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -272,150 +272,6 @@ RSpec.describe Projects::ClustersController do end end - describe 'POST #create_aws' do - let(:params) do - { - cluster: { - name: 'new-cluster', - provider_aws_attributes: { - key_name: 'key', - role_arn: 'arn:role', - region: 'region', - vpc_id: 'vpc', - instance_type: 'instance type', - num_nodes: 3, - security_group_id: 'security group', - subnet_ids: %w(subnet1 subnet2) - } - } - } - end - - def post_create_aws - post :create_aws, params: params.merge(namespace_id: project.namespace, project_id: project) - end - - include_examples ':certificate_based_clusters feature flag controller responses' do - let(:subject) { post_create_aws } - end - - it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { post_create_aws }.to change { Clusters::Cluster.count } - .and change { Clusters::Providers::Aws.count } - - cluster = project.clusters.first - - expect(response).to have_gitlab_http_status(:created) - expect(response.location).to eq(project_cluster_path(project, cluster)) - expect(cluster).to be_aws - expect(cluster).to be_kubernetes - end - - context 'params are invalid' do - let(:params) do - { - cluster: { name: '' } - } - end - - it 'does not create a cluster' do - expect { post_create_aws }.not_to change { Clusters::Cluster.count } - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(response.media_type).to eq('application/json') - expect(response.body).to include('is invalid') - end - end - - describe 'security' do - before do - allow(WaitForClusterCreationWorker).to receive(:perform_in) - end - - it 'is allowed for admin when admin mode enabled', :enable_admin_mode do - expect { post_create_aws }.to be_allowed_for(:admin) - end - it 'is disabled for admin when admin mode disabled' do - expect { post_create_aws }.to be_denied_for(:admin) - end - it { expect { post_create_aws }.to be_allowed_for(:owner).of(project) } - it { expect { post_create_aws }.to be_allowed_for(:maintainer).of(project) } - it { expect { post_create_aws }.to be_denied_for(:developer).of(project) } - it { expect { post_create_aws }.to be_denied_for(:reporter).of(project) } - it { expect { post_create_aws }.to be_denied_for(:guest).of(project) } - it { expect { post_create_aws }.to be_denied_for(:user) } - it { expect { post_create_aws }.to be_denied_for(:external) } - end - end - - describe 'POST authorize AWS role for EKS cluster' do - let!(:role) { create(:aws_role, user: user) } - - let(:role_arn) { 'arn:new-role' } - let(:params) do - { - cluster: { - role_arn: role_arn - } - } - end - - def go - post :authorize_aws_role, params: params.merge(namespace_id: project.namespace, project_id: project) - end - - before do - allow(Clusters::Aws::FetchCredentialsService).to receive(:new) - .and_return(double(execute: double)) - end - - include_examples ':certificate_based_clusters feature flag controller responses' do - let(:subject) { go } - end - - it 'updates the associated role with the supplied ARN' do - go - - expect(response).to have_gitlab_http_status(:ok) - expect(role.reload.role_arn).to eq(role_arn) - end - - context 'supplied role is invalid' do - let(:role_arn) { 'invalid-role' } - - it 'does not update the associated role' do - expect { go }.not_to change { role.role_arn } - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - end - end - - describe 'security' do - before do - allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service| - response = double(status: :ok, body: double) - - allow(service).to receive(:execute).and_return(response) - end - end - - it 'is allowed for admin when admin mode enabled', :enable_admin_mode do - expect { go }.to be_allowed_for(:admin) - end - it 'is disabled for admin when admin mode disabled' do - expect { go }.to be_denied_for(:admin) - end - it { expect { go }.to be_allowed_for(:owner).of(project) } - it { expect { go }.to be_allowed_for(:maintainer).of(project) } - it { expect { go }.to be_denied_for(:developer).of(project) } - it { expect { go }.to be_denied_for(:reporter).of(project) } - it { expect { go }.to be_denied_for(:guest).of(project) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } - end - end - describe 'DELETE clear cluster cache' do let(:cluster) { create(:cluster, :project, projects: [project]) } let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index c7f98406201..26d4725656f 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -166,6 +166,14 @@ RSpec.describe Projects::CommitsController do end end end + + context 'with markdown cache' do + it 'preloads markdown cache for commits' do + expect(Commit).to receive(:preload_markdown_cache!).and_call_original + + get :show, params: { namespace_id: project.namespace, project_id: project, id: 'master/README.md' } + end + end end describe "GET /commits/:id/signatures" do diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index 7e96e99640a..d50f1aa1dd8 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -25,19 +25,6 @@ RSpec.describe Projects::Settings::CiCdController do expect(response).to render_template(:show) end - context 'when the FF ci_owned_runners_cross_joins_fix is disabled' do - before do - stub_feature_flags(ci_owned_runners_cross_joins_fix: false) - end - - it 'renders show with 200 status code' do - get :show, params: { namespace_id: project.namespace, project_id: project } - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template(:show) - end - end - context 'with CI/CD disabled' do before do project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) diff --git a/spec/db/migration_spec.rb b/spec/db/migration_spec.rb index ac649925751..7987c78b423 100644 --- a/spec/db/migration_spec.rb +++ b/spec/db/migration_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Migrations Validation' do let(:all_migration_classes) do { 2022_01_26_21_06_58.. => Gitlab::Database::Migration[2.0], - 2021_09_01_15_33_24.. => Gitlab::Database::Migration[1.0], + 2021_09_01_15_33_24..2022_04_25_12_06_03 => Gitlab::Database::Migration[1.0], 2021_05_31_05_39_16..2021_09_01_15_33_24 => ActiveRecord::Migration[6.1], ..2021_05_31_05_39_16 => ActiveRecord::Migration[6.0] } diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb index 09e042b00cc..06e3e00db7d 100644 --- a/spec/features/clusters/cluster_detail_page_spec.rb +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -36,20 +36,6 @@ RSpec.describe 'Clusterable > Show page' do expect(page).not_to have_selector('[data-testid="cluster-environments-tab"]') end - - context 'content-security policy' do - it 'has AWS domains in the CSP' do - visit cluster_path - - expect(response_headers['Content-Security-Policy']).to include(::Clusters::ClustersController::AWS_CSP_DOMAINS.join(' ')) - end - - it 'keeps existing connect-src in the CSP' do - visit cluster_path - - expect(response_headers['Content-Security-Policy']).to include("connect-src #{Gitlab::ContentSecurityPolicy::Directives.connect_src}") - end - end end shared_examples 'editing a GCP cluster' do diff --git a/spec/graphql/types/work_item_type_spec.rb b/spec/graphql/types/work_item_type_spec.rb index a0480506156..7ed58786b5b 100644 --- a/spec/graphql/types/work_item_type_spec.rb +++ b/spec/graphql/types/work_item_type_spec.rb @@ -10,7 +10,18 @@ RSpec.describe GitlabSchema.types['WorkItem'] do specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::WorkItem) } it 'has specific fields' do - fields = %i[description description_html id iid lock_version state title title_html userPermissions work_item_type] + fields = %i[ + description + description_html + id + iid + lock_version + state title + title_html + userPermissions + widgets + work_item_type + ] fields.each do |field_name| expect(described_class).to have_graphql_fields(*fields) diff --git a/spec/graphql/types/work_items/widget_interface_spec.rb b/spec/graphql/types/work_items/widget_interface_spec.rb new file mode 100644 index 00000000000..63d7782df28 --- /dev/null +++ b/spec/graphql/types/work_items/widget_interface_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::WorkItems::WidgetInterface do + include GraphqlHelpers + + it 'exposes the expected fields' do + expected_fields = %i[type] + + expect(described_class).to have_graphql_fields(*expected_fields) + end + + describe ".resolve_type" do + it 'knows the correct type for objects' do + expect( + described_class.resolve_type(WorkItems::Widgets::Description.new(build(:work_item)), {}) + ).to eq(Types::WorkItems::Widgets::DescriptionType) + end + + it 'raises an error for an unknown type' do + project = build(:project) + + expect_graphql_error_to_be_created("Unknown GraphQL type for widget #{project}") do + described_class.resolve_type(project, {}) + end + end + end +end diff --git a/spec/graphql/types/work_items/widget_type_enum_spec.rb b/spec/graphql/types/work_items/widget_type_enum_spec.rb new file mode 100644 index 00000000000..e7ac9b9c317 --- /dev/null +++ b/spec/graphql/types/work_items/widget_type_enum_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['WorkItemWidgetType'] do + specify { expect(described_class.graphql_name).to eq('WorkItemWidgetType') } + + it 'exposes all the existing widget type values' do + expect(described_class.values.transform_values { |v| v.value }).to include( + 'DESCRIPTION' => :description + ) + end +end diff --git a/spec/graphql/types/work_items/widgets/description_type_spec.rb b/spec/graphql/types/work_items/widgets/description_type_spec.rb new file mode 100644 index 00000000000..5ade1fe4aa2 --- /dev/null +++ b/spec/graphql/types/work_items/widgets/description_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::WorkItems::Widgets::DescriptionType do + it 'exposes the expected fields' do + expected_fields = %i[description description_html type] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb index 84494d3dd68..a73eba1e9db 100644 --- a/spec/lib/gitlab/graphql/markdown_field_spec.rb +++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb @@ -55,6 +55,20 @@ RSpec.describe Gitlab::Graphql::MarkdownField do end end + context 'when a block is passed for the resolved object' do + let(:type_class) do + class_with_markdown_field(:note_html, null: false) do |resolved_object| + resolved_object.object + end + end + + let(:type_instance) { type_class.authorized_new(class_wrapped_object(note), context) } + + it 'renders markdown from the same property as the field name without the `_html` suffix' do + expect(field.resolve(type_instance, {}, context)).to eq(expected_markdown) + end + end + describe 'basic verification that references work' do let_it_be(:project) { create(:project, :public) } @@ -83,12 +97,22 @@ RSpec.describe Gitlab::Graphql::MarkdownField do end end - def class_with_markdown_field(name, **args) + def class_with_markdown_field(name, **args, &blk) Class.new(Types::BaseObject) do prepend Gitlab::Graphql::MarkdownField graphql_name 'MarkdownFieldTest' - markdown_field name, **args + markdown_field name, **args, &blk end end + + def class_wrapped_object(object) + Class.new do + def initialize(object) + @object = object + end + + attr_accessor :object + end.new(object) + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 71171f98492..67d7e29627b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4363,16 +4363,6 @@ RSpec.describe User do it_behaves_like '#ci_owned_runners' end - context 'when FF ci_owned_runners_cross_joins_fix is disabled' do - before do - skip_if_multiple_databases_are_setup - - stub_feature_flags(ci_owned_runners_cross_joins_fix: false) - end - - it_behaves_like '#ci_owned_runners' - end - context 'when FF ci_owned_runners_unnest_index is disabled uses GIN index' do before do stub_feature_flags(ci_owned_runners_unnest_index: false) diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb index d126e81f1cd..6b2e33ec08d 100644 --- a/spec/models/work_item_spec.rb +++ b/spec/models/work_item_spec.rb @@ -33,6 +33,12 @@ RSpec.describe WorkItem do end end + describe '#widgets' do + subject { build(:work_item).widgets } + + it { is_expected.to contain_exactly(instance_of(WorkItems::Widgets::Description)) } + end + describe 'callbacks' do describe 'record_create_action' do it 'records the creation action after saving' do diff --git a/spec/models/work_items/type_spec.rb b/spec/models/work_items/type_spec.rb index 6e9f3210e65..6936b8f558e 100644 --- a/spec/models/work_items/type_spec.rb +++ b/spec/models/work_items/type_spec.rb @@ -60,7 +60,13 @@ RSpec.describe WorkItems::Type do it { is_expected.not_to allow_value('s' * 256).for(:icon_name) } end - describe 'default?' do + describe '.available_widgets' do + subject { described_class.available_widgets } + + it { is_expected.to contain_exactly(::WorkItems::Widgets::Description) } + end + + describe '#default?' do subject { build(:work_item_type, namespace: namespace).default? } context 'when namespace is nil' do diff --git a/spec/models/work_items/widgets/base_spec.rb b/spec/models/work_items/widgets/base_spec.rb new file mode 100644 index 00000000000..9b4b4d9e98f --- /dev/null +++ b/spec/models/work_items/widgets/base_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::Widgets::Base do + let_it_be(:work_item) { create(:work_item, description: '# Title') } + + describe '.type' do + subject { described_class.type } + + it { is_expected.to eq(:base) } + end + + describe '#type' do + subject { described_class.new(work_item).type } + + it { is_expected.to eq(:base) } + end +end diff --git a/spec/models/work_items/widgets/description_spec.rb b/spec/models/work_items/widgets/description_spec.rb new file mode 100644 index 00000000000..8359db31bff --- /dev/null +++ b/spec/models/work_items/widgets/description_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::Widgets::Description do + let_it_be(:work_item) { create(:work_item, description: '# Title') } + + describe '.type' do + subject { described_class.type } + + it { is_expected.to eq(:description) } + end + + describe '#type' do + subject { described_class.new(work_item).type } + + it { is_expected.to eq(:description) } + end + + describe '#description' do + subject { described_class.new(work_item).description } + + it { is_expected.to eq(work_item.description) } + end +end diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index 5b34c21989a..1c93ae453c0 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Query.work_item(id)' do let_it_be(:developer) { create(:user) } let_it_be(:project) { create(:project, :private).tap { |project| project.add_developer(developer) } } - let_it_be(:work_item) { create(:work_item, project: project) } + let_it_be(:work_item) { create(:work_item, project: project, description: '- List item') } let(:current_user) { developer } let(:work_item_data) { graphql_data['workItem'] } @@ -38,6 +38,34 @@ RSpec.describe 'Query.work_item(id)' do ) end + context 'when querying widgets' do + let(:work_item_fields) do + <<~GRAPHQL + id + widgets { + type + ... on WorkItemWidgetDescription { + description + descriptionHtml + } + } + GRAPHQL + end + + it 'returns widget information' do + expect(work_item_data).to include( + 'id' => work_item.to_gid.to_s, + 'widgets' => contain_exactly( + hash_including( + 'type' => 'DESCRIPTION', + 'description' => work_item.description, + 'descriptionHtml' => ::MarkupHelper.markdown_field(work_item, :description, {}) + ) + ) + ) + end + end + context 'when an Issue Global ID is provided' do let(:global_id) { Issue.find(work_item.id).to_gid.to_s }