Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-06 00:14:55 +00:00
parent b7d0ee2a31
commit d39c778244
51 changed files with 577 additions and 297 deletions

View File

@ -3663,7 +3663,6 @@ RSpec/MissingFeatureCategory:
- 'spec/lib/gitlab/database/migrations/test_background_runner_spec.rb'
- 'spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb'
- 'spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb'
- 'spec/lib/gitlab/database/partitioning/convert_table_to_first_list_partition_spec.rb'
- 'spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb'
- 'spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb'
- 'spec/lib/gitlab/database/partitioning/partition_manager_spec.rb'
@ -3674,7 +3673,6 @@ RSpec/MissingFeatureCategory:
- 'spec/lib/gitlab/database/partitioning/time_partition_spec.rb'
- 'spec/lib/gitlab/database/partitioning_migration_helpers/backfill_partitioned_table_spec.rb'
- 'spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb'
- 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb'
- 'spec/lib/gitlab/database/partitioning_spec.rb'
- 'spec/lib/gitlab/database/pg_class_spec.rb'
- 'spec/lib/gitlab/database/postgres_constraint_spec.rb'

View File

@ -42,13 +42,14 @@ export default {
false,
),
todoList: __('To-Do list'),
stopImpersonating: __('Stop impersonating'),
},
directives: {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
SafeHtml,
},
inject: ['rootPath'],
inject: ['rootPath', 'isImpersonating'],
props: {
sidebarData: {
type: Object,
@ -114,6 +115,19 @@ export default {
<search-modal />
<user-menu :data="sidebarData" />
<gl-button
v-if="isImpersonating"
v-gl-tooltip
:href="sidebarData.stop_impersonation_path"
:title="$options.i18n.stopImpersonating"
:aria-label="$options.i18n.stopImpersonating"
icon="incognito"
variant="confirm"
category="tertiary"
data-method="delete"
data-testid="stop-impersonation-btn"
/>
</div>
<div class="gl-display-flex gl-justify-content-space-between gl-px-3 gl-py-2 gl-gap-2">
<counter

View File

@ -1,7 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import { initStatusTriggers } from '../header';
import createStore from './components/global_search/store';
import {
@ -29,6 +29,7 @@ export const initSuperSidebar = () => {
const searchData = convertObjectPropsToCamelCase(sidebarData.search);
const { searchPath, issuesPath, mrPath, autocompletePath, searchContext } = searchData;
const isImpersonating = parseBoolean(sidebarData.is_impersonating);
return new Vue({
el,
@ -37,6 +38,7 @@ export const initSuperSidebar = () => {
provide: {
rootPath,
toggleNewNavEndpoint,
isImpersonating,
},
store: createStore({
searchPath,

View File

@ -13,6 +13,11 @@ export default {
GlCollapsibleListbox,
},
props: {
block: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: true,
@ -176,6 +181,7 @@ export default {
<gl-collapsible-listbox
ref="listbox"
v-model="selected"
:block="block"
:header-text="headerText"
:reset-button-label="resetButtonLabel"
:toggle-text="toggleText"

View File

@ -20,6 +20,8 @@ export const initProjectSelects = () => {
orderBy,
selected: initialSelection,
} = el.dataset;
const block = parseBoolean(el.dataset.block);
const withShared = parseBoolean(el.dataset.withShared);
const includeSubgroups = parseBoolean(el.dataset.includeSubgroups);
const membership = parseBoolean(el.dataset.membership);
const hasHtmlLabel = parseBoolean(el.dataset.hasHtmlLabel);
@ -37,6 +39,8 @@ export const initProjectSelects = () => {
groupId,
userId,
orderBy,
block,
withShared,
includeSubgroups,
membership,
initialSelection,

View File

@ -20,6 +20,11 @@ export default {
SafeHtml,
},
props: {
block: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: true,
@ -47,6 +52,11 @@ export default {
required: false,
default: null,
},
withShared: {
type: Boolean,
required: false,
default: true,
},
includeSubgroups: {
type: Boolean,
required: false,
@ -86,7 +96,7 @@ export default {
if (this.groupId) {
return Api.groupProjects(this.groupId, searchString, {
...commonParams,
with_shared: true,
with_shared: this.withShared,
include_subgroups: this.includeSubgroups,
simple: true,
});
@ -99,7 +109,7 @@ export default {
this.userId,
searchString,
{
with_shared: true,
with_shared: this.withShared,
include_subgroups: this.includeSubgroups,
},
(res) => ({ data: res }),
@ -154,6 +164,7 @@ export default {
:default-toggle-text="$options.i18n.searchForProject"
:fetch-items="fetchProjects"
:fetch-initial-selection-text="fetchProjectName"
:block="block"
clearable
>
<template v-if="hasHtmlLabel" #label>

View File

@ -87,7 +87,9 @@ module SidebarsHelper
search: search_data,
pinned_items: user.pinned_nav_items[panel_type] || [],
panel_type: panel_type,
update_pins_url: pins_url
update_pins_url: pins_url,
is_impersonating: impersonating?,
stop_impersonation_path: admin_impersonation_path
}
end
@ -323,6 +325,10 @@ module SidebarsHelper
count.to_s
end
end
def impersonating?
!!session[:impersonator_id]
end
end
SidebarsHelper.prepend_mod_with('SidebarsHelper')

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module WorkItems
class ResourceLinkEvent < ResourceEvent
belongs_to :child_work_item, class_name: 'WorkItem'
validates :child_work_item, presence: true
enum action: {
add: 1,
remove: 2
}
end
end

View File

@ -24,6 +24,7 @@
order_by: 'last_activity_at',
group_id: group_id,
user_id: user_id,
with_shared: true.to_s,
include_subgroups: true.to_s,
membership: true.to_s,
selected: @cluster.management_project_id } }

View File

@ -0,0 +1,10 @@
---
table_name: resource_link_events
classes:
- WorkItems::ResourceLinkEvent
feature_categories:
- planning_analytics
description: Records the change of parent link on work items along with timestamps
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114394
milestone: '15.11'
gitlab_schema: gitlab_main

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class CreateResourceLinkEvents < Gitlab::Database::Migration[2.1]
def change
create_table :resource_link_events do |t|
t.integer :action, limit: 2, null: false
t.bigint :user_id, null: false
t.references :issue, index: true, null: false, foreign_key: { on_delete: :cascade }
t.references :child_work_item, index: true, null: false, foreign_key: { to_table: :issues, on_delete: :cascade }
t.datetime_with_timezone :created_at, null: false
t.index :user_id
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddForeignKeyToResourceLinkEventsOnUser < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :resource_link_events, :users, column: :user_id, on_delete: :nullify, validate: true
end
def down
with_lock_retries do
remove_foreign_key_if_exists :resource_link_events, column: :user_id
end
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddForkStorageSizeColumnsToRootStorageStatistics < Gitlab::Database::Migration[2.1]
enable_lock_retries!
def change
add_column :namespace_root_storage_statistics, :public_forks_storage_size, :bigint, default: 0, null: false
add_column :namespace_root_storage_statistics, :internal_forks_storage_size, :bigint, default: 0, null: false
add_column :namespace_root_storage_statistics, :private_forks_storage_size, :bigint, default: 0, null: false
end
end

View File

@ -0,0 +1 @@
44dc97ac36a6edcd0c0dba76f6b60204b72c005da7bd793af4ac7832d949bd0b

View File

@ -0,0 +1 @@
52c5c662dc46313dece9ed9228af5ea2734f0fc4872ba0f6a762e77437b9564e

View File

@ -0,0 +1 @@
ff04f9ef9bb479b85223e361b96c921e25b436a86a0041627b595c3635848a5b

View File

@ -18623,7 +18623,10 @@ CREATE TABLE namespace_root_storage_statistics (
dependency_proxy_size bigint DEFAULT 0 NOT NULL,
notification_level smallint DEFAULT 100 NOT NULL,
container_registry_size bigint DEFAULT 0 NOT NULL,
registry_size_estimated boolean DEFAULT false NOT NULL
registry_size_estimated boolean DEFAULT false NOT NULL,
public_forks_storage_size bigint DEFAULT 0 NOT NULL,
internal_forks_storage_size bigint DEFAULT 0 NOT NULL,
private_forks_storage_size bigint DEFAULT 0 NOT NULL
);
CREATE TABLE namespace_settings (
@ -21740,6 +21743,24 @@ CREATE SEQUENCE resource_label_events_id_seq
ALTER SEQUENCE resource_label_events_id_seq OWNED BY resource_label_events.id;
CREATE TABLE resource_link_events (
id bigint NOT NULL,
action smallint NOT NULL,
user_id bigint NOT NULL,
issue_id bigint NOT NULL,
child_work_item_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE resource_link_events_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE resource_link_events_id_seq OWNED BY resource_link_events.id;
CREATE TABLE resource_milestone_events (
id bigint NOT NULL,
user_id bigint,
@ -25341,6 +25362,8 @@ ALTER TABLE ONLY resource_iteration_events ALTER COLUMN id SET DEFAULT nextval('
ALTER TABLE ONLY resource_label_events ALTER COLUMN id SET DEFAULT nextval('resource_label_events_id_seq'::regclass);
ALTER TABLE ONLY resource_link_events ALTER COLUMN id SET DEFAULT nextval('resource_link_events_id_seq'::regclass);
ALTER TABLE ONLY resource_milestone_events ALTER COLUMN id SET DEFAULT nextval('resource_milestone_events_id_seq'::regclass);
ALTER TABLE ONLY resource_state_events ALTER COLUMN id SET DEFAULT nextval('resource_state_events_id_seq'::regclass);
@ -27703,6 +27726,9 @@ ALTER TABLE ONLY resource_iteration_events
ALTER TABLE ONLY resource_label_events
ADD CONSTRAINT resource_label_events_pkey PRIMARY KEY (id);
ALTER TABLE ONLY resource_link_events
ADD CONSTRAINT resource_link_events_pkey PRIMARY KEY (id);
ALTER TABLE ONLY resource_milestone_events
ADD CONSTRAINT resource_milestone_events_pkey PRIMARY KEY (id);
@ -31928,6 +31954,12 @@ CREATE INDEX index_resource_label_events_on_merge_request_id_label_id_action ON
CREATE INDEX index_resource_label_events_on_user_id ON resource_label_events USING btree (user_id);
CREATE INDEX index_resource_link_events_on_child_work_item_id ON resource_link_events USING btree (child_work_item_id);
CREATE INDEX index_resource_link_events_on_issue_id ON resource_link_events USING btree (issue_id);
CREATE INDEX index_resource_link_events_on_user_id ON resource_link_events USING btree (user_id);
CREATE INDEX index_resource_milestone_events_created_at ON resource_milestone_events USING btree (created_at);
CREATE INDEX index_resource_milestone_events_on_issue_id ON resource_milestone_events USING btree (issue_id);
@ -34950,6 +34982,9 @@ ALTER TABLE ONLY namespace_bans
ALTER TABLE ONLY gitlab_subscriptions
ADD CONSTRAINT fk_bd0c4019c3 FOREIGN KEY (hosted_plan_id) REFERENCES plans(id) ON DELETE CASCADE;
ALTER TABLE ONLY resource_link_events
ADD CONSTRAINT fk_bd4ae15ce4 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY metrics_users_starred_dashboards
ADD CONSTRAINT fk_bd6ae32fac FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
@ -35310,6 +35345,9 @@ ALTER TABLE ONLY audit_events_external_audit_event_destinations
ALTER TABLE ONLY operations_user_lists
ADD CONSTRAINT fk_rails_0c716e079b FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY resource_link_events
ADD CONSTRAINT fk_rails_0cea73eba5 FOREIGN KEY (child_work_item_id) REFERENCES issues(id) ON DELETE CASCADE;
ALTER TABLE ONLY geo_node_statuses
ADD CONSTRAINT fk_rails_0ecc699c2a FOREIGN KEY (geo_node_id) REFERENCES geo_nodes(id) ON DELETE CASCADE;
@ -36696,6 +36734,9 @@ ALTER TABLE ONLY merge_request_reviewers
ALTER TABLE ONLY ci_running_builds
ADD CONSTRAINT fk_rails_da45cfa165_p FOREIGN KEY (partition_id, build_id) REFERENCES ci_builds(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY resource_link_events
ADD CONSTRAINT fk_rails_da5dd8a56f FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
ALTER TABLE ONLY jira_imports
ADD CONSTRAINT fk_rails_da617096ce FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;

View File

@ -8,6 +8,8 @@ module Gitlab
SQL_STATEMENT_SEPARATOR = ";\n\n"
PARTITIONING_CONSTRAINT_NAME = 'partitioning_constraint'
attr_reader :partitioning_column, :table_name, :parent_table_name, :zero_partition_value
def initialize(
@ -23,10 +25,10 @@ module Gitlab
@lock_tables = Array.wrap(lock_tables)
end
def prepare_for_partitioning
def prepare_for_partitioning(async: false)
assert_existing_constraints_partitionable
add_partitioning_check_constraint
add_partitioning_check_constraint(async: async)
end
def revert_preparation_for_partitioning
@ -121,16 +123,17 @@ module Gitlab
constraints_on_column = Gitlab::Database::PostgresConstraint
.by_table_identifier(table_identifier)
.check_constraints
.valid
.including_column(partitioning_column)
constraints_on_column.to_a.find do |constraint|
constraint.definition == "CHECK ((#{partitioning_column} = #{zero_partition_value}))"
check_body = "CHECK ((#{partitioning_column} = #{zero_partition_value}))"
constraints_on_column.find do |constraint|
constraint.definition.start_with?(check_body)
end
end
def assert_partitioning_constraint_present
return if partitioning_constraint
return if partitioning_constraint&.constraint_valid?
raise UnableToPartition, <<~MSG
Table #{table_name} is not ready for partitioning.
@ -138,14 +141,43 @@ module Gitlab
MSG
end
def add_partitioning_check_constraint
return if partitioning_constraint.present?
def add_partitioning_check_constraint(async: false)
return validate_partitioning_constraint_synchronously if partitioning_constraint.present?
check_body = "#{partitioning_column} = #{connection.quote(zero_partition_value)}"
# Any constraint name would work. The constraint is found based on its definition before partitioning
migration_context.add_check_constraint(table_name, check_body, 'partitioning_constraint')
migration_context.add_check_constraint(
table_name, check_body, PARTITIONING_CONSTRAINT_NAME,
validate: !async
)
raise UnableToPartition, 'Error adding partitioning constraint' unless partitioning_constraint.present?
if async
migration_context.prepare_async_check_constraint_validation(
table_name, name: PARTITIONING_CONSTRAINT_NAME
)
end
return if partitioning_constraint.present?
raise UnableToPartition, <<~MSG
Error adding partitioning constraint `#{PARTITIONING_CONSTRAINT_NAME}` for `#{table_name}`
MSG
end
def validate_partitioning_constraint_synchronously
if partitioning_constraint.constraint_valid?
return Gitlab::AppLogger.info <<~MSG
Nothing to do, the partitioning constraint exists and is valid for `#{table_name}`
MSG
end
# Async validations are executed only on .com, we need to validate synchronously for self-managed
migration_context.validate_check_constraint(table_name, partitioning_constraint.name)
return if partitioning_constraint.constraint_valid?
raise UnableToPartition, <<~MSG
Error validating partitioning constraint `#{partitioning_constraint.name}` for `#{table_name}`
MSG
end
def create_parent_table

View File

@ -252,7 +252,7 @@ module Gitlab
create_sync_trigger(source_table_name, trigger_name, function_name)
end
def prepare_constraint_for_list_partitioning(table_name:, partitioning_column:, parent_table_name:, initial_partitioning_value:)
def prepare_constraint_for_list_partitioning(table_name:, partitioning_column:, parent_table_name:, initial_partitioning_value:, async: false)
validate_not_in_transaction!(:prepare_constraint_for_list_partitioning)
Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
@ -261,7 +261,7 @@ module Gitlab
parent_table_name: parent_table_name,
partitioning_column: partitioning_column,
zero_partition_value: initial_partitioning_value
).prepare_for_partitioning
).prepare_for_partitioning(async: async)
end
def revert_preparing_constraint_for_list_partitioning(table_name:, partitioning_column:, parent_table_name:, initial_partitioning_value:)

View File

@ -42294,6 +42294,9 @@ msgstr ""
msgid "Stop Terminal"
msgstr ""
msgid "Stop impersonating"
msgstr ""
msgid "Stop impersonation"
msgstr ""

View File

@ -60,6 +60,7 @@
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230323132525",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@popperjs/core": "^2.11.2",
"@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7",
"@sourcegraph/code-host-integration": "0.0.84",

View File

@ -5,7 +5,13 @@ module QA
module Group
class Menu < Page::Base
include SubMenus::Common
include SubMenus::SuperSidebar::Settings if Runtime::Env.super_sidebar_enabled?
if Runtime::Env.super_sidebar_enabled?
prepend Page::SubMenus::SuperSidebar::Manage
prepend Page::SubMenus::SuperSidebar::Plan
prepend SubMenus::SuperSidebar::Settings
prepend SubMenus::SuperSidebar::Build
end
def click_group_members_item
hover_group_information do
@ -16,6 +22,8 @@ module QA
end
def click_subgroup_members_item
return go_to_members if Runtime::Env.super_sidebar_enabled?
hover_subgroup_information do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Members')

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module QA
module Page
module Group
module SubMenus
module SuperSidebar
module Build
extend QA::Page::PageConcern
def go_to_runners
open_build_submenu("Runners")
end
private
def open_build_submenu(sub_menu)
open_submenu("Build", "#build", sub_menu)
end
end
end
end
end
end
end

View File

@ -1,24 +0,0 @@
# frozen_string_literal: true
module QA
module Page
module Group
module SubMenus
module SuperSidebar
module Common
private
def open_submenu(parent_menu_name, parent_section_id, sub_menu)
click_element(:sidebar_menu_link, menu_item: parent_menu_name)
# TODO: it's not possible to add qa-selectors to sub-menu containers at the moment
within(parent_section_id) do
click_element(:sidebar_menu_link, menu_item: sub_menu)
end
end
end
end
end
end
end
end

View File

@ -8,14 +8,6 @@ module QA
module Settings
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include Common
end
end
def go_to_general_settings
open_settings_submenu("General")
end

View File

@ -16,16 +16,14 @@ module QA
include SubMenus::Packages
if Runtime::Env.super_sidebar_enabled?
include Page::SubMenus::SuperSidebar::Manage
include SubMenus::SuperSidebar::Project
include SubMenus::SuperSidebar::Manage
include SubMenus::SuperSidebar::Plan
include SubMenus::SuperSidebar::Settings
include SubMenus::SuperSidebar::Code
include SubMenus::SuperSidebar::Build
include SubMenus::SuperSidebar::Secure
include SubMenus::SuperSidebar::Operate
include SubMenus::SuperSidebar::Monitor
include SubMenus::SuperSidebar::Analyze
end
def click_merge_requests

View File

@ -27,7 +27,7 @@ module QA
end
end
def go_to_boards
def go_to_issue_boards
hover_issues do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Boards')

View File

@ -1,45 +0,0 @@
# frozen_string_literal: true
module QA
module Page
module Project
module SubMenus
module SuperSidebar
module Analyze
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
end
end
def go_to_value_stream_analytics
open_analyze_submenu('Value stream analytics')
end
def go_to_contributor_statistics
open_analyze_submenu('Contributor statistics')
end
def go_to_ci_cd_analytics
open_analyze_submenu('CI/CD analytics')
end
def go_to_repository_analytics
open_analyze_submenu('Repository analytics')
end
private
def open_analyze_submenu(sub_menu)
open_submenu('Analyze', '#analyze', sub_menu)
end
end
end
end
end
end
end

View File

@ -8,14 +8,6 @@ module QA
module Build
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
end
end
def go_to_pipelines
open_build_submenu('Pipelines')
end

View File

@ -8,14 +8,6 @@ module QA
module Code
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
end
end
def go_to_repository
open_code_submenu('Repository')
end

View File

@ -1,24 +0,0 @@
# frozen_string_literal: true
module QA
module Page
module Project
module SubMenus
module SuperSidebar
module Common
private
def open_submenu(parent_menu_name, parent_section_id, sub_menu)
click_element(:sidebar_menu_link, menu_item: parent_menu_name)
# TODO: it's not possible to add qa-selectors to sub-menu container
within(parent_section_id) do
click_element(:sidebar_menu_link, menu_item: sub_menu)
end
end
end
end
end
end
end
end

View File

@ -1,45 +0,0 @@
# frozen_string_literal: true
module QA
module Page
module Project
module SubMenus
module SuperSidebar
module Manage
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
end
end
def go_to_activity
open_manage_submenu('Activity')
end
def go_to_members
open_manage_submenu('Members')
end
def go_to_labels
open_manage_submenu('Labels')
end
def go_to_milestones
open_manage_submenu('Milestones')
end
private
def open_manage_submenu(sub_menu)
open_submenu('Manage', '#manage', sub_menu)
end
end
end
end
end
end
end

View File

@ -8,14 +8,6 @@ module QA
module Monitor
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
end
end
def go_to_metrics
open_monitor_submenu('Metrics')
end

View File

@ -8,14 +8,6 @@ module QA
module Operate
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
end
end
def go_to_package_registry
open_operate_submenu('Package Registry')
end

View File

@ -6,32 +6,16 @@ module QA
module SubMenus
module SuperSidebar
module Plan
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
include QA::Page::SubMenus::SuperSidebar::Plan
end
end
def go_to_boards
open_plan_submenu("Issue boards")
end
def go_to_service_desk
open_plan_submenu("Service Desk")
end
def go_to_wiki
open_plan_submenu("Wiki")
end
private
def open_plan_submenu(sub_menu)
open_submenu("Plan", "#plan", sub_menu)
def go_to_requirements
open_plan_submenu("Requirements")
end
end
end

View File

@ -8,14 +8,6 @@ module QA
module Project
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::Common
end
end
def click_project
within_sidebar do
click_element(:sidebar_menu_link, menu_item: 'Project overview')

View File

@ -8,18 +8,6 @@ module QA
module Secure
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
end
end
def go_to_audit_events
open_secure_submenu('Audit events')
end
def go_to_security_configuration
open_secure_submenu('Security configuration')
end

View File

@ -8,14 +8,6 @@ module QA
module Settings
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::SuperSidebar::Common
end
end
def go_to_general_settings
open_settings_submenu('General')
end

View File

@ -15,38 +15,41 @@ module QA
end
end
def within_sidebar
def within_sidebar(&block)
wait_for_requests
within_element(sidebar_element) do
yield
end
within_element(sidebar_element, &block)
end
def within_submenu(element = nil)
def within_submenu(element = nil, &block)
if element
within_element(element) do
yield
end
within_element(element, &block)
else
within_submenu_without_element do
yield
end
within_submenu_without_element(&block)
end
end
private
def within_submenu_without_element
if has_css?('.fly-out-list')
within('.fly-out-list') do
yield
end
else
yield
# Implementation for super-sidebar, will replace within_submenu
#
# @param [String] parent_menu_name
# @param [String] parent_section_id
# @param [String] sub_menu
# @return [void]
def open_submenu(parent_menu_name, parent_section_id, sub_menu)
click_element(:sidebar_menu_link, menu_item: parent_menu_name)
# TODO: it's not possible to add qa-selectors to sub-menu container
within(parent_section_id) do
click_element(:sidebar_menu_link, menu_item: sub_menu)
end
end
def within_submenu_without_element(&block)
has_css?('.fly-out-list') ? within('.fly-out-list', &block) : yield
end
def sidebar_element
raise NotImplementedError
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module QA
module Page
module SubMenus
module SuperSidebar
module Manage
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::SubMenus::Common
end
end
def go_to_activity
open_manage_submenu('Activity')
end
def go_to_members
open_manage_submenu('Members')
end
def go_to_labels
open_manage_submenu('Labels')
end
def go_to_milestones
open_manage_submenu('Milestones')
end
private
def open_manage_submenu(sub_menu)
open_submenu('Manage', '#manage', sub_menu)
end
end
end
end
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
module QA
module Page
module SubMenus
module SuperSidebar
module Plan
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::SubMenus::Common
end
end
def go_to_issue_boards
open_plan_submenu("Issue boards")
end
def go_to_service_desk
open_plan_submenu("Service Desk")
end
def go_to_wiki
open_plan_submenu("Wiki")
end
private
def open_plan_submenu(sub_menu)
open_submenu("Plan", "#plan", sub_menu)
end
end
end
end
end
end

View File

@ -16,7 +16,7 @@ module QA
it 'focuses on issue board', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347996' do
project.visit!
Page::Project::Menu.perform(&:go_to_boards)
Page::Project::Menu.perform(&:go_to_issue_boards)
Page::Component::IssueBoard::Show.perform do |show|
show.click_focus_mode_button

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
FactoryBot.define do
factory :resource_link_event, class: 'WorkItems::ResourceLinkEvent' do
action { :add }
issue { association(:issue) }
user { issue&.author || association(:user) }
child_work_item { association(:work_item, :task) }
end
end

View File

@ -25,6 +25,7 @@ describe('UserBar component', () => {
const findBrandLogo = () => wrapper.findByTestId('brand-header-custom-logo');
const findSearchButton = () => wrapper.findByTestId('super-sidebar-search-button');
const findSearchModal = () => wrapper.findComponent(SearchModal);
const findStopImpersonationButton = () => wrapper.findByTestId('stop-impersonation-btn');
Vue.use(Vuex);
@ -33,7 +34,7 @@ describe('UserBar component', () => {
searchOptions: () => MOCK_DEFAULT_SEARCH_OPTIONS,
},
});
const createWrapper = (extraSidebarData = {}) => {
const createWrapper = ({ extraSidebarData = {}, provideOverrides = {} } = {}) => {
wrapper = shallowMountExtended(UserBar, {
propsData: {
sidebarData: { ...sidebarData, ...extraSidebarData },
@ -41,6 +42,8 @@ describe('UserBar component', () => {
provide: {
rootPath: '/',
toggleNewNavEndpoint: '/-/profile/preferences',
isImpersonating: false,
...provideOverrides,
},
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
@ -95,12 +98,16 @@ describe('UserBar component', () => {
expect(findBrandLogo().exists()).toBe(true);
expect(findBrandLogo().attributes('src')).toBe(sidebarData.logo_url);
});
it('does not render the "Stop impersonating" button', () => {
expect(findStopImpersonationButton().exists()).toBe(false);
});
});
describe('GitLab Next badge', () => {
describe('when on canary', () => {
it('should render a badge to switch off GitLab Next', () => {
createWrapper({ gitlab_com_and_canary: true });
createWrapper({ extraSidebarData: { gitlab_com_and_canary: true } });
const badge = wrapper.findComponent(GlBadge);
expect(badge.text()).toBe('Next');
expect(badge.attributes('href')).toBe(sidebarData.canary_toggle_com_url);
@ -109,7 +116,7 @@ describe('UserBar component', () => {
describe('when not on canary', () => {
it('should not render the GitLab Next badge', () => {
createWrapper({ gitlab_com_and_canary: false });
createWrapper({ extraSidebarData: { gitlab_com_and_canary: false } });
const badge = wrapper.findComponent(GlBadge);
expect(badge.exists()).toBe(false);
});
@ -135,4 +142,29 @@ describe('UserBar component', () => {
expect(findSearchModal().exists()).toBe(true);
});
});
describe('While impersonating a user', () => {
beforeEach(() => {
createWrapper({ provideOverrides: { isImpersonating: true } });
});
it('renders the "Stop impersonating" button', () => {
expect(findStopImpersonationButton().exists()).toBe(true);
});
it('sets the correct label on the button', () => {
const btn = findStopImpersonationButton();
const label = __('Stop impersonating');
expect(btn.attributes('title')).toBe(label);
expect(btn.attributes('aria-label')).toBe(label);
});
it('sets the href and data-method attributes', () => {
const btn = findStopImpersonationButton();
expect(btn.attributes('href')).toBe(sidebarData.stop_impersonation_path);
expect(btn.attributes('data-method')).toBe('delete');
});
});
});

View File

@ -103,6 +103,7 @@ export const sidebarData = {
pinned_items: [],
panel_type: 'your_work',
update_pins_url: 'path/to/pins',
stop_impersonation_path: '/admin/impersonation',
};
export const userMenuMockStatus = {

View File

@ -97,6 +97,7 @@ describe('ProjectSelect', () => {
${'defaultToggleText'} | ${PROJECT_TOGGLE_TEXT}
${'headerText'} | ${PROJECT_HEADER_TEXT}
${'clearable'} | ${true}
${'block'} | ${false}
`('passes the $prop prop to entity-select', ({ prop, expectedValue }) => {
expect(findEntitySelect().props(prop)).toBe(expectedValue);
});
@ -136,6 +137,18 @@ describe('ProjectSelect', () => {
expect(mock.history.get[0].params.include_subgroups).toBe(true);
});
it('does not include shared projects if withShared prop is false', async () => {
createComponent({
props: {
withShared: false,
},
});
openListbox();
await waitForPromises();
expect(mock.history.get[0].params.with_shared).toBe(false);
});
it('fetches projects globally if no group ID is provided', async () => {
createComponent({
props: {

View File

@ -331,6 +331,23 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
end
end
end
describe 'impersonation data' do
it 'sets is_impersonating to `false` when not impersonating' do
expect(subject[:is_impersonating]).to be(false)
end
it 'passes the stop_impersonation_path property' do
expect(subject[:stop_impersonation_path]).to eq(admin_impersonation_path)
end
describe 'when impersonating' do
it 'sets is_impersonating to `true`' do
expect(helper).to receive(:session).and_return({ impersonator_id: 1 })
expect(subject[:is_impersonating]).to be(true)
end
end
end
end
describe '#super_sidebar_nav_panel' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Partitioning::ConvertTableToFirstListPartition do
RSpec.describe Gitlab::Database::Partitioning::ConvertTableToFirstListPartition, feature_category: :database do
include Gitlab::Database::DynamicModelHelpers
include Database::TableSchemaHelpers
@ -77,7 +77,9 @@ RSpec.describe Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
end
describe "#prepare_for_partitioning" do
subject(:prepare) { converter.prepare_for_partitioning }
subject(:prepare) { converter.prepare_for_partitioning(async: async) }
let(:async) { false }
it 'adds a check constraint' do
expect { prepare }.to change {
@ -87,9 +89,100 @@ RSpec.describe Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
.count
}.from(0).to(1)
end
context 'when it fails to add constraint' do
before do
allow(migration_context).to receive(:add_check_constraint)
end
it 'raises UnableToPartition error' do
expect { prepare }
.to raise_error(described_class::UnableToPartition)
.and change {
Gitlab::Database::PostgresConstraint
.check_constraints
.by_table_identifier(table_identifier)
.count
}.by(0)
end
end
context 'when async' do
let(:async) { true }
it 'adds a NOT VALID check constraint' do
expect { prepare }.to change {
Gitlab::Database::PostgresConstraint
.check_constraints
.by_table_identifier(table_identifier)
.count
}.from(0).to(1)
constraint =
Gitlab::Database::PostgresConstraint
.check_constraints
.by_table_identifier(table_identifier)
.last
expect(constraint.definition).to end_with('NOT VALID')
end
it 'adds a PostgresAsyncConstraintValidation record' do
expect { prepare }.to change {
Gitlab::Database::AsyncConstraints::PostgresAsyncConstraintValidation.count
}.from(0).to(1)
record = Gitlab::Database::AsyncConstraints::PostgresAsyncConstraintValidation.last
expect(record.name).to eq described_class::PARTITIONING_CONSTRAINT_NAME
expect(record).to be_check_constraint
end
context 'when constraint exists but is not valid' do
before do
converter.prepare_for_partitioning(async: true)
end
it 'validates the check constraint' do
expect { prepare }.to change {
Gitlab::Database::PostgresConstraint
.check_constraints
.by_table_identifier(table_identifier).first.constraint_valid?
}.from(false).to(true)
end
context 'when it fails to validate constraint' do
before do
allow(migration_context).to receive(:validate_check_constraint)
end
it 'raises UnableToPartition error' do
expect { prepare }
.to raise_error(described_class::UnableToPartition,
starting_with('Error validating partitioning constraint'))
.and change {
Gitlab::Database::PostgresConstraint
.check_constraints
.by_table_identifier(table_identifier)
.count
}.by(0)
end
end
end
context 'when constraint exists and is valid' do
before do
converter.prepare_for_partitioning(async: false)
end
it 'raises UnableToPartition error' do
expect(Gitlab::AppLogger).to receive(:info).with(starting_with('Nothing to do'))
prepare
end
end
end
end
describe '#revert_prepare_for_partitioning' do
describe '#revert_preparation_for_partitioning' do
before do
converter.prepare_for_partitioning
end
@ -109,8 +202,10 @@ RSpec.describe Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
describe "#partition" do
subject(:partition) { converter.partition }
let(:async) { false }
before do
converter.prepare_for_partitioning
converter.prepare_for_partitioning(async: async)
end
context 'when the primary key is incorrect' do
@ -134,7 +229,15 @@ RSpec.describe Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
end
it 'throws a reasonable error message' do
expect { partition }.to raise_error(described_class::UnableToPartition, /constraint /)
expect { partition }.to raise_error(described_class::UnableToPartition, /is not ready for partitioning./)
end
end
context 'when supporting check constraint is not valid' do
let(:async) { true }
it 'throws a reasonable error message' do
expect { partition }.to raise_error(described_class::UnableToPartition, /is not ready for partitioning./)
end
end
@ -207,7 +310,7 @@ RSpec.describe Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
proc do
allow(migration_context.connection).to receive(:add_foreign_key).and_call_original
expect(migration_context.connection).to receive(:add_foreign_key).with(from_table, to_table, any_args)
.and_wrap_original(&fail_first_time)
.and_wrap_original(&fail_first_time)
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do
RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers, feature_category: :database do
include Database::PartitioningHelpers
include Database::TriggerHelpers
include Database::TableSchemaHelpers
@ -98,7 +98,8 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
migration.prepare_constraint_for_list_partitioning(table_name: source_table,
partitioning_column: partition_column,
parent_table_name: partitioned_table,
initial_partitioning_value: min_date)
initial_partitioning_value: min_date,
async: false)
end
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::ResourceLinkEvent, type: :model, feature_category: :team_planning do
it_behaves_like 'a resource event'
describe 'associations' do
it { is_expected.to belong_to(:work_item) }
it { is_expected.to belong_to(:child_work_item) }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:child_work_item) }
end
end

View File

@ -10,6 +10,8 @@ RSpec.shared_examples 'a resource event' do
let_it_be(:issue2) { create(:issue, author: user1) }
let_it_be(:issue3) { create(:issue, author: user2) }
let(:resource_event) { described_class.name.demodulize.underscore.to_sym }
describe 'importable' do
it { is_expected.to respond_to(:importing?) }
it { is_expected.to respond_to(:imported?) }
@ -36,9 +38,9 @@ RSpec.shared_examples 'a resource event' do
let!(:created_at2) { 2.days.ago }
let!(:created_at3) { 3.days.ago }
let!(:event1) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: created_at1) }
let!(:event2) { create(described_class.name.underscore.to_sym, issue: issue2, created_at: created_at2) }
let!(:event3) { create(described_class.name.underscore.to_sym, issue: issue2, created_at: created_at3) }
let!(:event1) { create(resource_event, issue: issue1, created_at: created_at1) }
let!(:event2) { create(resource_event, issue: issue2, created_at: created_at2) }
let!(:event3) { create(resource_event, issue: issue2, created_at: created_at3) }
it 'returns the expected events' do
events = described_class.created_after(created_at3)
@ -62,9 +64,10 @@ RSpec.shared_examples 'a resource event for issues' do
let_it_be(:issue2) { create(:issue, author: user1) }
let_it_be(:issue3) { create(:issue, author: user2) }
let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1) }
let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2) }
let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1) }
let_it_be(:resource_event) { described_class.name.demodulize.underscore.to_sym }
let_it_be(:event1) { create(resource_event, issue: issue1) }
let_it_be(:event2) { create(resource_event, issue: issue2) }
let_it_be(:event3) { create(resource_event, issue: issue1) }
describe 'associations' do
it { is_expected.to belong_to(:issue) }
@ -93,9 +96,9 @@ RSpec.shared_examples 'a resource event for issues' do
end
describe '.by_created_at_earlier_or_equal_to' do
let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: '2020-03-10') }
let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2, created_at: '2020-03-10') }
let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: '2020-03-12') }
let_it_be(:event1) { create(resource_event, issue: issue1, created_at: '2020-03-10') }
let_it_be(:event2) { create(resource_event, issue: issue2, created_at: '2020-03-10') }
let_it_be(:event3) { create(resource_event, issue: issue1, created_at: '2020-03-12') }
it 'returns the expected events' do
events = described_class.by_created_at_earlier_or_equal_to('2020-03-11 23:59:59')
@ -112,7 +115,7 @@ RSpec.shared_examples 'a resource event for issues' do
if described_class.method_defined?(:issuable)
describe '#issuable' do
let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue2) }
let_it_be(:event1) { create(resource_event, issue: issue2) }
it 'returns the expected issuable' do
expect(event1.issuable).to eq(issue2)
@ -125,6 +128,7 @@ RSpec.shared_examples 'a resource event for merge requests' do
let_it_be(:user1) { create(:user) }
let_it_be(:user2) { create(:user) }
let_it_be(:resource_event) { described_class.name.demodulize.underscore.to_sym }
let_it_be(:merge_request1) { create(:merge_request, author: user1) }
let_it_be(:merge_request2) { create(:merge_request, author: user1) }
let_it_be(:merge_request3) { create(:merge_request, author: user2) }
@ -134,9 +138,9 @@ RSpec.shared_examples 'a resource event for merge requests' do
end
describe '.by_merge_request' do
let_it_be(:event1) { create(described_class.name.underscore.to_sym, merge_request: merge_request1) }
let_it_be(:event2) { create(described_class.name.underscore.to_sym, merge_request: merge_request2) }
let_it_be(:event3) { create(described_class.name.underscore.to_sym, merge_request: merge_request1) }
let_it_be(:event1) { create(resource_event, merge_request: merge_request1) }
let_it_be(:event2) { create(resource_event, merge_request: merge_request2) }
let_it_be(:event3) { create(resource_event, merge_request: merge_request1) }
it 'returns the expected records for an issue with events' do
events = described_class.by_merge_request(merge_request1)
@ -153,7 +157,7 @@ RSpec.shared_examples 'a resource event for merge requests' do
if described_class.method_defined?(:issuable)
describe '#issuable' do
let_it_be(:event1) { create(described_class.name.underscore.to_sym, merge_request: merge_request2) }
let_it_be(:event1) { create(resource_event, merge_request: merge_request2) }
it 'returns the expected issuable' do
expect(event1.issuable).to eq(merge_request2)
@ -163,7 +167,7 @@ RSpec.shared_examples 'a resource event for merge requests' do
context 'on callbacks' do
it 'does not trigger note created subscription' do
event = build(described_class.name.underscore.to_sym, merge_request: merge_request1)
event = build(resource_event, merge_request: merge_request1)
expect(GraphqlTriggers).not_to receive(:work_item_note_created)
expect(event).not_to receive(:trigger_note_subscription_create)
@ -177,15 +181,17 @@ RSpec.shared_examples 'a note for work item resource event' do
let_it_be(:project) { create(:project) }
let_it_be(:work_item) { create(:work_item, :task, project: project, author: user) }
let(:resource_event) { described_class.name.demodulize.underscore.to_sym }
it 'builds synthetic note with correct synthetic_note_class' do
event = build(described_class.name.underscore.to_sym, issue: work_item)
event = build(resource_event, issue: work_item)
expect(event.work_item_synthetic_system_note.class.name).to eq(event.synthetic_note_class.name)
end
context 'on callbacks' do
it 'triggers note created subscription' do
event = build(described_class.name.underscore.to_sym, issue: work_item)
event = build(resource_event, issue: work_item)
expect(GraphqlTriggers).to receive(:work_item_note_created)
expect(event).to receive(:trigger_note_subscription_create).and_call_original