Skip to content
Snippets Groups Projects
Verified Commit 792c08e8 authored by Marc Saleiko's avatar Marc Saleiko Committed by GitLab
Browse files

Merge branch '504532-custom-fields-widget' into 'master'

Add custom field widget definition to all work item types

See merge request !176206



Merged-by: Marc Saleiko's avatarMarc Saleiko <msaleiko@gitlab.com>
Approved-by: default avatarDylan Griffith <dyl.griffith@gmail.com>
Approved-by: Marc Saleiko's avatarMarc Saleiko <msaleiko@gitlab.com>
Reviewed-by: Heinrich Lee Yu's avatarHeinrich Lee Yu <heinrich@gitlab.com>
Reviewed-by: Marc Saleiko's avatarMarc Saleiko <msaleiko@gitlab.com>
Co-authored-by: Heinrich Lee Yu's avatarHeinrich Lee Yu <heinrich@gitlab.com>
parents c3aaf0f8 d7f5cfca
No related branches found
No related tags found
3 merge requests!181325Fix ambiguous `created_at` in project.rb,!180187Draft: Update dashboard editing to save visualizations directly to the dashboard file,!176206Add custom field widget definition to all work item types
Pipeline #1649702136 passed
Showing
with 167 additions and 11 deletions
......@@ -262,6 +262,7 @@
"WorkItemWidgetColor",
"WorkItemWidgetCrmContacts",
"WorkItemWidgetCurrentUserTodos",
"WorkItemWidgetCustomFields",
"WorkItemWidgetCustomStatus",
"WorkItemWidgetDescription",
"WorkItemWidgetDesigns",
......
......@@ -43,7 +43,8 @@ class WidgetDefinition < ApplicationRecord
crm_contacts: 24,
email_participants: 25,
custom_status: 26,
linked_resources: 27
linked_resources: 27,
custom_fields: 28 # EE-only
}
attribute :widget_options, ::Gitlab::Database::Type::IndifferentJsonb.new
......
# frozen_string_literal: true
class AddCustomFieldsWidgetToWorkItemTypes < Gitlab::Database::Migration[2.2]
include Gitlab::Database::MigrationHelpers::WorkItems::Widgets
restrict_gitlab_migration gitlab_schema: :gitlab_main
disable_ddl_transaction!
milestone '17.9'
WORK_ITEM_TYPE_ENUM_VALUES = [
0, # issue
1, # incident
2, # test_case
3, # requirement
4, # task
5, # objective
6, # key_result
7, # epic
8 # ticket
]
WIDGETS = [
{
name: 'Custom fields',
widget_type: 28
}
]
def up
add_widget_definitions(type_enum_values: WORK_ITEM_TYPE_ENUM_VALUES, widgets: WIDGETS)
end
def down
remove_widget_definitions(type_enum_values: WORK_ITEM_TYPE_ENUM_VALUES, widgets: WIDGETS)
end
end
b43db797e03e591d7fbc4f734e2d5dc50cf1a1fc0e1bccd9100431f22396a072
\ No newline at end of file
......@@ -38616,6 +38616,16 @@ four standard [pagination arguments](#pagination-arguments):
| ---- | ---- | ----------- |
| <a id="workitemwidgetcurrentusertodoscurrentusertodosstate"></a>`state` | [`TodoStateEnum`](#todostateenum) | State of the to-do items. |
 
### `WorkItemWidgetCustomFields`
Represents a custom fields widget.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemwidgetcustomfieldstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
### `WorkItemWidgetCustomStatus`
 
Represents Custom Status widget.
......@@ -42781,6 +42791,7 @@ Type of a work item widget.
| <a id="workitemwidgettypecolor"></a>`COLOR` | Color widget. |
| <a id="workitemwidgettypecrm_contacts"></a>`CRM_CONTACTS` | Crm Contacts widget. |
| <a id="workitemwidgettypecurrent_user_todos"></a>`CURRENT_USER_TODOS` | Current User Todos widget. |
| <a id="workitemwidgettypecustom_fields"></a>`CUSTOM_FIELDS` | Custom Fields widget. |
| <a id="workitemwidgettypecustom_status"></a>`CUSTOM_STATUS` | Custom Status widget. |
| <a id="workitemwidgettypedescription"></a>`DESCRIPTION` | Description widget. |
| <a id="workitemwidgettypedesigns"></a>`DESIGNS` | Designs widget. |
......@@ -45148,6 +45159,7 @@ Implementations:
- [`WorkItemWidgetColor`](#workitemwidgetcolor)
- [`WorkItemWidgetCrmContacts`](#workitemwidgetcrmcontacts)
- [`WorkItemWidgetCurrentUserTodos`](#workitemwidgetcurrentusertodos)
- [`WorkItemWidgetCustomFields`](#workitemwidgetcustomfields)
- [`WorkItemWidgetCustomStatus`](#workitemwidgetcustomstatus)
- [`WorkItemWidgetDescription`](#workitemwidgetdescription)
- [`WorkItemWidgetDesigns`](#workitemwidgetdesigns)
......@@ -365,7 +365,7 @@ Refer to [merge request #158688](https://gitlab.com/gitlab-org/gitlab/-/merge_re
1. Assign the widget to the appropriate work item types, by:
- Adding it to the `WIDGETS_FOR_TYPE` hash in `lib/gitlab/database_importers/work_items/base_type_importer.rb`.
- Creating a migration in `db/migrate/<version>_add_<widget_name>_widget_to_work_item_types.rb`.
Refer to `db/migrate/20241127161525_add_designs_and_development_widgets_to_ticket_work_item_type.rb` for [the latest best practice](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174135/diffs#diff-content-94894df588ba8ac84a6ac5fbd86188f07053ba00).
Refer to `db/migrate/20250121163545_add_custom_fields_widget_to_work_item_types.rb` for [the latest best practice](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/176206/diffs#diff-content-b6944f559968654c39493bb9f786ee97f12fd370).
There is no need to use a post-migration, see [discussion on merge request 148119](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148119#note_1837432680).
See `lib/gitlab/database/migration_helpers/work_items/widgets.rb` if you want to learn more about the structure of the migration.
......
......@@ -24,7 +24,8 @@ def type_mappings
::WorkItems::Widgets::Progress => ::Types::WorkItems::Widgets::ProgressType,
::WorkItems::Widgets::RequirementLegacy => ::Types::WorkItems::Widgets::RequirementLegacyType,
::WorkItems::Widgets::TestReports => ::Types::WorkItems::Widgets::TestReportsType,
::WorkItems::Widgets::Color => ::Types::WorkItems::Widgets::ColorType
::WorkItems::Widgets::Color => ::Types::WorkItems::Widgets::ColorType,
::WorkItems::Widgets::CustomFields => ::Types::WorkItems::Widgets::CustomFieldsType
}.freeze
orphan_types(*type_mappings.values)
......
# frozen_string_literal: true
module Types
module WorkItems
module Widgets
# rubocop:disable Graphql/AuthorizeTypes -- already authorized in parent type
class CustomFieldsType < BaseObject
graphql_name 'WorkItemWidgetCustomFields'
description 'Represents a custom fields widget'
implements Types::WorkItems::WidgetInterface
end
# rubocop:enable Graphql/AuthorizeTypes
end
end
end
......@@ -96,6 +96,10 @@ def elastic_reference
::Search::Elastic::References::WorkItem.serialize(self)
end
def custom_field_values
[]
end
private
override :linked_work_items_query
......
......@@ -17,7 +17,8 @@ module Type
],
issuable_health_status: ::WorkItems::Widgets::HealthStatus,
okrs: ::WorkItems::Widgets::Progress,
epic_colors: ::WorkItems::Widgets::Color
epic_colors: ::WorkItems::Widgets::Color,
custom_fields: ::WorkItems::Widgets::CustomFields
}.freeze
LICENSED_TYPES = { epic: :epics, objective: :okrs, key_result: :okrs, requirement: :requirements }.freeze
......@@ -41,13 +42,17 @@ def allowed_group_level_types(resource_parent)
override :widgets
def widgets(resource_parent)
strong_memoize_with(:widgets, resource_parent) do
unlicensed_classes = unlicensed_widget_classes(resource_parent)
unavailable_widgets = unlicensed_widget_classes(resource_parent)
if epic? && !resource_parent.try(:work_items_beta_feature_flag_enabled?)
unlicensed_classes << ::WorkItems::Widgets::Assignees
unavailable_widgets << ::WorkItems::Widgets::Assignees
end
super.reject { |widget_def| unlicensed_classes.include?(widget_def.widget_class) }
if ::Feature.disabled?(:custom_fields_feature, resource_parent.root_ancestor)
unavailable_widgets << ::WorkItems::Widgets::CustomFields
end
super.reject { |widget_def| unavailable_widgets.include?(widget_def.widget_class) }
end
end
......
# frozen_string_literal: true
module WorkItems
module Widgets
class CustomFields < Base
delegate :custom_field_values, to: :work_item
end
end
end
# frozen_string_literal: true
module WorkItems
module DataSync
module Widgets
class CustomFields < Base
def after_save_commit
# copy custom field values if field exists in target namespace
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::WorkItems::Widgets::CustomFieldsType, feature_category: :team_planning do
let(:fields) do
%i[type]
end
specify { expect(described_class).to have_graphql_fields(fields) }
specify { expect(described_class.graphql_name).to eq('WorkItemWidgetCustomFields') }
end
......@@ -33,7 +33,8 @@
::WorkItems::Widgets::Development,
::WorkItems::Widgets::CrmContacts,
::WorkItems::Widgets::EmailParticipants,
::WorkItems::Widgets::CustomStatus
::WorkItems::Widgets::CustomStatus,
::WorkItems::Widgets::CustomFields
)
end
......
......@@ -62,6 +62,16 @@
end
end
end
context 'when custom_fields_feature is disabled' do
before do
stub_feature_flags(custom_fields_feature: false)
end
it 'does not return custom fields widget' do
expect(returned_widgets.map(&:widget_class)).not_to include(::WorkItems::Widgets::CustomFields)
end
end
end
where(feature_widget: WorkItems::Type::LICENSED_WIDGETS.transform_values { |v| Array(v) }.to_a)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::Widgets::CustomFields, feature_category: :team_planning do
let_it_be(:work_item) { create(:work_item, :issue) }
describe '#custom_field_values' do
subject { described_class.new(work_item).custom_field_values }
it { is_expected.to eq([]) }
end
end
......@@ -30,7 +30,8 @@ module BaseTypeImporter
development: 'Development',
crm_contacts: 'CRM contacts',
email_participants: 'Email participants',
custom_status: 'Custom status'
custom_status: 'Custom status',
custom_fields: 'Custom fields'
}.freeze
WIDGETS_FOR_TYPE = {
......@@ -39,6 +40,7 @@ module BaseTypeImporter
:award_emoji,
:crm_contacts,
:current_user_todos,
:custom_fields,
:description,
:designs,
:development,
......@@ -61,6 +63,7 @@ module BaseTypeImporter
:award_emoji,
:crm_contacts,
:current_user_todos,
:custom_fields,
:description,
:development,
:email_participants,
......@@ -78,6 +81,7 @@ module BaseTypeImporter
test_case: [
:award_emoji,
:current_user_todos,
:custom_fields,
:description,
:linked_items,
:notes,
......@@ -88,6 +92,7 @@ module BaseTypeImporter
requirement: [
:award_emoji,
:current_user_todos,
:custom_fields,
:description,
:linked_items,
:notes,
......@@ -103,6 +108,7 @@ module BaseTypeImporter
:award_emoji,
:crm_contacts,
:current_user_todos,
:custom_fields,
:description,
:development,
:hierarchy,
......@@ -122,6 +128,7 @@ module BaseTypeImporter
:assignees,
:award_emoji,
:current_user_todos,
:custom_fields,
:description,
:health_status,
:hierarchy,
......@@ -137,6 +144,7 @@ module BaseTypeImporter
:assignees,
:award_emoji,
:current_user_todos,
:custom_fields,
:description,
:health_status,
:hierarchy,
......@@ -154,6 +162,7 @@ module BaseTypeImporter
:color,
:crm_contacts,
:current_user_todos,
:custom_fields,
:description,
:health_status,
:hierarchy,
......@@ -172,6 +181,7 @@ module BaseTypeImporter
:award_emoji,
:crm_contacts,
:current_user_todos,
:custom_fields,
:description,
:designs,
:development,
......
......@@ -137,9 +137,10 @@
# 1 extra query per source (3 emojis + 2 notes) to fetch participables collection
# 2 extra queries to load work item widgets collection
# 1 extra query for root_ancestor in custom_fields_feature feature flag check
# 1 extra query to load the project creator to check if they are banned
# 1 extra query to load the invited groups to see if the user is banned from any of them
expect { query.call }.not_to exceed_query_limit(control_count).with_threshold(9)
expect { query.call }.not_to exceed_query_limit(control_count).with_threshold(10)
end
it 'does not execute N+1 for system note metadata relation' do
......
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddCustomFieldsWidgetToWorkItemTypes, :migration, feature_category: :team_planning do
it_behaves_like 'migration that adds widgets to a work item type'
end
......@@ -34,7 +34,8 @@
::WorkItems::Widgets::Progress,
::WorkItems::Widgets::RequirementLegacy,
::WorkItems::Widgets::TestReports,
::WorkItems::Widgets::Color
::WorkItems::Widgets::Color,
::WorkItems::Widgets::CustomFields
]
end
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment