Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5316a9bca9
commit
35b0f1fe13
|
@ -260,6 +260,8 @@
|
|||
- ".gitlab/ci/**/*"
|
||||
- "*_VERSION"
|
||||
- "scripts/rspec_helpers.sh"
|
||||
# Mapped patterns (see tests.yml)
|
||||
- "data/whats_new/*.yml"
|
||||
|
||||
# DB patterns + .ci-patterns
|
||||
.db-patterns: &db-patterns
|
||||
|
@ -314,10 +316,11 @@
|
|||
- "config.ru"
|
||||
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
|
||||
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
|
||||
- "data/whats_new/*.yml"
|
||||
# CI changes
|
||||
- ".gitlab-ci.yml"
|
||||
- ".gitlab/ci/**/*"
|
||||
# Mapped patterns (see tests.yml)
|
||||
- "data/whats_new/*.yml"
|
||||
|
||||
# .code-patterns + .backstage-patterns
|
||||
.code-backstage-patterns: &code-backstage-patterns
|
||||
|
@ -338,7 +341,6 @@
|
|||
- "config.ru"
|
||||
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
|
||||
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
|
||||
- "data/whats_new/*.yml"
|
||||
# CI changes
|
||||
- ".gitlab-ci.yml"
|
||||
- ".gitlab/ci/**/*"
|
||||
|
@ -349,6 +351,8 @@
|
|||
- "{,ee/,jh/}rubocop/**/*"
|
||||
- "{,ee/,jh/}spec/**/*"
|
||||
- "{,spec/}tooling/**/*"
|
||||
# Mapped patterns (see tests.yml)
|
||||
- "data/whats_new/*.yml"
|
||||
|
||||
# .code-patterns + .qa-patterns
|
||||
.code-qa-patterns: &code-qa-patterns
|
||||
|
@ -369,13 +373,14 @@
|
|||
- "config.ru"
|
||||
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
|
||||
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
|
||||
- "data/whats_new/*.yml"
|
||||
# CI changes
|
||||
- ".gitlab-ci.yml"
|
||||
- ".gitlab/ci/**/*"
|
||||
# QA changes
|
||||
- ".dockerignore"
|
||||
- "qa/**/*"
|
||||
# Mapped patterns (see tests.yml)
|
||||
- "data/whats_new/*.yml"
|
||||
|
||||
# .code-patterns + .backstage-patterns + .qa-patterns
|
||||
.code-backstage-qa-patterns: &code-backstage-qa-patterns
|
||||
|
@ -396,7 +401,6 @@
|
|||
- "config.ru"
|
||||
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
|
||||
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
|
||||
- "data/whats_new/*.yml"
|
||||
# CI changes
|
||||
- ".gitlab-ci.yml"
|
||||
- ".gitlab/ci/**/*"
|
||||
|
@ -410,6 +414,8 @@
|
|||
# QA changes
|
||||
- ".dockerignore"
|
||||
- "qa/**/*"
|
||||
# Mapped patterns (see tests.yml)
|
||||
- "data/whats_new/*.yml"
|
||||
|
||||
# .code-backstage-qa-patterns + .workhorse-patterns
|
||||
.setup-test-env-patterns: &setup-test-env-patterns
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
<script>
|
||||
import {
|
||||
GlSafeHtmlDirective as SafeHtml,
|
||||
GlModal,
|
||||
GlModalDirective,
|
||||
GlPopover,
|
||||
GlButton,
|
||||
} from '@gitlab/ui';
|
||||
import { GlSafeHtmlDirective as SafeHtml, GlModal, GlTooltip, GlModalDirective } from '@gitlab/ui';
|
||||
import $ from 'jquery';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
|
||||
import createFlash from '~/flash';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import TaskList from '~/task_list';
|
||||
import Tracking from '~/tracking';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
@ -25,9 +19,8 @@ export default {
|
|||
},
|
||||
components: {
|
||||
GlModal,
|
||||
GlPopover,
|
||||
CreateWorkItem,
|
||||
GlButton,
|
||||
GlTooltip,
|
||||
WorkItemDetailModal,
|
||||
},
|
||||
mixins: [animateMixin, glFeatureFlagMixin(), Tracking.mixin()],
|
||||
|
@ -216,9 +209,11 @@ export default {
|
|||
this.taskButtons.push(button.id);
|
||||
button.innerHTML = `
|
||||
<svg data-testid="ellipsis_v-icon" role="img" aria-hidden="true" class="dropdown-icon gl-icon s14">
|
||||
<use href="${gon.sprite_icons}#ellipsis_v"></use>
|
||||
<use href="${gon.sprite_icons}#doc-new"></use>
|
||||
</svg>
|
||||
`;
|
||||
button.setAttribute('aria-label', s__('WorkItem|Convert to work item'));
|
||||
button.addEventListener('click', () => this.openCreateTaskModal(button.id));
|
||||
item.prepend(button);
|
||||
});
|
||||
},
|
||||
|
@ -246,9 +241,6 @@ export default {
|
|||
this.$emit('updateDescription', description);
|
||||
this.closeCreateTaskModal();
|
||||
},
|
||||
focusButton() {
|
||||
this.$refs.convertButton[0].$el.focus();
|
||||
},
|
||||
},
|
||||
safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] },
|
||||
};
|
||||
|
@ -309,23 +301,9 @@ export default {
|
|||
@error="handleWorkItemDetailModalError"
|
||||
/>
|
||||
<template v-if="workItemsEnabled">
|
||||
<gl-popover
|
||||
v-for="item in taskButtons"
|
||||
:key="item"
|
||||
:target="item"
|
||||
placement="top"
|
||||
triggers="focus"
|
||||
@shown="focusButton"
|
||||
>
|
||||
<gl-button
|
||||
ref="convertButton"
|
||||
variant="link"
|
||||
data-testid="convert-to-task"
|
||||
class="gl-text-gray-900! gl-text-decoration-none! gl-outline-0!"
|
||||
@click="openCreateTaskModal(item)"
|
||||
>{{ s__('WorkItem|Convert to work item') }}</gl-button
|
||||
>
|
||||
</gl-popover>
|
||||
<gl-tooltip v-for="item in taskButtons" :key="item" :target="item">
|
||||
{{ s__('WorkItem|Convert to work item') }}
|
||||
</gl-tooltip>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -87,10 +87,7 @@ export default {
|
|||
return this.selectedWorkItemType?.name || s__('WorkItem|Type');
|
||||
},
|
||||
formOptions() {
|
||||
return [
|
||||
{ value: null, text: s__('WorkItem|Please select work item type') },
|
||||
...this.workItemTypes,
|
||||
];
|
||||
return [{ value: null, text: s__('WorkItem|Select type') }, ...this.workItemTypes];
|
||||
},
|
||||
isButtonDisabled() {
|
||||
return this.title.trim().length === 0 || !this.selectedWorkItemType;
|
||||
|
|
|
@ -311,7 +311,7 @@ ul.related-merge-requests > li gl-emoji {
|
|||
.description.work-items-enabled {
|
||||
ul.task-list {
|
||||
> li.task-list-item {
|
||||
padding-inline-start: 2.25rem;
|
||||
padding-inline-start: 2.5rem;
|
||||
|
||||
.js-add-task {
|
||||
svg {
|
||||
|
@ -324,7 +324,7 @@ ul.related-merge-requests > li gl-emoji {
|
|||
}
|
||||
|
||||
> input.task-list-item-checkbox {
|
||||
left: 0.875rem;
|
||||
left: 1.25rem;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
|
|
|
@ -196,6 +196,27 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Devise defines current_user to be:
|
||||
#
|
||||
# def current_user
|
||||
# @current_user ||= warden.authenticate(scope: mapping)
|
||||
# end
|
||||
#
|
||||
# That means whenever current_user is called and `@current_user` is
|
||||
# nil, Warden will attempt to authenticate a user. To avoid
|
||||
# reauthenticating anonymous users, we may need to invalidate
|
||||
# the user.
|
||||
def reset_auth_user!
|
||||
return if strong_memoized?(:auth_user) && auth_user
|
||||
|
||||
# Controllers usually call auth_user first, but for some controllers
|
||||
# authenticate_sessionless_user! is called after that. If we relied
|
||||
# on the memoized auth_user, the value would always be nil for
|
||||
# sessionless users.
|
||||
clear_memoization(:auth_user)
|
||||
auth_user
|
||||
end
|
||||
|
||||
def log_exception(exception)
|
||||
# At this point, the controller already exits set_current_context around
|
||||
# block. To maintain the context while handling error exception, we need to
|
||||
|
|
|
@ -20,16 +20,21 @@ module SessionlessAuthentication
|
|||
end
|
||||
|
||||
def sessionless_sign_in(user)
|
||||
if user.can_log_in_with_non_expired_password?
|
||||
# Notice we are passing store false, so the user is not
|
||||
# actually stored in the session and a token is needed
|
||||
# for every request. If you want the token to work as a
|
||||
# sign in token, you can simply remove store: false.
|
||||
sign_in(user, store: false, message: :sessionless_sign_in)
|
||||
elsif request_authenticator.can_sign_in_bot?(user)
|
||||
# we suppress callbacks to avoid redirecting the bot
|
||||
sign_in(user, store: false, message: :sessionless_sign_in, run_callbacks: false)
|
||||
end
|
||||
signed_in_user =
|
||||
if user.can_log_in_with_non_expired_password?
|
||||
# Notice we are passing store false, so the user is not
|
||||
# actually stored in the session and a token is needed
|
||||
# for every request. If you want the token to work as a
|
||||
# sign in token, you can simply remove store: false.
|
||||
sign_in(user, store: false, message: :sessionless_sign_in)
|
||||
elsif request_authenticator.can_sign_in_bot?(user)
|
||||
# we suppress callbacks to avoid redirecting the bot
|
||||
sign_in(user, store: false, message: :sessionless_sign_in, run_callbacks: false)
|
||||
end
|
||||
|
||||
reset_auth_user! if respond_to?(:reset_auth_user!, true)
|
||||
|
||||
signed_in_user
|
||||
end
|
||||
|
||||
def sessionless_bypass_admin_mode!(&block)
|
||||
|
|
|
@ -5,6 +5,7 @@ module Mutations
|
|||
module CanaryIngress
|
||||
class Update < ::Mutations::BaseMutation
|
||||
graphql_name 'EnvironmentsCanaryIngressUpdate'
|
||||
description '**Deprecated** This endpoint is planned to be removed along with certificate-based clusters. [See this epic](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) for more information.'
|
||||
|
||||
authorize :update_environment
|
||||
|
||||
|
@ -18,7 +19,13 @@ module Mutations
|
|||
required: true,
|
||||
description: 'Weight of the Canary Ingress.'
|
||||
|
||||
REMOVAL_ERR_MSG = 'This endpoint was deactivated as part of the certificate-based' \
|
||||
'kubernetes integration removal. See Epic:' \
|
||||
'https://gitlab.com/groups/gitlab-org/configure/-/epics/8'
|
||||
|
||||
def resolve(id:, **kwargs)
|
||||
return { errors: [REMOVAL_ERR_MSG] } if cert_based_clusters_ff_disabled?
|
||||
|
||||
environment = authorized_find!(id: id)
|
||||
|
||||
result = ::Environments::CanaryIngress::UpdateService
|
||||
|
@ -33,6 +40,12 @@ module Mutations
|
|||
id = ::Types::GlobalIDType[::Environment].coerce_isolated_input(id)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cert_based_clusters_ff_disabled?
|
||||
Feature.disabled?(:certificate_based_clusters, default_enabled: :yaml, type: :ops)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,7 +51,7 @@ class EnvironmentStatus
|
|||
|
||||
def deployment
|
||||
strong_memoize(:deployment) do
|
||||
Deployment.where(environment: environment).find_by_sha(sha)
|
||||
Deployment.where(environment: environment).ordered.find_by_sha(sha)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveIssuesWorkItemTypeIdIndex < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_issues_on_work_item_type_id'
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :issues, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :issues, :work_item_type_id, name: INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTemporaryIssueTypeIndexForWorkItemTypes < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'tmp_index_issues_on_issue_type_and_id'
|
||||
|
||||
def up
|
||||
add_concurrent_index :issues, [:issue_type, :id], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :issues, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BackfillWorkItemTypeIdOnIssues < Gitlab::Database::Migration[1.0]
|
||||
MIGRATION = 'BackfillWorkItemTypeIdForIssues'
|
||||
BATCH_SIZE = 10_000
|
||||
MAX_BATCH_SIZE = 30_000
|
||||
SUB_BATCH_SIZE = 100
|
||||
INTERVAL = 2.minutes
|
||||
|
||||
class MigrationWorkItemType < ApplicationRecord
|
||||
self.table_name = 'work_item_types'
|
||||
|
||||
def self.id_by_type
|
||||
where(namespace_id: nil).order(:base_type).pluck(:base_type, :id).to_h
|
||||
end
|
||||
end
|
||||
|
||||
def up
|
||||
# We expect no more than 5 types. Only 3 of them are expected to have associated issues at the moment
|
||||
MigrationWorkItemType.id_by_type.each do |base_type, type_id|
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:issues,
|
||||
:id,
|
||||
base_type,
|
||||
type_id,
|
||||
job_interval: INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
max_batch_size: MAX_BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
Gitlab::Database::BackgroundMigration::BatchedMigration.where(job_class_name: MIGRATION).delete_all
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
1276e86db3e2922cd1535e98f17768d1ab0dd653317f918fb8e6819760893a47
|
|
@ -0,0 +1 @@
|
|||
8ab13f76037450704a29220ca6af0174b373ce743620952e3bc197c952d00c1d
|
|
@ -0,0 +1 @@
|
|||
7dc3d314e76f26a539c0aa009bd45ea6ccc9c6108f798728909fd4b18e3c31b9
|
|
@ -27961,8 +27961,6 @@ CREATE INDEX index_issues_on_updated_at ON issues USING btree (updated_at);
|
|||
|
||||
CREATE INDEX index_issues_on_updated_by_id ON issues USING btree (updated_by_id) WHERE (updated_by_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_issues_on_work_item_type_id ON issues USING btree (work_item_type_id);
|
||||
|
||||
CREATE INDEX index_iterations_cadences_on_group_id ON iterations_cadences USING btree (group_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_jira_connect_installations_on_client_key ON jira_connect_installations USING btree (client_key);
|
||||
|
@ -29615,6 +29613,8 @@ CREATE INDEX tmp_index_for_namespace_id_migration_on_group_members ON members US
|
|||
|
||||
CREATE INDEX tmp_index_for_namespace_id_migration_on_routes ON routes USING btree (id) WHERE ((namespace_id IS NULL) AND ((source_type)::text = 'Namespace'::text));
|
||||
|
||||
CREATE INDEX tmp_index_issues_on_issue_type_and_id ON issues USING btree (issue_type, id);
|
||||
|
||||
CREATE INDEX tmp_index_members_on_state ON members USING btree (state) WHERE (state = 2);
|
||||
|
||||
CREATE INDEX tmp_index_namespaces_empty_traversal_ids_with_child_namespaces ON namespaces USING btree (id) WHERE ((parent_id IS NOT NULL) AND (traversal_ids = '{}'::integer[]));
|
||||
|
|
|
@ -2372,6 +2372,8 @@ Input type: `EnableDevopsAdoptionNamespaceInput`
|
|||
|
||||
### `Mutation.environmentsCanaryIngressUpdate`
|
||||
|
||||
**Deprecated** This endpoint is planned to be removed along with certificate-based clusters. [See this epic](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) for more information.
|
||||
|
||||
Input type: `EnvironmentsCanaryIngressUpdateInput`
|
||||
|
||||
#### Arguments
|
||||
|
|
|
@ -23,7 +23,7 @@ Otherwise, to add your license:
|
|||
1. Select the **Terms of Service** checkbox.
|
||||
1. Select **Add license**.
|
||||
|
||||
## Add your license during installation
|
||||
## Add your license file during installation
|
||||
|
||||
You can import a license file when you install GitLab.
|
||||
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# Backfills the `issues.work_item_type_id` column, replacing any
|
||||
# instances of `NULL` with the appropriate `work_item_types.id` based on `issues.issue_type`
|
||||
class BackfillWorkItemTypeIdForIssues
|
||||
# Basic AR model for issues table
|
||||
class MigrationIssue < ApplicationRecord
|
||||
include ::EachBatch
|
||||
|
||||
self.table_name = 'issues'
|
||||
|
||||
scope :base_query, ->(base_type) { where(work_item_type_id: nil, issue_type: base_type) }
|
||||
end
|
||||
|
||||
MAX_UPDATE_RETRIES = 3
|
||||
|
||||
def perform(start_id, end_id, batch_table, batch_column, sub_batch_size, pause_ms, base_type, base_type_id)
|
||||
parent_batch_relation = relation_scoped_to_range(batch_table, batch_column, start_id, end_id, base_type)
|
||||
|
||||
parent_batch_relation.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
|
||||
first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
|
||||
|
||||
# The query need to be reconstructed because .each_batch modifies the default scope
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330510
|
||||
reconstructed_sub_batch = MigrationIssue.unscoped.base_query(base_type).where(id: first..last)
|
||||
|
||||
batch_metrics.time_operation(:update_all) do
|
||||
update_with_retry(reconstructed_sub_batch, base_type_id)
|
||||
end
|
||||
|
||||
pause_ms = 0 if pause_ms < 0
|
||||
sleep(pause_ms * 0.001)
|
||||
end
|
||||
end
|
||||
|
||||
def batch_metrics
|
||||
@batch_metrics ||= Gitlab::Database::BackgroundMigration::BatchMetrics.new
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Retry mechanism required as update statements on the issues table will randomly take longer than
|
||||
# expected due to gin indexes https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71869#note_775796352
|
||||
def update_with_retry(sub_batch, base_type_id)
|
||||
update_attempt = 1
|
||||
|
||||
begin
|
||||
update_batch(sub_batch, base_type_id)
|
||||
rescue ActiveRecord::StatementTimeout => e
|
||||
update_attempt += 1
|
||||
|
||||
if update_attempt <= MAX_UPDATE_RETRIES
|
||||
# sleeping 30 seconds as it might take a long time to clean the gin index pending list
|
||||
sleep(30)
|
||||
retry
|
||||
end
|
||||
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
def update_batch(sub_batch, base_type_id)
|
||||
sub_batch.update_all(work_item_type_id: base_type_id)
|
||||
end
|
||||
|
||||
def relation_scoped_to_range(source_table, source_key_column, start_id, end_id, base_type)
|
||||
MigrationIssue.where(source_key_column => start_id..end_id).base_query(base_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -42374,7 +42374,7 @@ msgstr ""
|
|||
msgid "WorkItem|New Task"
|
||||
msgstr ""
|
||||
|
||||
msgid "WorkItem|Please select work item type"
|
||||
msgid "WorkItem|Select type"
|
||||
msgstr ""
|
||||
|
||||
msgid "WorkItem|Something went wrong when creating a work item. Please try again"
|
||||
|
|
|
@ -5,4 +5,7 @@ require_relative '../spec/simplecov_env'
|
|||
SimpleCovEnv.configure_profile
|
||||
SimpleCovEnv.configure_formatter
|
||||
|
||||
SimpleCov.collate Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json'))
|
||||
resultset_files = Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json'))
|
||||
exit(0) if resultset_files.empty?
|
||||
|
||||
SimpleCov.collate(resultset_files)
|
||||
|
|
|
@ -134,6 +134,12 @@ RSpec.describe GraphqlController do
|
|||
|
||||
post :execute
|
||||
end
|
||||
|
||||
it "assigns username in ApplicationContext" do
|
||||
post :execute
|
||||
|
||||
expect(Gitlab::ApplicationContext.current).to include('meta.user' => user.username)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user uses an API token' do
|
||||
|
@ -189,6 +195,12 @@ RSpec.describe GraphqlController do
|
|||
expect(assigns(:context)[:is_sessionless_user]).to be true
|
||||
end
|
||||
|
||||
it "assigns username in ApplicationContext" do
|
||||
subject
|
||||
|
||||
expect(Gitlab::ApplicationContext.current).to include('meta.user' => user.username)
|
||||
end
|
||||
|
||||
it 'calls the track api when trackable method' do
|
||||
agent = 'vs-code-gitlab-workflow/3.11.1 VSCode/1.52.1 Node.js/12.14.1 (darwin; x64)'
|
||||
request.env['HTTP_USER_AGENT'] = agent
|
||||
|
@ -222,6 +234,12 @@ RSpec.describe GraphqlController do
|
|||
|
||||
expect(assigns(:context)[:is_sessionless_user]).to be false
|
||||
end
|
||||
|
||||
it "does not assign a username in ApplicationContext" do
|
||||
subject
|
||||
|
||||
expect(Gitlab::ApplicationContext.current.key?('meta.user')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'includes request object in context' do
|
||||
|
|
|
@ -10,6 +10,10 @@ RSpec.describe 'Database schema' do
|
|||
let(:tables) { connection.tables }
|
||||
let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb }
|
||||
|
||||
IGNORED_INDEXES_ON_FKS = {
|
||||
issues: %w[work_item_type_id]
|
||||
}.with_indifferent_access.freeze
|
||||
|
||||
# List of columns historically missing a FK, don't add more columns
|
||||
# See: https://docs.gitlab.com/ee/development/foreign_keys.html#naming-foreign-keys
|
||||
IGNORED_FK_COLUMNS = {
|
||||
|
@ -115,6 +119,7 @@ RSpec.describe 'Database schema' do
|
|||
columns.first.chomp
|
||||
end
|
||||
foreign_keys_columns = all_foreign_keys.map(&:column)
|
||||
required_indexed_columns = foreign_keys_columns - ignored_index_columns(table)
|
||||
|
||||
# Add the primary key column to the list of indexed columns because
|
||||
# postgres and mysql both automatically create an index on the primary
|
||||
|
@ -122,7 +127,7 @@ RSpec.describe 'Database schema' do
|
|||
# automatically generated indexes (like the primary key index).
|
||||
first_indexed_column.push(primary_key_column)
|
||||
|
||||
expect(first_indexed_column.uniq).to include(*foreign_keys_columns)
|
||||
expect(first_indexed_column.uniq).to include(*required_indexed_columns)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -305,8 +310,12 @@ RSpec.describe 'Database schema' do
|
|||
@models_by_table_name ||= ApplicationRecord.descendants.reject(&:abstract_class).group_by(&:table_name)
|
||||
end
|
||||
|
||||
def ignored_fk_columns(column)
|
||||
IGNORED_FK_COLUMNS.fetch(column, [])
|
||||
def ignored_fk_columns(table)
|
||||
IGNORED_FK_COLUMNS.fetch(table, [])
|
||||
end
|
||||
|
||||
def ignored_index_columns(table)
|
||||
IGNORED_INDEXES_ON_FKS.fetch(table, [])
|
||||
end
|
||||
|
||||
def ignored_limit_enums(model)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import { nextTick } from 'vue';
|
||||
import '~/behaviors/markdown/render_gfm';
|
||||
import { GlPopover, GlModal } from '@gitlab/ui';
|
||||
import { GlTooltip, GlModal } from '@gitlab/ui';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
|
@ -29,9 +29,9 @@ describe('Description component', () => {
|
|||
const findGfmContent = () => wrapper.find('[data-testid="gfm-content"]');
|
||||
const findTextarea = () => wrapper.find('[data-testid="textarea"]');
|
||||
const findTaskActionButtons = () => wrapper.findAll('.js-add-task');
|
||||
const findConvertToTaskButton = () => wrapper.find('[data-testid="convert-to-task"]');
|
||||
const findConvertToTaskButton = () => wrapper.find('.js-add-task');
|
||||
|
||||
const findPopovers = () => wrapper.findAllComponents(GlPopover);
|
||||
const findTooltips = () => wrapper.findAllComponents(GlTooltip);
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findCreateWorkItem = () => wrapper.findComponent(CreateWorkItem);
|
||||
const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
|
||||
|
@ -51,7 +51,6 @@ describe('Description component', () => {
|
|||
hide: hideModal,
|
||||
},
|
||||
}),
|
||||
GlPopover,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -254,9 +253,9 @@ describe('Description component', () => {
|
|||
expect(findTaskActionButtons()).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('renders a list of popovers corresponding to checkboxes in description HTML', () => {
|
||||
expect(findPopovers()).toHaveLength(3);
|
||||
expect(findPopovers().at(0).props('target')).toBe(
|
||||
it('renders a list of tooltips corresponding to checkboxes in description HTML', () => {
|
||||
expect(findTooltips()).toHaveLength(3);
|
||||
expect(findTooltips().at(0).props('target')).toBe(
|
||||
findTaskActionButtons().at(0).attributes('id'),
|
||||
);
|
||||
});
|
||||
|
@ -265,20 +264,18 @@ describe('Description component', () => {
|
|||
expect(findModal().props('visible')).toBe(false);
|
||||
});
|
||||
|
||||
it('opens a modal when a button on popover is clicked and displays correct title', async () => {
|
||||
findConvertToTaskButton().vm.$emit('click');
|
||||
expect(showModal).toHaveBeenCalled();
|
||||
await nextTick();
|
||||
it('opens a modal when a button is clicked and displays correct title', async () => {
|
||||
await findConvertToTaskButton().trigger('click');
|
||||
expect(findCreateWorkItem().props('initialTitle').trim()).toBe('todo 1');
|
||||
});
|
||||
|
||||
it('closes the modal on `closeCreateTaskModal` event', () => {
|
||||
findConvertToTaskButton().vm.$emit('click');
|
||||
it('closes the modal on `closeCreateTaskModal` event', async () => {
|
||||
await findConvertToTaskButton().trigger('click');
|
||||
findCreateWorkItem().vm.$emit('closeModal');
|
||||
expect(hideModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits `updateDescription` on `onCreate` event', async () => {
|
||||
it('emits `updateDescription` on `onCreate` event', () => {
|
||||
const newDescription = `<p>New description</p>`;
|
||||
findCreateWorkItem().vm.$emit('onCreate', newDescription);
|
||||
expect(hideModal).toHaveBeenCalled();
|
||||
|
|
|
@ -36,6 +36,20 @@ RSpec.describe Mutations::Environments::CanaryIngress::Update do
|
|||
it 'returns no errors' do
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
|
||||
context 'with certificate_based_clusters disabled' do
|
||||
before do
|
||||
stub_feature_flags(certificate_based_clusters: false)
|
||||
end
|
||||
|
||||
it 'returns notice about feature removal' do
|
||||
expect(subject[:errors]).to match_array([
|
||||
'This endpoint was deactivated as part of the certificate-based' \
|
||||
'kubernetes integration removal. See Epic:' \
|
||||
'https://gitlab.com/groups/gitlab-org/configure/-/epics/8'
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service encounters a problem' do
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillWorkItemTypeIdForIssues do
|
||||
subject(:migrate) { migration.perform(start_id, end_id, batch_table, batch_column, sub_batch_size, pause_ms, issue_type_enum[:issue], issue_type.id) }
|
||||
|
||||
let(:migration) { described_class.new }
|
||||
|
||||
let(:batch_table) { 'issues' }
|
||||
let(:batch_column) { 'id' }
|
||||
let(:sub_batch_size) { 2 }
|
||||
let(:pause_ms) { 0 }
|
||||
|
||||
# let_it_be can't be used in migration specs because all tables but `work_item_types` are deleted after each spec
|
||||
let(:issue_type_enum) { { issue: 0, incident: 1, test_case: 2, requirement: 3, task: 4 } }
|
||||
let(:namespace) { table(:namespaces).create!(name: 'namespace', path: 'namespace') }
|
||||
let(:project) { table(:projects).create!(namespace_id: namespace.id) }
|
||||
let(:issues_table) { table(:issues) }
|
||||
let(:issue_type) { table(:work_item_types).find_by!(namespace_id: nil, base_type: issue_type_enum[:issue]) }
|
||||
|
||||
let(:issue1) { issues_table.create!(project_id: project.id, issue_type: issue_type_enum[:issue]) }
|
||||
let(:issue2) { issues_table.create!(project_id: project.id, issue_type: issue_type_enum[:issue]) }
|
||||
let(:issue3) { issues_table.create!(project_id: project.id, issue_type: issue_type_enum[:issue]) }
|
||||
let(:incident1) { issues_table.create!(project_id: project.id, issue_type: issue_type_enum[:incident]) }
|
||||
# test_case and requirement are EE only, but enum values exist on the FOSS model
|
||||
let(:test_case1) { issues_table.create!(project_id: project.id, issue_type: issue_type_enum[:test_case]) }
|
||||
let(:requirement1) { issues_table.create!(project_id: project.id, issue_type: issue_type_enum[:requirement]) }
|
||||
|
||||
let(:start_id) { issue1.id }
|
||||
let(:end_id) { requirement1.id }
|
||||
|
||||
let(:all_issues) { [issue1, issue2, issue3, incident1, test_case1, requirement1] }
|
||||
|
||||
it 'sets work_item_type_id only for the given type' do
|
||||
expect(all_issues).to all(have_attributes(work_item_type_id: nil))
|
||||
|
||||
expect { migrate }.to make_queries_matching(/UPDATE \"issues\" SET "work_item_type_id"/, 2)
|
||||
all_issues.each(&:reload)
|
||||
|
||||
expect([issue1, issue2, issue3]).to all(have_attributes(work_item_type_id: issue_type.id))
|
||||
expect(all_issues - [issue1, issue2, issue3]).to all(have_attributes(work_item_type_id: nil))
|
||||
end
|
||||
|
||||
it 'tracks timings of queries' do
|
||||
expect(migration.batch_metrics.timings).to be_empty
|
||||
|
||||
expect { migrate }.to change { migration.batch_metrics.timings }
|
||||
end
|
||||
|
||||
it 'retries on ActiveRecord::StatementTimeout' do
|
||||
expect(migration).to receive(:update_batch).exactly(3).times.and_raise(ActiveRecord::StatementTimeout)
|
||||
expect(migration).to receive(:sleep).with(30).twice
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.to raise_error(ActiveRecord::StatementTimeout)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe BackfillWorkItemTypeIdOnIssues, :migration do
|
||||
let_it_be(:migration) { described_class::MIGRATION }
|
||||
let_it_be(:interval) { 2.minutes }
|
||||
let_it_be(:issue_type_enum) { { issue: 0, incident: 1, test_case: 2, requirement: 3, task: 4 } }
|
||||
let_it_be(:base_work_item_type_ids) do
|
||||
table(:work_item_types).where(namespace_id: nil).order(:base_type).each_with_object({}) do |type, hash|
|
||||
hash[type.base_type] = type.id
|
||||
end
|
||||
end
|
||||
|
||||
describe '#up' do
|
||||
it 'correctly schedules background migrations' do
|
||||
Sidekiq::Testing.fake! do
|
||||
freeze_time do
|
||||
migrate!
|
||||
|
||||
scheduled_migrations = Gitlab::Database::BackgroundMigration::BatchedMigration.where(job_class_name: migration)
|
||||
work_item_types = table(:work_item_types).where(namespace_id: nil)
|
||||
|
||||
expect(scheduled_migrations.count).to eq(work_item_types.count)
|
||||
|
||||
[:issue, :incident, :test_case, :requirement, :task].each do |issue_type|
|
||||
expect(migration).to have_scheduled_batched_migration(
|
||||
table_name: :issues,
|
||||
column_name: :id,
|
||||
job_arguments: [issue_type_enum[issue_type], base_work_item_type_ids[issue_type_enum[issue_type]]],
|
||||
interval: interval,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
max_batch_size: described_class::MAX_BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#down' do
|
||||
it 'deletes all batched migration records' do
|
||||
migrate!
|
||||
schema_migrate_down!
|
||||
|
||||
expect(migration).not_to have_scheduled_batched_migration
|
||||
end
|
||||
end
|
||||
end
|
|
@ -34,6 +34,13 @@ RSpec.describe EnvironmentStatus do
|
|||
subject { environment_status.deployment }
|
||||
|
||||
it { is_expected.to eq(deployment) }
|
||||
|
||||
context 'multiple deployments' do
|
||||
it {
|
||||
new_deployment = create(:deployment, :succeed, environment: deployment.environment, sha: deployment.sha )
|
||||
is_expected.to eq(new_deployment)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# $ git diff --stat pages-deploy-target...pages-deploy
|
||||
|
|
Loading…
Reference in New Issue