Commit 2d631a95 authored by James Fargher's avatar James Fargher

Merge branch '346039-add-tast-to-work-item-types' into 'master'

Add Task work item type to the database

See merge request gitlab-org/gitlab!75447
parents c44c1185 b003d23b
...@@ -5,7 +5,7 @@ module Types ...@@ -5,7 +5,7 @@ module Types
graphql_name 'IssueType' graphql_name 'IssueType'
description 'Issue type' description 'Issue type'
::WorkItem::Type.base_types.keys.each do |issue_type| ::WorkItem::Type.allowed_types_for_issues.each do |issue_type|
value issue_type.upcase, value: issue_type, description: "#{issue_type.titleize} issue type" value issue_type.upcase, value: issue_type, description: "#{issue_type.titleize} issue type"
end end
end end
......
...@@ -15,7 +15,8 @@ class WorkItem::Type < ApplicationRecord ...@@ -15,7 +15,8 @@ class WorkItem::Type < ApplicationRecord
issue: { name: 'Issue', icon_name: 'issue-type-issue', enum_value: 0 }, issue: { name: 'Issue', icon_name: 'issue-type-issue', enum_value: 0 },
incident: { name: 'Incident', icon_name: 'issue-type-incident', enum_value: 1 }, incident: { name: 'Incident', icon_name: 'issue-type-incident', enum_value: 1 },
test_case: { name: 'Test Case', icon_name: 'issue-type-test-case', enum_value: 2 }, ## EE-only test_case: { name: 'Test Case', icon_name: 'issue-type-test-case', enum_value: 2 }, ## EE-only
requirement: { name: 'Requirement', icon_name: 'issue-type-requirements', enum_value: 3 } ## EE-only requirement: { name: 'Requirement', icon_name: 'issue-type-requirements', enum_value: 3 }, ## EE-only
task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 }
}.freeze }.freeze
cache_markdown_field :description, pipeline: :single_line cache_markdown_field :description, pipeline: :single_line
...@@ -42,6 +43,10 @@ class WorkItem::Type < ApplicationRecord ...@@ -42,6 +43,10 @@ class WorkItem::Type < ApplicationRecord
default_by_type(:issue) default_by_type(:issue)
end end
def self.allowed_types_for_issues
base_types.keys.excluding('task')
end
private private
def strip_whitespace def strip_whitespace
......
# frozen_string_literal: true
class AddTaskToWorkItemTypes < Gitlab::Database::Migration[1.0]
TASK_ENUM_VALUE = 4
class WorkItemType < ActiveRecord::Base
self.inheritance_column = :_type_disabled
self.table_name = 'work_item_types'
validates :name, uniqueness: { case_sensitive: false, scope: [:namespace_id] }
end
def up
# New instances will not run this migration and add this type via fixtures
# checking if record exists mostly because migration specs will run all migrations
# and that will conflict with the preloaded base work item types
task_work_item = WorkItemType.find_by(name: 'Task', namespace_id: nil)
if task_work_item
say('Task item record exist, skipping creation')
else
WorkItemType.create(name: 'Task', namespace_id: nil, base_type: TASK_ENUM_VALUE, icon_name: 'issue-type-task')
end
end
def down
# There's the remote possibility that issues could already be
# using this issue type, with a tight foreign constraint.
# Therefore we will not attempt to remove any data.
end
end
e31592bbeb6ba6175f19cfceaafb37672633028dd021052542909999b46eac38
\ No newline at end of file
...@@ -491,6 +491,15 @@ RSpec.describe API::Issues, :mailer do ...@@ -491,6 +491,15 @@ RSpec.describe API::Issues, :mailer do
expect(Issue.last.work_item_type.base_type).to eq('issue') expect(Issue.last.work_item_type.base_type).to eq('issue')
end end
it 'does not allow the creation of an issue of type task' do
expect do
post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', issue_type: 'task' }
end.to not_change(Issue, :count)
expect(response).to have_gitlab_http_status(:bad_request)
end
it_behaves_like 'with epic parameter' do it_behaves_like 'with epic parameter' do
let(:request) { post api("/projects/#{target_project.id}/issues", user), params: params } let(:request) { post api("/projects/#{target_project.id}/issues", user), params: params }
end end
......
...@@ -56,11 +56,11 @@ RSpec.describe Issues::BuildService do ...@@ -56,11 +56,11 @@ RSpec.describe Issues::BuildService do
end end
context 'as developer' do context 'as developer' do
WorkItem::Type.base_types.each_key do |issue_type| WorkItem::Type.allowed_types_for_issues do |issue_type|
it "sets the issue type to #{issue_type}" do it "sets the issue type to #{issue_type}" do
issue = build_issue(issue_type: issue_type) issue = build_issue(issue_type: issue_type)
expect(issue.issue_type).to eq(issue_type.to_s) expect(issue.issue_type).to eq(issue_type)
end end
end end
end end
......
...@@ -23,7 +23,7 @@ module API ...@@ -23,7 +23,7 @@ module API
expose :issue_type, expose :issue_type,
as: :type, as: :type,
format_with: :upcase, format_with: :upcase,
documentation: { type: "String", desc: "One of #{::WorkItem::Type.base_types.keys.map(&:upcase)}" } documentation: { type: "String", desc: "One of #{::WorkItem::Type.allowed_types_for_issues.map(&:upcase)}" }
expose :assignee, using: ::API::Entities::UserBasic do |issue| expose :assignee, using: ::API::Entities::UserBasic do |issue|
issue.assignees.first issue.assignees.first
......
...@@ -82,7 +82,7 @@ module API ...@@ -82,7 +82,7 @@ module API
desc: 'Return issues sorted in `asc` or `desc` order.' desc: 'Return issues sorted in `asc` or `desc` order.'
optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '', optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '',
desc: 'Return issues that have no due date (`0`), or whose due date is this week, this month, between two weeks ago and next month, or which are overdue. Accepts: `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`, `0`' desc: 'Return issues that have no due date (`0`), or whose due date is this week, this month, between two weeks ago and next month, or which are overdue. Accepts: `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`, `0`'
optional :issue_type, type: String, values: WorkItem::Type.base_types.keys, desc: "The type of the issue. Accepts: #{WorkItem::Type.base_types.keys.join(', ')}" optional :issue_type, type: String, values: WorkItem::Type.allowed_types_for_issues, desc: "The type of the issue. Accepts: #{WorkItem::Type.allowed_types_for_issues.join(', ')}"
use :issues_stats_params use :issues_stats_params
use :pagination use :pagination
...@@ -99,7 +99,7 @@ module API ...@@ -99,7 +99,7 @@ module API
optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY' optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked" optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked"
optional :issue_type, type: String, values: WorkItem::Type.base_types.keys, desc: "The type of the issue. Accepts: #{WorkItem::Type.base_types.keys.join(', ')}" optional :issue_type, type: String, values: WorkItem::Type.allowed_types_for_issues, desc: "The type of the issue. Accepts: #{WorkItem::Type.allowed_types_for_issues.join(', ')}"
use :optional_issue_params_ee use :optional_issue_params_ee
end end
......
...@@ -1197,6 +1197,15 @@ RSpec.describe Projects::IssuesController do ...@@ -1197,6 +1197,15 @@ RSpec.describe Projects::IssuesController do
end end
end end
context 'when trying to create a task' do
it 'defaults to issue type' do
issue = post_new_issue(issue_type: 'task')
expect(issue.issue_type).to eq('issue')
expect(issue.work_item_type.base_type).to eq('issue')
end
end
it 'creates the issue successfully', :aggregate_failures do it 'creates the issue successfully', :aggregate_failures do
issue = post_new_issue issue = post_new_issue
......
...@@ -5,9 +5,9 @@ require 'spec_helper' ...@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe Types::IssueTypeEnum do RSpec.describe Types::IssueTypeEnum do
specify { expect(described_class.graphql_name).to eq('IssueType') } specify { expect(described_class.graphql_name).to eq('IssueType') }
it 'exposes all the existing issue type values' do it 'exposes all the existing issue type values except for task' do
expect(described_class.values.keys).to include( expect(described_class.values.keys).to match_array(
*%w[ISSUE INCIDENT] %w[ISSUE INCIDENT TEST_CASE REQUIREMENT]
) )
end end
end end
...@@ -4,18 +4,28 @@ require 'spec_helper' ...@@ -4,18 +4,28 @@ require 'spec_helper'
require_migration! require_migration!
RSpec.describe CreateBaseWorkItemTypes, :migration do RSpec.describe CreateBaseWorkItemTypes, :migration do
let!(:work_item_types) { table(:work_item_types) } include MigrationHelpers::WorkItemTypesHelper
let_it_be(:work_item_types) { table(:work_item_types) }
let(:base_types) do
{
issue: 0,
incident: 1,
test_case: 2,
requirement: 3
}
end
after(:all) do after(:all) do
# Make sure base types are recreated after running the migration # Make sure base types are recreated after running the migration
# because migration specs are not run in a transaction # because migration specs are not run in a transaction
WorkItem::Type.delete_all reset_work_item_types
Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.import
end end
it 'creates default data' do it 'creates default data' do
# Need to delete all as base types are seeded before entire test suite # Need to delete all as base types are seeded before entire test suite
WorkItem::Type.delete_all work_item_types.delete_all
reversible_migration do |migration| reversible_migration do |migration|
migration.before -> { migration.before -> {
...@@ -24,8 +34,8 @@ RSpec.describe CreateBaseWorkItemTypes, :migration do ...@@ -24,8 +34,8 @@ RSpec.describe CreateBaseWorkItemTypes, :migration do
} }
migration.after -> { migration.after -> {
expect(work_item_types.count).to eq 4 expect(work_item_types.count).to eq(4)
expect(work_item_types.all.pluck(:base_type)).to match_array WorkItem::Type.base_types.values expect(work_item_types.all.pluck(:base_type)).to match_array(base_types.values)
} }
end end
end end
......
...@@ -4,19 +4,29 @@ require 'spec_helper' ...@@ -4,19 +4,29 @@ require 'spec_helper'
require_migration! require_migration!
RSpec.describe UpsertBaseWorkItemTypes, :migration do RSpec.describe UpsertBaseWorkItemTypes, :migration do
let!(:work_item_types) { table(:work_item_types) } include MigrationHelpers::WorkItemTypesHelper
let_it_be(:work_item_types) { table(:work_item_types) }
let(:base_types) do
{
issue: 0,
incident: 1,
test_case: 2,
requirement: 3
}
end
after(:all) do after(:all) do
# Make sure base types are recreated after running the migration # Make sure base types are recreated after running the migration
# because migration specs are not run in a transaction # because migration specs are not run in a transaction
WorkItem::Type.delete_all reset_work_item_types
Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.import
end end
context 'when no default types exist' do context 'when no default types exist' do
it 'creates default data' do it 'creates default data' do
# Need to delete all as base types are seeded before entire test suite # Need to delete all as base types are seeded before entire test suite
WorkItem::Type.delete_all work_item_types.delete_all
expect(work_item_types.count).to eq(0) expect(work_item_types.count).to eq(0)
...@@ -29,7 +39,7 @@ RSpec.describe UpsertBaseWorkItemTypes, :migration do ...@@ -29,7 +39,7 @@ RSpec.describe UpsertBaseWorkItemTypes, :migration do
migration.after -> { migration.after -> {
expect(work_item_types.count).to eq(4) expect(work_item_types.count).to eq(4)
expect(work_item_types.all.pluck(:base_type)).to match_array(WorkItem::Type.base_types.values) expect(work_item_types.all.pluck(:base_type)).to match_array(base_types.values)
} }
end end
end end
...@@ -37,16 +47,21 @@ RSpec.describe UpsertBaseWorkItemTypes, :migration do ...@@ -37,16 +47,21 @@ RSpec.describe UpsertBaseWorkItemTypes, :migration do
context 'when default types already exist' do context 'when default types already exist' do
it 'does not create default types again' do it 'does not create default types again' do
expect(work_item_types.all.pluck(:base_type)).to match_array(WorkItem::Type.base_types.values) # Database needs to be in a similar state as when this migration was created
work_item_types.delete_all
work_item_types.find_or_create_by!(name: 'Issue', namespace_id: nil, base_type: base_types[:issue], icon_name: 'issue-type-issue')
work_item_types.find_or_create_by!(name: 'Incident', namespace_id: nil, base_type: base_types[:incident], icon_name: 'issue-type-incident')
work_item_types.find_or_create_by!(name: 'Test Case', namespace_id: nil, base_type: base_types[:test_case], icon_name: 'issue-type-test-case')
work_item_types.find_or_create_by!(name: 'Requirement', namespace_id: nil, base_type: base_types[:requirement], icon_name: 'issue-type-requirements')
reversible_migration do |migration| reversible_migration do |migration|
migration.before -> { migration.before -> {
expect(work_item_types.all.pluck(:base_type)).to match_array(WorkItem::Type.base_types.values) expect(work_item_types.all.pluck(:base_type)).to match_array(base_types.values)
} }
migration.after -> { migration.after -> {
expect(work_item_types.count).to eq(4) expect(work_item_types.count).to eq(4)
expect(work_item_types.all.pluck(:base_type)).to match_array(WorkItem::Type.base_types.values) expect(work_item_types.all.pluck(:base_type)).to match_array(base_types.values)
} }
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddTaskToWorkItemTypes, :migration do
include MigrationHelpers::WorkItemTypesHelper
let_it_be(:work_item_types) { table(:work_item_types) }
let(:base_types) do
{
issue: 0,
incident: 1,
test_case: 2,
requirement: 3,
task: 4
}
end
after(:all) do
# Make sure base types are recreated after running the migration
# because migration specs are not run in a transaction
reset_work_item_types
end
it 'skips creating the record if it already exists' do
reset_db_state_prior_to_migration
work_item_types.find_or_create_by!(name: 'Task', namespace_id: nil, base_type: base_types[:task], icon_name: 'issue-type-task')
expect do
migrate!
end.to not_change(work_item_types, :count)
end
it 'adds task to base work item types' do
reset_db_state_prior_to_migration
expect do
migrate!
end.to change(work_item_types, :count).from(4).to(5)
expect(work_item_types.all.pluck(:base_type)).to include(base_types[:task])
end
def reset_db_state_prior_to_migration
# Database needs to be in a similar state as when this migration was created
work_item_types.delete_all
work_item_types.find_or_create_by!(name: 'Issue', namespace_id: nil, base_type: base_types[:issue], icon_name: 'issue-type-issue')
work_item_types.find_or_create_by!(name: 'Incident', namespace_id: nil, base_type: base_types[:incident], icon_name: 'issue-type-incident')
work_item_types.find_or_create_by!(name: 'Test Case', namespace_id: nil, base_type: base_types[:test_case], icon_name: 'issue-type-test-case')
work_item_types.find_or_create_by!(name: 'Requirement', namespace_id: nil, base_type: base_types[:requirement], icon_name: 'issue-type-requirements')
end
end
...@@ -19,10 +19,10 @@ RSpec.describe WorkItem::Type do ...@@ -19,10 +19,10 @@ RSpec.describe WorkItem::Type do
it 'deletes type but not unrelated issues' do it 'deletes type but not unrelated issues' do
type = create(:work_item_type) type = create(:work_item_type)
expect(WorkItem::Type.count).to eq(5) expect(WorkItem::Type.count).to eq(6)
expect { type.destroy! }.not_to change(Issue, :count) expect { type.destroy! }.not_to change(Issue, :count)
expect(WorkItem::Type.count).to eq(4) expect(WorkItem::Type.count).to eq(5)
end end
end end
......
# frozen_string_literal: true
module MigrationHelpers
module WorkItemTypesHelper
DEFAULT_WORK_ITEM_TYPES = {
issue: { name: 'Issue', icon_name: 'issue-type-issue', enum_value: 0 },
incident: { name: 'Incident', icon_name: 'issue-type-incident', enum_value: 1 },
test_case: { name: 'Test Case', icon_name: 'issue-type-test-case', enum_value: 2 },
requirement: { name: 'Requirement', icon_name: 'issue-type-requirements', enum_value: 3 },
task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 }
}.freeze
def reset_work_item_types
work_item_types_table.delete_all
DEFAULT_WORK_ITEM_TYPES.each do |type, attributes|
work_item_types_table.create!(base_type: attributes[:enum_value], **attributes.slice(:name, :icon_name))
end
end
private
def work_item_types_table
table(:work_item_types)
end
end
end
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