Commit 36271f3a authored by Nick Thomas's avatar Nick Thomas

Merge branch 'issue_28457' into 'master'

Change state to integer for issuables - Patch 2 of 3

See merge request gitlab-org/gitlab!18099
parents 20fb8ed9 e404c311
...@@ -161,7 +161,7 @@ class IssuableFinder ...@@ -161,7 +161,7 @@ class IssuableFinder
labels_count = label_names.any? ? label_names.count : 1 labels_count = label_names.any? ? label_names.count : 1
labels_count = 1 if use_cte_for_search? labels_count = 1 if use_cte_for_search?
finder.execute.reorder(nil).group(:state).count.each do |key, value| finder.execute.reorder(nil).group(:state_id).count.each do |key, value|
counts[count_key(key)] += value / labels_count counts[count_key(key)] += value / labels_count
end end
...@@ -385,7 +385,8 @@ class IssuableFinder ...@@ -385,7 +385,8 @@ class IssuableFinder
end end
def count_key(value) def count_key(value)
Array(value).last.to_sym value = Array(value).last
klass.available_states.key(value)
end end
# Negates all params found in `negatable_params` # Negates all params found in `negatable_params`
...@@ -444,7 +445,6 @@ class IssuableFinder ...@@ -444,7 +445,6 @@ class IssuableFinder
items items
end end
# rubocop: disable CodeReuse/ActiveRecord
def by_state(items) def by_state(items)
case params[:state].to_s case params[:state].to_s
when 'closed' when 'closed'
...@@ -454,12 +454,11 @@ class IssuableFinder ...@@ -454,12 +454,11 @@ class IssuableFinder
when 'opened' when 'opened'
items.opened items.opened
when 'locked' when 'locked'
items.where(state: 'locked') items.with_state(:locked)
else else
items items
end end
end end
# rubocop: enable CodeReuse/ActiveRecord
def by_group(items) def by_group(items)
# Selection by group is already covered by `by_project` and `projects` # Selection by group is already covered by `by_project` and `projects`
......
...@@ -32,6 +32,13 @@ module Issuable ...@@ -32,6 +32,13 @@ module Issuable
DESCRIPTION_LENGTH_MAX = 1.megabyte DESCRIPTION_LENGTH_MAX = 1.megabyte
DESCRIPTION_HTML_LENGTH_MAX = 5.megabytes DESCRIPTION_HTML_LENGTH_MAX = 5.megabytes
STATE_ID_MAP = {
opened: 1,
closed: 2,
merged: 3,
locked: 4
}.with_indifferent_access.freeze
# This object is used to gather issuable meta data for displaying # This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests # upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance. # lists avoiding n+1 queries and improving performance.
...@@ -173,13 +180,17 @@ module Issuable ...@@ -173,13 +180,17 @@ module Issuable
fuzzy_search(query, [:title]) fuzzy_search(query, [:title])
end end
# Available state values persisted in state_id column using state machine def available_states
@available_states ||= STATE_ID_MAP.slice(*available_state_names)
end
# Available state names used to persist state_id column using state machine
# #
# Override this on subclasses if different states are needed # Override this on subclasses if different states are needed
# #
# Check MergeRequest.available_states for example # Check MergeRequest.available_states_names for example
def available_states def available_state_names
@available_states ||= { opened: 1, closed: 2 }.with_indifferent_access [:opened, :closed]
end end
# Searches for records with a matching title or description. # Searches for records with a matching title or description.
...@@ -298,6 +309,14 @@ module Issuable ...@@ -298,6 +309,14 @@ module Issuable
end end
end end
def state
self.class.available_states.key(state_id)
end
def state=(value)
self.state_id = self.class.available_states[value]
end
def resource_parent def resource_parent
project project
end end
......
...@@ -4,22 +4,20 @@ module IssuableStates ...@@ -4,22 +4,20 @@ module IssuableStates
extend ActiveSupport::Concern extend ActiveSupport::Concern
# The state:string column is being migrated to state_id:integer column # The state:string column is being migrated to state_id:integer column
# This is a temporary hook to populate state_id column with new values # This is a temporary hook to keep state column in sync until it is removed.
# and should be removed after the state column is removed. # Check https: https://gitlab.com/gitlab-org/gitlab/issues/33814 for more information
# Check https://gitlab.com/gitlab-org/gitlab-foss/issues/51789 for more information # The state column can be safely removed after 2019-10-27
included do included do
before_save :set_state_id before_save :sync_issuable_deprecated_state
end end
def set_state_id def sync_issuable_deprecated_state
return if state.nil? || state.empty? return if self.is_a?(Epic)
return unless respond_to?(:state)
return if state_id.nil?
# Needed to prevent breaking some migration specs that deprecated_state = self.class.available_states.key(state_id)
# rollback database to a point where state_id does not exist.
# We can use this guard clause for now since this file will
# be removed in the next release.
return unless self.has_attribute?(:state_id)
self.state_id = self.class.available_states[state] self.write_attribute(:state, deprecated_state)
end end
end end
...@@ -6,7 +6,9 @@ module Milestoneish ...@@ -6,7 +6,9 @@ module Milestoneish
end end
def closed_issues_count(user) def closed_issues_count(user)
count_issues_by_state(user)['closed'].to_i closed_state_id = Issue.available_states[:closed]
count_issues_by_state(user)[closed_state_id].to_i
end end
def complete?(user) def complete?(user)
...@@ -117,7 +119,7 @@ module Milestoneish ...@@ -117,7 +119,7 @@ module Milestoneish
def count_issues_by_state(user) def count_issues_by_state(user)
memoize_per_user(user, :count_issues_by_state) do memoize_per_user(user, :count_issues_by_state) do
issues_visible_to_user(user).reorder(nil).group(:state).count issues_visible_to_user(user).reorder(nil).group(:state_id).count
end end
end end
......
...@@ -71,7 +71,7 @@ class Issue < ApplicationRecord ...@@ -71,7 +71,7 @@ class Issue < ApplicationRecord
attr_spammable :title, spam_title: true attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true attr_spammable :description, spam_description: true
state_machine :state, initial: :opened do state_machine :state_id, initial: :opened do
event :close do event :close do
transition [:opened] => :closed transition [:opened] => :closed
end end
...@@ -80,8 +80,8 @@ class Issue < ApplicationRecord ...@@ -80,8 +80,8 @@ class Issue < ApplicationRecord
transition closed: :opened transition closed: :opened
end end
state :opened state :opened, value: Issue.available_states[:opened]
state :closed state :closed, value: Issue.available_states[:closed]
before_transition any => :closed do |issue| before_transition any => :closed do |issue|
issue.closed_at = issue.system_note_timestamp issue.closed_at = issue.system_note_timestamp
...@@ -93,6 +93,13 @@ class Issue < ApplicationRecord ...@@ -93,6 +93,13 @@ class Issue < ApplicationRecord
end end
end end
# Alias to state machine .with_state_id method
# This needs to be defined after the state machine block to avoid errors
class << self
alias_method :with_state, :with_state_id
alias_method :with_states, :with_state_ids
end
def self.relative_positioning_query_base(issue) def self.relative_positioning_query_base(issue)
in_projects(issue.parent_ids) in_projects(issue.parent_ids)
end end
......
...@@ -85,7 +85,13 @@ class MergeRequest < ApplicationRecord ...@@ -85,7 +85,13 @@ class MergeRequest < ApplicationRecord
# when creating new merge request # when creating new merge request
attr_accessor :can_be_created, :compare_commits, :diff_options, :compare attr_accessor :can_be_created, :compare_commits, :diff_options, :compare
state_machine :state, initial: :opened do # Keep states definition to be evaluated before the state_machine block to avoid spec failures.
# If this gets evaluated after, the `merged` and `locked` states which are overrided can be nil.
def self.available_state_names
super + [:merged, :locked]
end
state_machine :state_id, initial: :opened do
event :close do event :close do
transition [:opened] => :closed transition [:opened] => :closed
end end
...@@ -116,10 +122,17 @@ class MergeRequest < ApplicationRecord ...@@ -116,10 +122,17 @@ class MergeRequest < ApplicationRecord
end end
end end
state :opened state :opened, value: MergeRequest.available_states[:opened]
state :closed state :closed, value: MergeRequest.available_states[:closed]
state :merged state :merged, value: MergeRequest.available_states[:merged]
state :locked state :locked, value: MergeRequest.available_states[:locked]
end
# Alias to state machine .with_state_id method
# This needs to be defined after the state machine block to avoid errors
class << self
alias_method :with_state, :with_state_id
alias_method :with_states, :with_state_ids
end end
state_machine :merge_status, initial: :unchecked do state_machine :merge_status, initial: :unchecked do
...@@ -211,10 +224,6 @@ class MergeRequest < ApplicationRecord ...@@ -211,10 +224,6 @@ class MergeRequest < ApplicationRecord
'!' '!'
end end
def self.available_states
@available_states ||= super.merge(merged: 3, locked: 4)
end
# Returns the top 100 target branches # Returns the top 100 target branches
# #
# The returned value is a Array containing branch names # The returned value is a Array containing branch names
......
...@@ -83,7 +83,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -83,7 +83,7 @@ class MergeRequestDiff < ApplicationRecord
metrics_join = mr_diffs.join(mr_metrics).on(metrics_join_condition) metrics_join = mr_diffs.join(mr_metrics).on(metrics_join_condition)
condition = MergeRequest.arel_table[:state].eq(:merged) condition = MergeRequest.arel_table[:state_id].eq(MergeRequest.available_states[:merged])
.and(MergeRequest::Metrics.arel_table[:merged_at].lteq(before)) .and(MergeRequest::Metrics.arel_table[:merged_at].lteq(before))
.and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil)) .and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil))
...@@ -91,7 +91,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -91,7 +91,7 @@ class MergeRequestDiff < ApplicationRecord
end end
scope :old_closed_diffs, -> (before) do scope :old_closed_diffs, -> (before) do
condition = MergeRequest.arel_table[:state].eq(:closed) condition = MergeRequest.arel_table[:state_id].eq(MergeRequest.available_states[:closed])
.and(MergeRequest::Metrics.arel_table[:latest_closed_at].lteq(before)) .and(MergeRequest::Metrics.arel_table[:latest_closed_at].lteq(before))
joins(merge_request: :metrics).where(condition) joins(merge_request: :metrics).where(condition)
......
...@@ -161,7 +161,7 @@ class HipchatService < Service ...@@ -161,7 +161,7 @@ class HipchatService < Service
obj_attr = data[:object_attributes] obj_attr = data[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr) obj_attr = HashWithIndifferentAccess.new(obj_attr)
title = render_line(obj_attr[:title]) title = render_line(obj_attr[:title])
state = obj_attr[:state] state = Issue.available_states.key(obj_attr[:state_id])
issue_iid = obj_attr[:iid] issue_iid = obj_attr[:iid]
issue_url = obj_attr[:url] issue_url = obj_attr[:url]
description = obj_attr[:description] description = obj_attr[:description]
......
...@@ -54,7 +54,7 @@ class PushEvent < Event ...@@ -54,7 +54,7 @@ class PushEvent < Event
.select(1) .select(1)
.where('merge_requests.source_project_id = events.project_id') .where('merge_requests.source_project_id = events.project_id')
.where('merge_requests.source_branch = push_event_payloads.ref') .where('merge_requests.source_branch = push_event_payloads.ref')
.where(state: :opened) .with_state(:opened)
# For reasons unknown the use of #eager_load will result in the # For reasons unknown the use of #eager_load will result in the
# "push_event_payload" association not being set. Because of this we're # "push_event_payload" association not being set. Because of this we're
......
...@@ -33,6 +33,6 @@ class NamespacelessProjectDestroyWorker ...@@ -33,6 +33,6 @@ class NamespacelessProjectDestroyWorker
def unlink_fork(project) def unlink_fork(project)
merge_requests = project.forked_from_project.merge_requests.opened.from_project(project) merge_requests = project.forked_from_project.merge_requests.opened.from_project(project)
merge_requests.update_all(state: 'closed') merge_requests.update_all(state_id: MergeRequest.available_states[:closed])
end end
end end
...@@ -33,7 +33,7 @@ class StuckMergeJobsWorker ...@@ -33,7 +33,7 @@ class StuckMergeJobsWorker
def apply_current_state!(completed_jids, completed_ids) def apply_current_state!(completed_jids, completed_ids)
merge_requests = MergeRequest.where(id: completed_ids) merge_requests = MergeRequest.where(id: completed_ids)
merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged) merge_requests.where.not(merge_commit_sha: nil).update_all(state_id: MergeRequest.available_states[:merged])
merge_requests_to_reopen = merge_requests.where(merge_commit_sha: nil) merge_requests_to_reopen = merge_requests.where(merge_commit_sha: nil)
......
---
title: Deprecate usage of state column for issues and merge requests
merge_request: 18099
author:
type: changed
# frozen_string_literal: true
class AddIssuableStateIdIndexes < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
# Creates the same indexes that are currently using state:string column
# for issues and merge_requests tables
create_indexes_for_issues
create_indexes_for_merge_requests
end
def down
# Removes indexes for issues
remove_concurrent_index_by_name :issues, 'idx_issues_on_state_id'
remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_created_at_and_id_and_state_id'
remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_due_date_and_id_and_state_id'
remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_rel_position_and_state_id_and_id'
remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_updated_at_and_id_and_state_id'
# Removes indexes from merge_requests
remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_id_and_merge_jid'
remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_source_project_and_branch_state_opened'
remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_state_id_and_merge_status'
remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_target_project_id_and_iid_opened'
end
def create_indexes_for_issues
add_concurrent_index :issues, :state_id, name: 'idx_issues_on_state_id'
add_concurrent_index :issues,
[:project_id, :created_at, :id, :state_id],
name: 'idx_issues_on_project_id_and_created_at_and_id_and_state_id'
add_concurrent_index :issues,
[:project_id, :due_date, :id, :state_id],
where: 'due_date IS NOT NULL',
name: 'idx_issues_on_project_id_and_due_date_and_id_and_state_id'
add_concurrent_index :issues,
[:project_id, :relative_position, :state_id, :id],
order: { id: :desc },
name: 'idx_issues_on_project_id_and_rel_position_and_state_id_and_id'
add_concurrent_index :issues,
[:project_id, :updated_at, :id, :state_id],
name: 'idx_issues_on_project_id_and_updated_at_and_id_and_state_id'
end
def create_indexes_for_merge_requests
add_concurrent_index :merge_requests,
[:id, :merge_jid],
where: 'merge_jid IS NOT NULL and state_id = 4',
name: 'idx_merge_requests_on_id_and_merge_jid'
add_concurrent_index :merge_requests,
[:source_project_id, :source_branch],
where: 'state_id = 1',
name: 'idx_merge_requests_on_source_project_and_branch_state_opened'
add_concurrent_index :merge_requests,
[:state_id, :merge_status],
where: "state_id = 1 AND merge_status = 'can_be_merged'",
name: 'idx_merge_requests_on_state_id_and_merge_status'
add_concurrent_index :merge_requests,
[:target_project_id, :iid],
where: 'state_id = 1',
name: 'idx_merge_requests_on_target_project_id_and_iid_opened'
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddStateIdDefaultValue < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
change_column_default :issues, :state_id, 1
change_column_null :issues, :state_id, false
change_column_default :merge_requests, :state_id, 1
change_column_null :merge_requests, :state_id, false
end
def down
change_column_default :issues, :state_id, nil
change_column_null :issues, :state_id, true
change_column_default :merge_requests, :state_id, nil
change_column_null :merge_requests, :state_id, true
end
end
...@@ -1928,7 +1928,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do ...@@ -1928,7 +1928,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.boolean "discussion_locked" t.boolean "discussion_locked"
t.datetime_with_timezone "closed_at" t.datetime_with_timezone "closed_at"
t.integer "closed_by_id" t.integer "closed_by_id"
t.integer "state_id", limit: 2 t.integer "state_id", limit: 2, default: 1, null: false
t.integer "duplicated_to_id" t.integer "duplicated_to_id"
t.index ["author_id"], name: "index_issues_on_author_id" t.index ["author_id"], name: "index_issues_on_author_id"
t.index ["closed_by_id"], name: "index_issues_on_closed_by_id" t.index ["closed_by_id"], name: "index_issues_on_closed_by_id"
...@@ -1938,12 +1938,17 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do ...@@ -1938,12 +1938,17 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.index ["milestone_id"], name: "index_issues_on_milestone_id" t.index ["milestone_id"], name: "index_issues_on_milestone_id"
t.index ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)" t.index ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)"
t.index ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state" t.index ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state"
t.index ["project_id", "created_at", "id", "state_id"], name: "idx_issues_on_project_id_and_created_at_and_id_and_state_id"
t.index ["project_id", "due_date", "id", "state"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_partial", where: "(due_date IS NOT NULL)" t.index ["project_id", "due_date", "id", "state"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_partial", where: "(due_date IS NOT NULL)"
t.index ["project_id", "due_date", "id", "state_id"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_id", where: "(due_date IS NOT NULL)"
t.index ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true t.index ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true
t.index ["project_id", "relative_position", "state", "id"], name: "index_issues_on_project_id_and_rel_position_and_state_and_id", order: { id: :desc } t.index ["project_id", "relative_position", "state", "id"], name: "index_issues_on_project_id_and_rel_position_and_state_and_id", order: { id: :desc }
t.index ["project_id", "relative_position", "state_id", "id"], name: "idx_issues_on_project_id_and_rel_position_and_state_id_and_id", order: { id: :desc }
t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state" t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state"
t.index ["project_id", "updated_at", "id", "state_id"], name: "idx_issues_on_project_id_and_updated_at_and_id_and_state_id"
t.index ["relative_position"], name: "index_issues_on_relative_position" t.index ["relative_position"], name: "index_issues_on_relative_position"
t.index ["state"], name: "index_issues_on_state" t.index ["state"], name: "index_issues_on_state"
t.index ["state_id"], name: "idx_issues_on_state_id"
t.index ["title"], name: "index_issues_on_title_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["title"], name: "index_issues_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["updated_at"], name: "index_issues_on_updated_at" t.index ["updated_at"], name: "index_issues_on_updated_at"
t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)" t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)"
...@@ -2288,23 +2293,27 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do ...@@ -2288,23 +2293,27 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.boolean "discussion_locked" t.boolean "discussion_locked"
t.integer "latest_merge_request_diff_id" t.integer "latest_merge_request_diff_id"
t.boolean "allow_maintainer_to_push" t.boolean "allow_maintainer_to_push"
t.integer "state_id", limit: 2 t.integer "state_id", limit: 2, default: 1, null: false
t.string "rebase_jid" t.string "rebase_jid"
t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id" t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id"
t.index ["author_id"], name: "index_merge_requests_on_author_id" t.index ["author_id"], name: "index_merge_requests_on_author_id"
t.index ["created_at"], name: "index_merge_requests_on_created_at" t.index ["created_at"], name: "index_merge_requests_on_created_at"
t.index ["description"], name: "index_merge_requests_on_description_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["description"], name: "index_merge_requests_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id" t.index ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id"
t.index ["id", "merge_jid"], name: "idx_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND (state_id = 4))"
t.index ["id", "merge_jid"], name: "index_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND ((state)::text = 'locked'::text))" t.index ["id", "merge_jid"], name: "index_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND ((state)::text = 'locked'::text))"
t.index ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id" t.index ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id"
t.index ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)" t.index ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)"
t.index ["milestone_id"], name: "index_merge_requests_on_milestone_id" t.index ["milestone_id"], name: "index_merge_requests_on_milestone_id"
t.index ["source_branch"], name: "index_merge_requests_on_source_branch" t.index ["source_branch"], name: "index_merge_requests_on_source_branch"
t.index ["source_project_id", "source_branch"], name: "idx_merge_requests_on_source_project_and_branch_state_opened", where: "(state_id = 1)"
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)" t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)"
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch" t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch"
t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))" t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))"
t.index ["state_id", "merge_status"], name: "idx_merge_requests_on_state_id_and_merge_status", where: "((state_id = 1) AND ((merge_status)::text = 'can_be_merged'::text))"
t.index ["target_branch"], name: "index_merge_requests_on_target_branch" t.index ["target_branch"], name: "index_merge_requests_on_target_branch"
t.index ["target_project_id", "created_at"], name: "index_merge_requests_target_project_id_created_at" t.index ["target_project_id", "created_at"], name: "index_merge_requests_target_project_id_created_at"
t.index ["target_project_id", "iid"], name: "idx_merge_requests_on_target_project_id_and_iid_opened", where: "(state_id = 1)"
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)" t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)"
t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id" t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id"
......
...@@ -14,7 +14,7 @@ class ApprovalMergeRequestRule < ApplicationRecord ...@@ -14,7 +14,7 @@ class ApprovalMergeRequestRule < ApplicationRecord
) )
end end
scope :for_unmerged_merge_requests, -> (merge_requests = nil) do scope :for_unmerged_merge_requests, -> (merge_requests = nil) do
query = joins(:merge_request).where.not(merge_requests: { state: 'merged' }) query = joins(:merge_request).where.not(merge_requests: { state_id: MergeRequest.available_states[:merged] })
if merge_requests if merge_requests
query.where(merge_request_id: merge_requests) query.where(merge_request_id: merge_requests)
......
...@@ -15,7 +15,11 @@ module EE ...@@ -15,7 +15,11 @@ module EE
include RelativePositioning include RelativePositioning
include UsageStatistics include UsageStatistics
enum state_id: { opened: 1, closed: 2 } enum state_id: {
opened: ::Epic.available_states[:opened],
closed: ::Epic.available_states[:closed]
}
alias_attribute :state, :state_id alias_attribute :state, :state_id
belongs_to :closed_by, class_name: 'User' belongs_to :closed_by, class_name: 'User'
......
...@@ -159,7 +159,7 @@ module EE ...@@ -159,7 +159,7 @@ module EE
visible_mrs = object.visible_blocking_merge_requests(current_user) visible_mrs = object.visible_blocking_merge_requests(current_user)
visible_mrs_by_state = visible_mrs visible_mrs_by_state = visible_mrs
.map { |mr| represent_blocking_mr(mr) } .map { |mr| represent_blocking_mr(mr) }
.group_by { |blocking_mr| blocking_mr.object.state_name } .group_by { |blocking_mr| blocking_mr.object.state_id_name }
{ {
total_count: visible_mrs.count + hidden_blocking_count, total_count: visible_mrs.count + hidden_blocking_count,
......
...@@ -99,8 +99,16 @@ module Gitlab ...@@ -99,8 +99,16 @@ module Gitlab
@approver_group_ids ||= ApproverGroup.where(target_type: 'MergeRequest', target_id: id).joins(:group).pluck(distinct(:group_id)) @approver_group_ids ||= ApproverGroup.where(target_type: 'MergeRequest', target_id: id).joins(:group).pluck(distinct(:group_id))
end end
def merged_state_id
3
end
def closed_state_id
2
end
def sync_code_owners_with_approvers def sync_code_owners_with_approvers
return if state == 'merged' || state == 'closed' return if state_id == merged_state_id || state == closed_state_id
Gitlab::GitalyClient.allow_n_plus_1_calls do Gitlab::GitalyClient.allow_n_plus_1_calls do
gl_merge_request = ::MergeRequest.find(id) gl_merge_request = ::MergeRequest.find(id)
......
...@@ -25,7 +25,7 @@ describe Gitlab::HookData::IssueBuilder do ...@@ -25,7 +25,7 @@ describe Gitlab::HookData::IssueBuilder do
moved_to_id moved_to_id
project_id project_id
relative_position relative_position
state state_id
time_estimate time_estimate
title title
updated_at updated_at
......
...@@ -108,12 +108,12 @@ describe Issue, :elastic do ...@@ -108,12 +108,12 @@ describe Issue, :elastic do
'description', 'description',
'created_at', 'created_at',
'updated_at', 'updated_at',
'state',
'project_id', 'project_id',
'author_id', 'author_id',
'confidential' 'confidential'
).merge({ ).merge({
'type' => issue.es_type, 'type' => issue.es_type,
'state' => issue.state,
'join_field' => { 'join_field' => {
'name' => issue.es_type, 'name' => issue.es_type,
'parent' => issue.es_parent 'parent' => issue.es_parent
......
...@@ -131,7 +131,7 @@ describe MergeRequest do ...@@ -131,7 +131,7 @@ describe MergeRequest do
context 'MR is merged' do context 'MR is merged' do
before do before do
blocking_mr.update_columns(state: 'merged') blocking_mr.update_columns(state_id: described_class.available_states[:merged])
end end
it 'returns 0 by default' do it 'returns 0 by default' do
......
...@@ -271,7 +271,7 @@ describe MergeRequestWidgetEntity do ...@@ -271,7 +271,7 @@ describe MergeRequestWidgetEntity do
end end
it 'does not count a merged and hidden blocking MR' do it 'does not count a merged and hidden blocking MR' do
blocking_mr.update_columns(state: 'merged') blocking_mr.update_columns(state_id: MergeRequest.available_states[:merged])
is_expected.to eq( is_expected.to eq(
hidden_count: 0, hidden_count: 0,
......
...@@ -104,7 +104,7 @@ module Gitlab ...@@ -104,7 +104,7 @@ module Gitlab
iid: issue.iid, iid: issue.iid,
title: issue.title, title: issue.title,
description: description, description: description,
state: issue.state, state_id: Issue.available_states[issue.state],
author_id: gitlab_user_id(project, issue.author), author_id: gitlab_user_id(project, issue.author),
milestone: milestone, milestone: milestone,
created_at: issue.created_at, created_at: issue.created_at,
......
...@@ -117,7 +117,7 @@ module Gitlab ...@@ -117,7 +117,7 @@ module Gitlab
description: body, description: body,
author_id: project.creator_id, author_id: project.creator_id,
assignee_ids: [assignee_id], assignee_ids: [assignee_id],
state: raw_issue['state'] == 'closed' ? 'closed' : 'opened' state_id: raw_issue['state'] == 'closed' ? Issue.available_states[:closed] : Issue.available_states[:opened]
) )
issue_labels = ::LabelsFinder.new(nil, project_id: project.id, title: labels).execute(skip_authorization: true) issue_labels = ::LabelsFinder.new(nil, project_id: project.id, title: labels).execute(skip_authorization: true)
......
...@@ -27,7 +27,7 @@ module Gitlab ...@@ -27,7 +27,7 @@ module Gitlab
duplicated_to_id duplicated_to_id
project_id project_id
relative_position relative_position
state state_id
time_estimate time_estimate
title title
updated_at updated_at
...@@ -46,7 +46,8 @@ module Gitlab ...@@ -46,7 +46,8 @@ module Gitlab
human_time_estimate: issue.human_time_estimate, human_time_estimate: issue.human_time_estimate,
assignee_ids: issue.assignee_ids, assignee_ids: issue.assignee_ids,
assignee_id: issue.assignee_ids.first, # This key is deprecated assignee_id: issue.assignee_ids.first, # This key is deprecated
labels: issue.labels_hook_attrs labels: issue.labels_hook_attrs,
state: issue.state
} }
issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes) issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
......
...@@ -292,7 +292,11 @@ module Gitlab ...@@ -292,7 +292,11 @@ module Gitlab
existing_object existing_object
else else
relation_class.new(parsed_relation_hash) object = relation_class.new
# Use #assign_attributes here to call object custom setters
object.assign_attributes(parsed_relation_hash)
object
end end
end end
end end
......
...@@ -12,7 +12,7 @@ FactoryBot.define do ...@@ -12,7 +12,7 @@ FactoryBot.define do
end end
trait :opened do trait :opened do
state { :opened } state_id { Issue.available_states[:opened] }
end end
trait :locked do trait :locked do
...@@ -20,10 +20,14 @@ FactoryBot.define do ...@@ -20,10 +20,14 @@ FactoryBot.define do
end end
trait :closed do trait :closed do
state { :closed } state_id { Issue.available_states[:closed] }
closed_at { Time.now } closed_at { Time.now }
end end
after(:build) do |issue, evaluator|
issue.state_id = Issue.available_states[evaluator.state]
end
factory :closed_issue, traits: [:closed] factory :closed_issue, traits: [:closed]
factory :reopened_issue, traits: [:opened] factory :reopened_issue, traits: [:opened]
......
...@@ -40,7 +40,7 @@ FactoryBot.define do ...@@ -40,7 +40,7 @@ FactoryBot.define do
end end
trait :merged do trait :merged do
state { :merged } state_id { MergeRequest.available_states[:merged] }
end end
trait :merged_target do trait :merged_target do
...@@ -57,7 +57,7 @@ FactoryBot.define do ...@@ -57,7 +57,7 @@ FactoryBot.define do
end end
trait :closed do trait :closed do
state { :closed } state_id { MergeRequest.available_states[:closed] }
end end
trait :closed_last_month do trait :closed_last_month do
...@@ -69,7 +69,7 @@ FactoryBot.define do ...@@ -69,7 +69,7 @@ FactoryBot.define do
end end
trait :opened do trait :opened do
state { :opened } state_id { MergeRequest.available_states[:opened] }
end end
trait :invalid do trait :invalid do
...@@ -78,7 +78,7 @@ FactoryBot.define do ...@@ -78,7 +78,7 @@ FactoryBot.define do
end end
trait :locked do trait :locked do
state { :locked } state_id { MergeRequest.available_states[:locked] }
end end
trait :simple do trait :simple do
...@@ -186,6 +186,10 @@ FactoryBot.define do ...@@ -186,6 +186,10 @@ FactoryBot.define do
end end
end end
after(:build) do |merge_request, evaluator|
merge_request.state_id = MergeRequest.available_states[evaluator.state]
end
after(:create) do |merge_request, evaluator| after(:create) do |merge_request, evaluator|
merge_request.cache_merge_request_closes_issues! merge_request.cache_merge_request_closes_issues!
end end
......
...@@ -129,7 +129,7 @@ ...@@ -129,7 +129,7 @@
"updated_at": "2017-08-15T18:37:40.807Z", "updated_at": "2017-08-15T18:37:40.807Z",
"branch_name": null, "branch_name": null,
"description": "Quam totam fuga numquam in eveniet.", "description": "Quam totam fuga numquam in eveniet.",
"state": "opened", "state": "closed",
"iid": 2, "iid": 2,
"updated_by_id": 1, "updated_by_id": 1,
"confidential": false, "confidential": false,
......
...@@ -308,8 +308,8 @@ describe Gitlab::BitbucketImport::Importer do ...@@ -308,8 +308,8 @@ describe Gitlab::BitbucketImport::Importer do
importer.execute importer.execute
expect(project.issues.where(state: "closed").size).to eq(5) expect(project.issues.where(state_id: Issue.available_states[:closed]).size).to eq(5)
expect(project.issues.where(state: "opened").size).to eq(2) expect(project.issues.where(state_id: Issue.available_states[:opened]).size).to eq(2)
end end
describe 'wiki import' do describe 'wiki import' do
......
...@@ -26,7 +26,7 @@ describe Gitlab::HookData::IssueBuilder do ...@@ -26,7 +26,7 @@ describe Gitlab::HookData::IssueBuilder do
duplicated_to_id duplicated_to_id
project_id project_id
relative_position relative_position
state state_id
time_estimate time_estimate
title title
updated_at updated_at
...@@ -41,6 +41,7 @@ describe Gitlab::HookData::IssueBuilder do ...@@ -41,6 +41,7 @@ describe Gitlab::HookData::IssueBuilder do
expect(data).to include(:human_time_estimate) expect(data).to include(:human_time_estimate)
expect(data).to include(:human_total_time_spent) expect(data).to include(:human_total_time_spent)
expect(data).to include(:assignee_ids) expect(data).to include(:assignee_ids)
expect(data).to include(:state)
expect(data).to include('labels' => [label.hook_attrs]) expect(data).to include('labels' => [label.hook_attrs])
end end
......
...@@ -434,6 +434,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -434,6 +434,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
labels: 0, labels: 0,
milestones: 0, milestones: 0,
first_issue_labels: 1 first_issue_labels: 1
it 'restores issue states' do
expect(project.issues.with_state(:closed).count).to eq(1)
expect(project.issues.with_state(:opened).count).to eq(1)
end
end end
context 'with existing group models' do context 'with existing group models' do
......
...@@ -85,7 +85,7 @@ describe Gitlab::ImportExport::RelationFactory do ...@@ -85,7 +85,7 @@ describe Gitlab::ImportExport::RelationFactory do
class FooModel class FooModel
include ActiveModel::Model include ActiveModel::Model
def initialize(params) def initialize(params = {})
params.each { |key, value| send("#{key}=", value) } params.each { |key, value| send("#{key}=", value) }
end end
......
...@@ -138,7 +138,10 @@ describe Issue do ...@@ -138,7 +138,10 @@ describe Issue do
end end
it 'changes the state to closed' do it 'changes the state to closed' do
expect { issue.close }.to change { issue.state }.from('opened').to('closed') open_state = described_class.available_states[:opened]
closed_state = described_class.available_states[:closed]
expect { issue.close }.to change { issue.state_id }.from(open_state).to(closed_state)
end end
end end
...@@ -155,7 +158,7 @@ describe Issue do ...@@ -155,7 +158,7 @@ describe Issue do
end end
it 'changes the state to opened' do it 'changes the state to opened' do
expect { issue.reopen }.to change { issue.state }.from('closed').to('opened') expect { issue.reopen }.to change { issue.state_id }.from(described_class.available_states[:closed]).to(described_class.available_states[:opened])
end end
end end
......
...@@ -470,7 +470,7 @@ describe MergeRequest do ...@@ -470,7 +470,7 @@ describe MergeRequest do
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit]) allow(subject).to receive(:commits).and_return([commit])
allow(subject).to receive(:state).and_return("closed") allow(subject).to receive(:state_id).and_return(described_class.available_states[:closed])
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count) expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
end end
...@@ -479,7 +479,7 @@ describe MergeRequest do ...@@ -479,7 +479,7 @@ describe MergeRequest do
issue = create :issue, project: subject.project issue = create :issue, project: subject.project
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit]) allow(subject).to receive(:commits).and_return([commit])
allow(subject).to receive(:state).and_return("merged") allow(subject).to receive(:state_id).and_return(described_class.available_states[:merged])
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count) expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
end end
...@@ -2075,7 +2075,7 @@ describe MergeRequest do ...@@ -2075,7 +2075,7 @@ describe MergeRequest do
end end
it 'refuses to enqueue a job if the MR is not open' do it 'refuses to enqueue a job if the MR is not open' do
merge_request.update_column(:state, 'foo') merge_request.update_column(:state_id, 5)
expect(RebaseWorker).not_to receive(:perform_async) expect(RebaseWorker).not_to receive(:perform_async)
...@@ -2571,32 +2571,32 @@ describe MergeRequest do ...@@ -2571,32 +2571,32 @@ describe MergeRequest do
describe '#merge_ongoing?' do describe '#merge_ongoing?' do
it 'returns true when the merge request is locked' do it 'returns true when the merge request is locked' do
merge_request = build_stubbed(:merge_request, state: :locked) merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:locked])
expect(merge_request.merge_ongoing?).to be(true) expect(merge_request.merge_ongoing?).to be(true)
end end
it 'returns true when merge_id, MR is not merged and it has no running job' do it 'returns true when merge_id, MR is not merged and it has no running job' do
merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo') merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: 'foo')
allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { true } allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { true }
expect(merge_request.merge_ongoing?).to be(true) expect(merge_request.merge_ongoing?).to be(true)
end end
it 'returns false when merge_jid is nil' do it 'returns false when merge_jid is nil' do
merge_request = build_stubbed(:merge_request, state: :open, merge_jid: nil) merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: nil)
expect(merge_request.merge_ongoing?).to be(false) expect(merge_request.merge_ongoing?).to be(false)
end end
it 'returns false if MR is merged' do it 'returns false if MR is merged' do
merge_request = build_stubbed(:merge_request, state: :merged, merge_jid: 'foo') merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:merged], merge_jid: 'foo')
expect(merge_request.merge_ongoing?).to be(false) expect(merge_request.merge_ongoing?).to be(false)
end end
it 'returns false if there is no merge job running' do it 'returns false if there is no merge job running' do
merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo') merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: 'foo')
allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { false } allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { false }
expect(merge_request.merge_ongoing?).to be(false) expect(merge_request.merge_ongoing?).to be(false)
...@@ -2730,7 +2730,7 @@ describe MergeRequest do ...@@ -2730,7 +2730,7 @@ describe MergeRequest do
context 'closed MR' do context 'closed MR' do
before do before do
merge_request.update_attribute(:state, :closed) merge_request.update_attribute(:state_id, described_class.available_states[:closed])
end end
it 'is not mergeable' do it 'is not mergeable' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment