Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b7d0ee2a31
commit
d39c778244
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 } }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
44dc97ac36a6edcd0c0dba76f6b60204b72c005da7bd793af4ac7832d949bd0b
|
||||
|
|
@ -0,0 +1 @@
|
|||
52c5c662dc46313dece9ed9228af5ea2734f0fc4872ba0f6a762e77437b9564e
|
||||
|
|
@ -0,0 +1 @@
|
|||
ff04f9ef9bb479b85223e361b96c921e25b436a86a0041627b595c3635848a5b
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:)
|
||||
|
|
|
|||
|
|
@ -42294,6 +42294,9 @@ msgstr ""
|
|||
msgid "Stop Terminal"
|
||||
msgstr ""
|
||||
|
||||
msgid "Stop impersonating"
|
||||
msgstr ""
|
||||
|
||||
msgid "Stop impersonation"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue