Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5316a9bca9
commit
35b0f1fe13
|
@ -260,6 +260,8 @@
|
||||||
- ".gitlab/ci/**/*"
|
- ".gitlab/ci/**/*"
|
||||||
- "*_VERSION"
|
- "*_VERSION"
|
||||||
- "scripts/rspec_helpers.sh"
|
- "scripts/rspec_helpers.sh"
|
||||||
|
# Mapped patterns (see tests.yml)
|
||||||
|
- "data/whats_new/*.yml"
|
||||||
|
|
||||||
# DB patterns + .ci-patterns
|
# DB patterns + .ci-patterns
|
||||||
.db-patterns: &db-patterns
|
.db-patterns: &db-patterns
|
||||||
|
@ -314,10 +316,11 @@
|
||||||
- "config.ru"
|
- "config.ru"
|
||||||
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
|
- "{,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
|
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
|
||||||
- "data/whats_new/*.yml"
|
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- ".gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- ".gitlab/ci/**/*"
|
||||||
|
# Mapped patterns (see tests.yml)
|
||||||
|
- "data/whats_new/*.yml"
|
||||||
|
|
||||||
# .code-patterns + .backstage-patterns
|
# .code-patterns + .backstage-patterns
|
||||||
.code-backstage-patterns: &code-backstage-patterns
|
.code-backstage-patterns: &code-backstage-patterns
|
||||||
|
@ -338,7 +341,6 @@
|
||||||
- "config.ru"
|
- "config.ru"
|
||||||
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
|
- "{,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
|
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
|
||||||
- "data/whats_new/*.yml"
|
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- ".gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- ".gitlab/ci/**/*"
|
||||||
|
@ -349,6 +351,8 @@
|
||||||
- "{,ee/,jh/}rubocop/**/*"
|
- "{,ee/,jh/}rubocop/**/*"
|
||||||
- "{,ee/,jh/}spec/**/*"
|
- "{,ee/,jh/}spec/**/*"
|
||||||
- "{,spec/}tooling/**/*"
|
- "{,spec/}tooling/**/*"
|
||||||
|
# Mapped patterns (see tests.yml)
|
||||||
|
- "data/whats_new/*.yml"
|
||||||
|
|
||||||
# .code-patterns + .qa-patterns
|
# .code-patterns + .qa-patterns
|
||||||
.code-qa-patterns: &code-qa-patterns
|
.code-qa-patterns: &code-qa-patterns
|
||||||
|
@ -369,13 +373,14 @@
|
||||||
- "config.ru"
|
- "config.ru"
|
||||||
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
|
- "{,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
|
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
|
||||||
- "data/whats_new/*.yml"
|
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- ".gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- ".gitlab/ci/**/*"
|
||||||
# QA changes
|
# QA changes
|
||||||
- ".dockerignore"
|
- ".dockerignore"
|
||||||
- "qa/**/*"
|
- "qa/**/*"
|
||||||
|
# Mapped patterns (see tests.yml)
|
||||||
|
- "data/whats_new/*.yml"
|
||||||
|
|
||||||
# .code-patterns + .backstage-patterns + .qa-patterns
|
# .code-patterns + .backstage-patterns + .qa-patterns
|
||||||
.code-backstage-qa-patterns: &code-backstage-qa-patterns
|
.code-backstage-qa-patterns: &code-backstage-qa-patterns
|
||||||
|
@ -396,7 +401,6 @@
|
||||||
- "config.ru"
|
- "config.ru"
|
||||||
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
|
- "{,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
|
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
|
||||||
- "data/whats_new/*.yml"
|
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- ".gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- ".gitlab/ci/**/*"
|
||||||
|
@ -410,6 +414,8 @@
|
||||||
# QA changes
|
# QA changes
|
||||||
- ".dockerignore"
|
- ".dockerignore"
|
||||||
- "qa/**/*"
|
- "qa/**/*"
|
||||||
|
# Mapped patterns (see tests.yml)
|
||||||
|
- "data/whats_new/*.yml"
|
||||||
|
|
||||||
# .code-backstage-qa-patterns + .workhorse-patterns
|
# .code-backstage-qa-patterns + .workhorse-patterns
|
||||||
.setup-test-env-patterns: &setup-test-env-patterns
|
.setup-test-env-patterns: &setup-test-env-patterns
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { GlSafeHtmlDirective as SafeHtml, GlModal, GlTooltip, GlModalDirective } from '@gitlab/ui';
|
||||||
GlSafeHtmlDirective as SafeHtml,
|
|
||||||
GlModal,
|
|
||||||
GlModalDirective,
|
|
||||||
GlPopover,
|
|
||||||
GlButton,
|
|
||||||
} from '@gitlab/ui';
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||||
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
|
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, s__, sprintf } from '~/locale';
|
||||||
import TaskList from '~/task_list';
|
import TaskList from '~/task_list';
|
||||||
import Tracking from '~/tracking';
|
import Tracking from '~/tracking';
|
||||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
@ -25,9 +19,8 @@ export default {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
GlModal,
|
GlModal,
|
||||||
GlPopover,
|
|
||||||
CreateWorkItem,
|
CreateWorkItem,
|
||||||
GlButton,
|
GlTooltip,
|
||||||
WorkItemDetailModal,
|
WorkItemDetailModal,
|
||||||
},
|
},
|
||||||
mixins: [animateMixin, glFeatureFlagMixin(), Tracking.mixin()],
|
mixins: [animateMixin, glFeatureFlagMixin(), Tracking.mixin()],
|
||||||
|
@ -216,9 +209,11 @@ export default {
|
||||||
this.taskButtons.push(button.id);
|
this.taskButtons.push(button.id);
|
||||||
button.innerHTML = `
|
button.innerHTML = `
|
||||||
<svg data-testid="ellipsis_v-icon" role="img" aria-hidden="true" class="dropdown-icon gl-icon s14">
|
<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>
|
</svg>
|
||||||
`;
|
`;
|
||||||
|
button.setAttribute('aria-label', s__('WorkItem|Convert to work item'));
|
||||||
|
button.addEventListener('click', () => this.openCreateTaskModal(button.id));
|
||||||
item.prepend(button);
|
item.prepend(button);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -246,9 +241,6 @@ export default {
|
||||||
this.$emit('updateDescription', description);
|
this.$emit('updateDescription', description);
|
||||||
this.closeCreateTaskModal();
|
this.closeCreateTaskModal();
|
||||||
},
|
},
|
||||||
focusButton() {
|
|
||||||
this.$refs.convertButton[0].$el.focus();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] },
|
safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] },
|
||||||
};
|
};
|
||||||
|
@ -309,23 +301,9 @@ export default {
|
||||||
@error="handleWorkItemDetailModalError"
|
@error="handleWorkItemDetailModalError"
|
||||||
/>
|
/>
|
||||||
<template v-if="workItemsEnabled">
|
<template v-if="workItemsEnabled">
|
||||||
<gl-popover
|
<gl-tooltip v-for="item in taskButtons" :key="item" :target="item">
|
||||||
v-for="item in taskButtons"
|
{{ s__('WorkItem|Convert to work item') }}
|
||||||
:key="item"
|
</gl-tooltip>
|
||||||
: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>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -87,10 +87,7 @@ export default {
|
||||||
return this.selectedWorkItemType?.name || s__('WorkItem|Type');
|
return this.selectedWorkItemType?.name || s__('WorkItem|Type');
|
||||||
},
|
},
|
||||||
formOptions() {
|
formOptions() {
|
||||||
return [
|
return [{ value: null, text: s__('WorkItem|Select type') }, ...this.workItemTypes];
|
||||||
{ value: null, text: s__('WorkItem|Please select work item type') },
|
|
||||||
...this.workItemTypes,
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
isButtonDisabled() {
|
isButtonDisabled() {
|
||||||
return this.title.trim().length === 0 || !this.selectedWorkItemType;
|
return this.title.trim().length === 0 || !this.selectedWorkItemType;
|
||||||
|
|
|
@ -311,7 +311,7 @@ ul.related-merge-requests > li gl-emoji {
|
||||||
.description.work-items-enabled {
|
.description.work-items-enabled {
|
||||||
ul.task-list {
|
ul.task-list {
|
||||||
> li.task-list-item {
|
> li.task-list-item {
|
||||||
padding-inline-start: 2.25rem;
|
padding-inline-start: 2.5rem;
|
||||||
|
|
||||||
.js-add-task {
|
.js-add-task {
|
||||||
svg {
|
svg {
|
||||||
|
@ -324,7 +324,7 @@ ul.related-merge-requests > li gl-emoji {
|
||||||
}
|
}
|
||||||
|
|
||||||
> input.task-list-item-checkbox {
|
> input.task-list-item-checkbox {
|
||||||
left: 0.875rem;
|
left: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
|
|
@ -196,6 +196,27 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
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)
|
def log_exception(exception)
|
||||||
# At this point, the controller already exits set_current_context around
|
# At this point, the controller already exits set_current_context around
|
||||||
# block. To maintain the context while handling error exception, we need to
|
# block. To maintain the context while handling error exception, we need to
|
||||||
|
|
|
@ -20,6 +20,7 @@ module SessionlessAuthentication
|
||||||
end
|
end
|
||||||
|
|
||||||
def sessionless_sign_in(user)
|
def sessionless_sign_in(user)
|
||||||
|
signed_in_user =
|
||||||
if user.can_log_in_with_non_expired_password?
|
if user.can_log_in_with_non_expired_password?
|
||||||
# Notice we are passing store false, so the user is not
|
# Notice we are passing store false, so the user is not
|
||||||
# actually stored in the session and a token is needed
|
# actually stored in the session and a token is needed
|
||||||
|
@ -30,6 +31,10 @@ module SessionlessAuthentication
|
||||||
# we suppress callbacks to avoid redirecting the bot
|
# we suppress callbacks to avoid redirecting the bot
|
||||||
sign_in(user, store: false, message: :sessionless_sign_in, run_callbacks: false)
|
sign_in(user, store: false, message: :sessionless_sign_in, run_callbacks: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
reset_auth_user! if respond_to?(:reset_auth_user!, true)
|
||||||
|
|
||||||
|
signed_in_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def sessionless_bypass_admin_mode!(&block)
|
def sessionless_bypass_admin_mode!(&block)
|
||||||
|
|
|
@ -5,6 +5,7 @@ module Mutations
|
||||||
module CanaryIngress
|
module CanaryIngress
|
||||||
class Update < ::Mutations::BaseMutation
|
class Update < ::Mutations::BaseMutation
|
||||||
graphql_name 'EnvironmentsCanaryIngressUpdate'
|
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
|
authorize :update_environment
|
||||||
|
|
||||||
|
@ -18,7 +19,13 @@ module Mutations
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Weight of the Canary Ingress.'
|
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)
|
def resolve(id:, **kwargs)
|
||||||
|
return { errors: [REMOVAL_ERR_MSG] } if cert_based_clusters_ff_disabled?
|
||||||
|
|
||||||
environment = authorized_find!(id: id)
|
environment = authorized_find!(id: id)
|
||||||
|
|
||||||
result = ::Environments::CanaryIngress::UpdateService
|
result = ::Environments::CanaryIngress::UpdateService
|
||||||
|
@ -33,6 +40,12 @@ module Mutations
|
||||||
id = ::Types::GlobalIDType[::Environment].coerce_isolated_input(id)
|
id = ::Types::GlobalIDType[::Environment].coerce_isolated_input(id)
|
||||||
GitlabSchema.find_by_gid(id)
|
GitlabSchema.find_by_gid(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def cert_based_clusters_ff_disabled?
|
||||||
|
Feature.disabled?(:certificate_based_clusters, default_enabled: :yaml, type: :ops)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,7 +51,7 @@ class EnvironmentStatus
|
||||||
|
|
||||||
def deployment
|
def deployment
|
||||||
strong_memoize(:deployment) do
|
strong_memoize(:deployment) do
|
||||||
Deployment.where(environment: environment).find_by_sha(sha)
|
Deployment.where(environment: environment).ordered.find_by_sha(sha)
|
||||||
end
|
end
|
||||||
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_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 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);
|
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_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_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[]));
|
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`
|
### `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`
|
Input type: `EnvironmentsCanaryIngressUpdateInput`
|
||||||
|
|
||||||
#### Arguments
|
#### Arguments
|
||||||
|
|
|
@ -23,7 +23,7 @@ Otherwise, to add your license:
|
||||||
1. Select the **Terms of Service** checkbox.
|
1. Select the **Terms of Service** checkbox.
|
||||||
1. Select **Add license**.
|
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.
|
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"
|
msgid "WorkItem|New Task"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "WorkItem|Please select work item type"
|
msgid "WorkItem|Select type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "WorkItem|Something went wrong when creating a work item. Please try again"
|
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_profile
|
||||||
SimpleCovEnv.configure_formatter
|
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
|
post :execute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "assigns username in ApplicationContext" do
|
||||||
|
post :execute
|
||||||
|
|
||||||
|
expect(Gitlab::ApplicationContext.current).to include('meta.user' => user.username)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user uses an API token' do
|
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
|
expect(assigns(:context)[:is_sessionless_user]).to be true
|
||||||
end
|
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
|
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)'
|
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
|
request.env['HTTP_USER_AGENT'] = agent
|
||||||
|
@ -222,6 +234,12 @@ RSpec.describe GraphqlController do
|
||||||
|
|
||||||
expect(assigns(:context)[:is_sessionless_user]).to be false
|
expect(assigns(:context)[:is_sessionless_user]).to be false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "does not assign a username in ApplicationContext" do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(Gitlab::ApplicationContext.current.key?('meta.user')).to be false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes request object in context' do
|
it 'includes request object in context' do
|
||||||
|
|
|
@ -10,6 +10,10 @@ RSpec.describe 'Database schema' do
|
||||||
let(:tables) { connection.tables }
|
let(:tables) { connection.tables }
|
||||||
let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb }
|
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
|
# 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
|
# See: https://docs.gitlab.com/ee/development/foreign_keys.html#naming-foreign-keys
|
||||||
IGNORED_FK_COLUMNS = {
|
IGNORED_FK_COLUMNS = {
|
||||||
|
@ -115,6 +119,7 @@ RSpec.describe 'Database schema' do
|
||||||
columns.first.chomp
|
columns.first.chomp
|
||||||
end
|
end
|
||||||
foreign_keys_columns = all_foreign_keys.map(&:column)
|
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
|
# Add the primary key column to the list of indexed columns because
|
||||||
# postgres and mysql both automatically create an index on the primary
|
# 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).
|
# automatically generated indexes (like the primary key index).
|
||||||
first_indexed_column.push(primary_key_column)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -305,8 +310,12 @@ RSpec.describe 'Database schema' do
|
||||||
@models_by_table_name ||= ApplicationRecord.descendants.reject(&:abstract_class).group_by(&:table_name)
|
@models_by_table_name ||= ApplicationRecord.descendants.reject(&:abstract_class).group_by(&:table_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def ignored_fk_columns(column)
|
def ignored_fk_columns(table)
|
||||||
IGNORED_FK_COLUMNS.fetch(column, [])
|
IGNORED_FK_COLUMNS.fetch(table, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def ignored_index_columns(table)
|
||||||
|
IGNORED_INDEXES_ON_FKS.fetch(table, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def ignored_limit_enums(model)
|
def ignored_limit_enums(model)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import '~/behaviors/markdown/render_gfm';
|
import '~/behaviors/markdown/render_gfm';
|
||||||
import { GlPopover, GlModal } from '@gitlab/ui';
|
import { GlTooltip, GlModal } from '@gitlab/ui';
|
||||||
import { stubComponent } from 'helpers/stub_component';
|
import { stubComponent } from 'helpers/stub_component';
|
||||||
import { TEST_HOST } from 'helpers/test_constants';
|
import { TEST_HOST } from 'helpers/test_constants';
|
||||||
import { mockTracking } from 'helpers/tracking_helper';
|
import { mockTracking } from 'helpers/tracking_helper';
|
||||||
|
@ -29,9 +29,9 @@ describe('Description component', () => {
|
||||||
const findGfmContent = () => wrapper.find('[data-testid="gfm-content"]');
|
const findGfmContent = () => wrapper.find('[data-testid="gfm-content"]');
|
||||||
const findTextarea = () => wrapper.find('[data-testid="textarea"]');
|
const findTextarea = () => wrapper.find('[data-testid="textarea"]');
|
||||||
const findTaskActionButtons = () => wrapper.findAll('.js-add-task');
|
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 findModal = () => wrapper.findComponent(GlModal);
|
||||||
const findCreateWorkItem = () => wrapper.findComponent(CreateWorkItem);
|
const findCreateWorkItem = () => wrapper.findComponent(CreateWorkItem);
|
||||||
const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
|
const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
|
||||||
|
@ -51,7 +51,6 @@ describe('Description component', () => {
|
||||||
hide: hideModal,
|
hide: hideModal,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
GlPopover,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -254,9 +253,9 @@ describe('Description component', () => {
|
||||||
expect(findTaskActionButtons()).toHaveLength(3);
|
expect(findTaskActionButtons()).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a list of popovers corresponding to checkboxes in description HTML', () => {
|
it('renders a list of tooltips corresponding to checkboxes in description HTML', () => {
|
||||||
expect(findPopovers()).toHaveLength(3);
|
expect(findTooltips()).toHaveLength(3);
|
||||||
expect(findPopovers().at(0).props('target')).toBe(
|
expect(findTooltips().at(0).props('target')).toBe(
|
||||||
findTaskActionButtons().at(0).attributes('id'),
|
findTaskActionButtons().at(0).attributes('id'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -265,20 +264,18 @@ describe('Description component', () => {
|
||||||
expect(findModal().props('visible')).toBe(false);
|
expect(findModal().props('visible')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens a modal when a button on popover is clicked and displays correct title', async () => {
|
it('opens a modal when a button is clicked and displays correct title', async () => {
|
||||||
findConvertToTaskButton().vm.$emit('click');
|
await findConvertToTaskButton().trigger('click');
|
||||||
expect(showModal).toHaveBeenCalled();
|
|
||||||
await nextTick();
|
|
||||||
expect(findCreateWorkItem().props('initialTitle').trim()).toBe('todo 1');
|
expect(findCreateWorkItem().props('initialTitle').trim()).toBe('todo 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes the modal on `closeCreateTaskModal` event', () => {
|
it('closes the modal on `closeCreateTaskModal` event', async () => {
|
||||||
findConvertToTaskButton().vm.$emit('click');
|
await findConvertToTaskButton().trigger('click');
|
||||||
findCreateWorkItem().vm.$emit('closeModal');
|
findCreateWorkItem().vm.$emit('closeModal');
|
||||||
expect(hideModal).toHaveBeenCalled();
|
expect(hideModal).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('emits `updateDescription` on `onCreate` event', async () => {
|
it('emits `updateDescription` on `onCreate` event', () => {
|
||||||
const newDescription = `<p>New description</p>`;
|
const newDescription = `<p>New description</p>`;
|
||||||
findCreateWorkItem().vm.$emit('onCreate', newDescription);
|
findCreateWorkItem().vm.$emit('onCreate', newDescription);
|
||||||
expect(hideModal).toHaveBeenCalled();
|
expect(hideModal).toHaveBeenCalled();
|
||||||
|
|
|
@ -36,6 +36,20 @@ RSpec.describe Mutations::Environments::CanaryIngress::Update do
|
||||||
it 'returns no errors' do
|
it 'returns no errors' do
|
||||||
expect(subject[:errors]).to be_empty
|
expect(subject[:errors]).to be_empty
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context 'when service encounters a problem' do
|
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 }
|
subject { environment_status.deployment }
|
||||||
|
|
||||||
it { is_expected.to eq(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
|
end
|
||||||
|
|
||||||
# $ git diff --stat pages-deploy-target...pages-deploy
|
# $ git diff --stat pages-deploy-target...pages-deploy
|
||||||
|
|
Loading…
Reference in New Issue