diff --git a/app/graphql/resolvers/work_items/widgets/custom_status_resolver.rb b/app/graphql/resolvers/work_items/widgets/custom_status_resolver.rb index 24ac11c39f71920068f28489923537f83d020fa0..ece6cf242583cc91370b8b6e5410a98af066742e 100644 --- a/app/graphql/resolvers/work_items/widgets/custom_status_resolver.rb +++ b/app/graphql/resolvers/work_items/widgets/custom_status_resolver.rb @@ -7,10 +7,11 @@ class CustomStatusResolver < BaseResolver type ::Types::WorkItems::Widgets::CustomStatusType.connection_type, null: true def resolve - # Implement during https://gitlab.com/gitlab-org/gitlab/-/issues/498393 - [::WorkItems::Widgets::CustomStatus.new(nil), ::WorkItems::Widgets::CustomStatus.new(nil)] + [] end end end end end + +Resolvers::WorkItems::Widgets::CustomStatusResolver.prepend_mod diff --git a/app/graphql/types/work_items/widgets/custom_status_type.rb b/app/graphql/types/work_items/widgets/custom_status_type.rb index 4e04259020d3e868891e1f72242ba2e58e589b8b..7a53a45c8504980ce6292bab4387d7d2cc155f38 100644 --- a/app/graphql/types/work_items/widgets/custom_status_type.rb +++ b/app/graphql/types/work_items/widgets/custom_status_type.rb @@ -12,22 +12,30 @@ class CustomStatusType < BaseObject implements ::Types::WorkItems::WidgetInterface - # TODO change the ID to CustomStatus model ID while implementing - # https://gitlab.com/gitlab-org/gitlab/-/issues/498393 - field :id, ::Types::GlobalIDType[::WorkItems::Widgets::CustomStatus], - null: false, + field :id, Types::GlobalIDType, + null: true, experiment: { milestone: '17.8' }, - description: 'ID of the Custom Status.' + description: 'ID of the custom status.' field :name, GraphQL::Types::String, null: true, experiment: { milestone: '17.8' }, - description: 'Name of the Custom Status.' + description: 'Name of the custom status.' field :icon_name, GraphQL::Types::String, null: true, experiment: { milestone: '17.8' }, - description: 'Icon name of the Custom Status.' + description: 'Icon name of the custom status.' + + field :color, GraphQL::Types::String, + null: true, + experiment: { milestone: '17.10' }, + description: 'Color of the custom status.' + + field :position, GraphQL::Types::Int, + null: true, + experiment: { milestone: '17.10' }, + description: 'Position of the custom status within its category.' end # rubocop:enable Graphql/AuthorizeTypes end diff --git a/app/models/group.rb b/app/models/group.rb index 9c04bf0277d3850078bbf8508ef4cbc7b18f16bd..76c312b5fe4b5d8e8b667f41d459749050919323 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1004,6 +1004,11 @@ def work_items_alpha_feature_flag_enabled? feature_flag_enabled_for_self_or_ancestor?(:work_items_alpha) end + def work_item_status_feature_available? + feature_flag_enabled_for_self_or_ancestor?(:work_item_status, type: :wip) && + licensed_feature_available?(:work_item_custom_status) + end + def continue_indented_text_feature_flag_enabled? feature_flag_enabled_for_self_or_ancestor?(:continue_indented_text, type: :wip) end diff --git a/app/models/project.rb b/app/models/project.rb index bd055a8a4105a2c9eac924c8db6bc18f28766fcd..01a8a8f48c6d403f52a80a7fe0e8ded4c7869959 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -3313,6 +3313,11 @@ def work_items_alpha_feature_flag_enabled? group&.work_items_alpha_feature_flag_enabled? || Feature.enabled?(:work_items_alpha) end + def work_item_status_feature_available? + (group&.work_item_status_feature_available? || Feature.enabled?(:work_item_status, type: :wip)) && + licensed_feature_available?(:work_item_custom_status) + end + def glql_integration_feature_flag_enabled? group&.glql_integration_feature_flag_enabled? || Feature.enabled?(:glql_integration, self) end diff --git a/config/feature_flags/wip/work_item_status.yml b/config/feature_flags/wip/work_item_status.yml new file mode 100644 index 0000000000000000000000000000000000000000..863454fc2957ccb1fec0750bdca2935b31e4c34a --- /dev/null +++ b/config/feature_flags/wip/work_item_status.yml @@ -0,0 +1,9 @@ +--- +name: work_item_status +feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/5099 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182225 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/521286 +milestone: '17.10' +group: group::project management +type: wip +default_enabled: false diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 300523bb5d4a1245ba30bc2ec777282892700978..fdc72b5b92142a96debdc69f0d243168171b9668 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -39901,9 +39901,11 @@ Represents Custom Status widget. | Name | Type | Description | | ---- | ---- | ----------- | -| <a id="workitemwidgetcustomstatusiconname"></a>`iconName` {{< icon name="warning-solid" >}} | [`String`](#string) | **Introduced** in GitLab 17.8. **Status**: Experiment. Icon name of the Custom Status. | -| <a id="workitemwidgetcustomstatusid"></a>`id` {{< icon name="warning-solid" >}} | [`WorkItemsWidgetsCustomStatusID!`](#workitemswidgetscustomstatusid) | **Introduced** in GitLab 17.8. **Status**: Experiment. ID of the Custom Status. | -| <a id="workitemwidgetcustomstatusname"></a>`name` {{< icon name="warning-solid" >}} | [`String`](#string) | **Introduced** in GitLab 17.8. **Status**: Experiment. Name of the Custom Status. | +| <a id="workitemwidgetcustomstatuscolor"></a>`color` {{< icon name="warning-solid" >}} | [`String`](#string) | **Introduced** in GitLab 17.10. **Status**: Experiment. Color of the custom status. | +| <a id="workitemwidgetcustomstatusiconname"></a>`iconName` {{< icon name="warning-solid" >}} | [`String`](#string) | **Introduced** in GitLab 17.8. **Status**: Experiment. Icon name of the custom status. | +| <a id="workitemwidgetcustomstatusid"></a>`id` {{< icon name="warning-solid" >}} | [`GlobalID`](#globalid) | **Introduced** in GitLab 17.8. **Status**: Experiment. ID of the custom status. | +| <a id="workitemwidgetcustomstatusname"></a>`name` {{< icon name="warning-solid" >}} | [`String`](#string) | **Introduced** in GitLab 17.8. **Status**: Experiment. Name of the custom status. | +| <a id="workitemwidgetcustomstatusposition"></a>`position` {{< icon name="warning-solid" >}} | [`Int`](#int) | **Introduced** in GitLab 17.10. **Status**: Experiment. Position of the custom status within its category. | | <a id="workitemwidgetcustomstatustype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | ### `WorkItemWidgetDefinitionAssignees` @@ -45393,12 +45395,6 @@ A `WorkItemsTypeID` is a global ID. It is encoded as a string. An example `WorkItemsTypeID` is: `"gid://gitlab/WorkItems::Type/1"`. -### `WorkItemsWidgetsCustomStatusID` - -A `WorkItemsWidgetsCustomStatusID` is a global ID. It is encoded as a string. - -An example `WorkItemsWidgetsCustomStatusID` is: `"gid://gitlab/WorkItems::Widgets::CustomStatus/1"`. - ## Abstract types Abstract types (unions and interfaces) are ways the schema can represent diff --git a/ee/app/graphql/ee/resolvers/work_items/widgets/custom_status_resolver.rb b/ee/app/graphql/ee/resolvers/work_items/widgets/custom_status_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..2142fd2e28b7bdab27bf260b27da9f0a370ffc35 --- /dev/null +++ b/ee/app/graphql/ee/resolvers/work_items/widgets/custom_status_resolver.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module EE + module Resolvers + module WorkItems + module Widgets + module CustomStatusResolver + extend ::Gitlab::Utils::Override + + override :resolve + def resolve + return [] unless work_item_status_feature_available? + + # As part of iteration 1, we only support system defined statuses + # Custom lifecycle based on namespace will be supported in iteration 2 + lifecycle_for(object.work_item_type)&.statuses || [] + end + + private + + def work_item_status_feature_available? + root_ancestor&.try(:work_item_status_feature_available?) + end + + def root_ancestor + context[:resource_parent]&.root_ancestor + end + + def lifecycle_for(work_item_type) + base_type = work_item_type.base_type.to_sym + ::WorkItems::Statuses::SystemDefined::Lifecycle.of_work_item_base_type(base_type) + end + end + end + end + end +end diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb index 89316ee19eaf8ff934ddfe8239346219faaa2c3f..9c5ae2ed14dd24edb5c9b0c1e07bbf2e263eb010 100644 --- a/ee/app/models/gitlab_subscriptions/features.rb +++ b/ee/app/models/gitlab_subscriptions/features.rb @@ -187,6 +187,7 @@ class Features default_roles_assignees ci_component_usages_in_projects branch_rule_squash_options + work_item_custom_status ].freeze ULTIMATE_FEATURES = %i[ diff --git a/ee/spec/graphql/resolvers/work_items/widgets/custom_status_resolver_spec.rb b/ee/spec/graphql/resolvers/work_items/widgets/custom_status_resolver_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..bdaeb9e9c73bc48be3d535b191c7d80a669c3107 --- /dev/null +++ b/ee/spec/graphql/resolvers/work_items/widgets/custom_status_resolver_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::WorkItems::Widgets::CustomStatusResolver, feature_category: :team_planning do + include GraphqlHelpers + + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:current_user) { create(:user) } + let_it_be(:task_type) { create(:work_item_type, :task) } + let_it_be(:widget_definition) do + create(:widget_definition, widget_type: :custom_status, work_item_type: task_type, name: 'TesT Widget') + end + + shared_examples 'returns system defined statuses' do + it 'fetches allowed custom statuses for a given work item type' do + system_defined_statuses = ::WorkItems::Statuses::SystemDefined::Status.all + + expect(resolve_custom_statuses.items.map(&:name)).to eq(system_defined_statuses.map(&:name)) + end + end + + shared_examples 'does not return system defined statuses' do + it 'returns an empty array' do + expect(resolve_custom_statuses&.items).to eq([]) + end + end + + describe '#resolve' do + let(:resource_parent) { group } + + before do + stub_licensed_features(work_item_custom_status: true) + end + + context 'with group' do + it_behaves_like 'returns system defined statuses' + end + + context 'with project' do + let(:resource_parent) { project } + + it_behaves_like 'returns system defined statuses' + end + + context 'with unsupported namespace' do + let(:resource_parent) { current_user.namespace } + + it_behaves_like 'does not return system defined statuses' + end + + context 'when work item type is not supported' do + let_it_be(:epic_type) { create(:work_item_type, :epic) } + let_it_be(:widget_definition) do + create(:widget_definition, widget_type: :custom_status, work_item_type: epic_type, name: 'TesT Widget') + end + + it_behaves_like 'does not return system defined statuses' + end + + context 'with work_item_status feature flag disabled' do + before do + stub_feature_flags(work_item_status: false) + end + + it_behaves_like 'does not return system defined statuses' + end + + context 'with work_item_status licensed feature disabled' do + before do + stub_licensed_features(work_item_custom_status: false) + end + + it_behaves_like 'does not return system defined statuses' + end + end + + def resolve_custom_statuses(args = {}, context = { current_user: current_user, resource_parent: resource_parent }) + resolve(described_class, obj: widget_definition, args: args, ctx: context) + end +end diff --git a/ee/spec/graphql/types/work_items/widgets/custom_status_type_spec.rb b/ee/spec/graphql/types/work_items/widgets/custom_status_type_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..6d5ed400bcc8af3e69756ad83b97c6c84d53a695 --- /dev/null +++ b/ee/spec/graphql/types/work_items/widgets/custom_status_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::WorkItems::Widgets::CustomStatusType, feature_category: :team_planning do + it 'exposes the expected fields' do + expected_fields = %i[id name icon_name color position] + + expected_fields.each do |field| + expect(described_class).to have_graphql_field(field) + end + end +end diff --git a/ee/spec/models/ee/group_spec.rb b/ee/spec/models/ee/group_spec.rb index 8b0270960bdb8d530dda2974e732a525a01d22fb..9d24a9551b608afbba046954775700fe54adfa83 100644 --- a/ee/spec/models/ee/group_spec.rb +++ b/ee/spec/models/ee/group_spec.rb @@ -4322,6 +4322,30 @@ def webhook_headers end end + describe '#work_item_status_feature_available?' do + subject { group.work_item_status_feature_available? } + + before do + stub_feature_flags(work_item_status: true) + end + + context 'when work_item_status licensed feature is enabled' do + before do + stub_licensed_features(work_item_custom_status: true) + end + + it { is_expected.to be true } + end + + context 'when work_item_status licensed feature is disabled' do + before do + stub_licensed_features(work_item_custom_status: false) + end + + it { is_expected.to be false } + end + end + describe '#can_manage_extensions_marketplace_for_enterprise_users?' do let_it_be(:root_group) { create(:group) } let_it_be(:child_group) { create(:group, parent: root_group) } diff --git a/ee/spec/models/ee/project_spec.rb b/ee/spec/models/ee/project_spec.rb index 15a74de4cc980b645ba54f3ebd455ab9aaa99ea6..4c129e268cb751185e2c9106f5c115eaf0ef6d40 100644 --- a/ee/spec/models/ee/project_spec.rb +++ b/ee/spec/models/ee/project_spec.rb @@ -235,6 +235,30 @@ end end + describe '#work_item_status_feature_available?' do + subject { project.work_item_status_feature_available? } + + before do + stub_feature_flags(work_item_status: true) + end + + context 'when work_item_status licensed feature is enabled' do + before do + stub_licensed_features(work_item_custom_status: true) + end + + it { is_expected.to be true } + end + + context 'when work_item_status licensed feature is disabled' do + before do + stub_licensed_features(work_item_custom_status: false) + end + + it { is_expected.to be false } + end + end + context 'import_state dependant predicate method' do shared_examples 'returns expected values' do context 'when project lacks a import_state relation' do diff --git a/ee/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb b/ee/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb index 6313b33bdd918ac645f77788e97807cc96c3b86e..70687b37f6ab49afdb7826b4fe36cc6831f63609 100644 --- a/ee/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb +++ b/ee/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb @@ -31,7 +31,7 @@ } ... on WorkItemWidgetDefinitionCustomStatus { allowedCustomStatuses { - nodes { id name iconName } + nodes { id name iconName color position } } } } diff --git a/spec/graphql/resolvers/work_items/widgets/custom_status_resolver_spec.rb b/spec/graphql/resolvers/work_items/widgets/custom_status_resolver_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..70ef0b94a549412edc0e39e4698b7c38b9bd2607 --- /dev/null +++ b/spec/graphql/resolvers/work_items/widgets/custom_status_resolver_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::WorkItems::Widgets::CustomStatusResolver, feature_category: :team_planning do + include GraphqlHelpers + + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:current_user) { create(:user) } + let_it_be(:task_type) { create(:work_item_type, :task) } + let_it_be(:widget_definition) do + create(:widget_definition, widget_type: :custom_status, work_item_type: task_type, name: 'TesT Widget') + end + + shared_examples 'does not return system defined statuses' do + it 'returns an empty array' do + expect(resolve_custom_statuses&.items).to eq([]) + end + end + + describe '#resolve' do + let(:resource_parent) { group } + + context 'with group' do + it_behaves_like 'does not return system defined statuses' + end + + context 'with project' do + let(:resource_parent) { project } + + it_behaves_like 'does not return system defined statuses' + end + + context 'with unsupported namespace' do + let(:resource_parent) { current_user.namespace } + + it_behaves_like 'does not return system defined statuses' + end + + context 'with work_item_status feature flag disabled' do + before do + stub_feature_flags(work_item_status: false) + end + + it_behaves_like 'does not return system defined statuses' + end + end + + def resolve_custom_statuses(args = {}, context = { current_user: current_user, resource_parent: resource_parent }) + resolve(described_class, obj: widget_definition, args: args, ctx: context) + end +end diff --git a/spec/graphql/types/work_items/widgets/custom_status_type_spec.rb b/spec/graphql/types/work_items/widgets/custom_status_type_spec.rb index a7821cc2ab99902536662d7eac2acad8ef6e3f29..6d5ed400bcc8af3e69756ad83b97c6c84d53a695 100644 --- a/spec/graphql/types/work_items/widgets/custom_status_type_spec.rb +++ b/spec/graphql/types/work_items/widgets/custom_status_type_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' -RSpec.describe Types::WorkItems::Widgets::CustomStatusType, feature_category: :service_desk do +RSpec.describe Types::WorkItems::Widgets::CustomStatusType, feature_category: :team_planning do it 'exposes the expected fields' do - expected_fields = %i[id name icon_name] + expected_fields = %i[id name icon_name color position] expected_fields.each do |field| expect(described_class).to have_graphql_field(field) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 0cb7212d4f1dc395d044a7d717f7b11312dd5a2e..cacd9d5563504cb21cb76750920aa4714ce7adcd 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -4063,6 +4063,12 @@ def define_cache_expectations(cache_key) end end + describe '#work_item_status_feature_available?' do + subject { group.work_item_status_feature_available? } + + it { is_expected.to be false } + end + describe '#continue_indented_text_feature_flag_enabled?' do it_behaves_like 'checks self and root ancestor feature flag' do let(:feature_flag) { :continue_indented_text } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 52a43daf0a53340ad583b195d7d762b6bc744f24..43c8e06bb060df518b0c656f1590587bb5b956dd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5767,9 +5767,9 @@ def has_external_wiki end describe '#predefined_project_variables' do - let_it_be(:project) { create(:project, :repository) } + let(:project_with_pre_defined_var) { create(:project, :repository) } - subject { project.predefined_project_variables.to_runner_variables } + subject { project_with_pre_defined_var.predefined_project_variables.to_runner_variables } specify do expect(subject).to include( @@ -5779,7 +5779,7 @@ def has_external_wiki context 'when ci config path is overridden' do before do - project.update!(ci_config_path: 'random.yml') + project_with_pre_defined_var.update!(ci_config_path: 'random.yml') end it do @@ -9164,6 +9164,14 @@ def has_external_wiki end end + describe '#work_item_status_feature_available?' do + let_it_be(:group_project) { create(:project, :in_subgroup) } + + it "return false" do + expect(group_project.work_item_status_feature_available?).to be false + end + end + describe 'serialization' do let(:object) { build(:project) } diff --git a/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb b/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb index 403e7645f64b92280b79e8913ee12390e8dc5e16..e492f935ccccc2775fcc67259033a587b32acd95 100644 --- a/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb +++ b/spec/support/shared_contexts/requests/api/graphql/work_items/work_item_types_shared_context.rb @@ -21,7 +21,7 @@ } ... on WorkItemWidgetDefinitionCustomStatus { allowedCustomStatuses { - nodes { id name iconName } + nodes { id name iconName color position } } } } @@ -72,7 +72,7 @@ def widgets_for(work_item_type, resource_parent) if widget == WorkItems::Widgets::CustomStatus next custom_status_widget_attributes(work_item_type, - base_attributes) + base_attributes, resource_parent) end next base_attributes unless widget_attributes[widget.type] @@ -96,17 +96,46 @@ def hierarchy_widget_attributes(work_item_type, base_attributes, resource_parent .merge({ 'allowedChildTypes' => { 'nodes' => child_types }, 'allowedParentTypes' => { 'nodes' => parent_types } }) end - def custom_status_widget_attributes(_work_item_type, base_attributes) + def custom_status_widget_attributes(_work_item_type, base_attributes, resource_parent) + unless resource_parent&.root_ancestor&.try(:work_item_status_feature_available?) + return base_attributes.merge({ 'allowedCustomStatuses' => { 'nodes' => [] } }) + end + statuses = [ { - 'id' => 'gid://gitlab/WorkItems::Widgets::CustomStatus/10', - 'name' => 'Custom Status', - 'iconName' => 'custom_status icon' + 'id' => 'gid://gitlab/WorkItems::Statuses::SystemDefined::Status/1', + 'name' => 'To do', + 'iconName' => 'status-waiting', + 'color' => '#535158', + 'position' => 0 + }, + { + 'id' => 'gid://gitlab/WorkItems::Statuses::SystemDefined::Status/2', + 'name' => 'In progress', + 'iconName' => 'status-running', + 'color' => '#0b5cad', + 'position' => 0 + }, + { + 'id' => 'gid://gitlab/WorkItems::Statuses::SystemDefined::Status/3', + 'name' => 'Done', + 'iconName' => 'status-success', + 'color' => '#23663b', + 'position' => 0 + }, + { + 'id' => 'gid://gitlab/WorkItems::Statuses::SystemDefined::Status/4', + 'name' => "Won't do", + 'iconName' => 'status-cancelled', + 'color' => '#ae1901', + 'position' => 0 }, { - 'id' => 'gid://gitlab/WorkItems::Widgets::CustomStatus/10', - 'name' => 'Custom Status', - 'iconName' => 'custom_status icon' + 'id' => 'gid://gitlab/WorkItems::Statuses::SystemDefined::Status/5', + 'name' => 'Duplicate', + 'iconName' => 'status-cancelled', + 'color' => '#ae1901', + 'position' => 10 } ] diff --git a/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb index 28ac13d8d1feb1b32f51c06de90d3ad9b2a75536..2833013135e2a6bc608c922dc833cd1f17e49e40 100644 --- a/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/work_item_type_list_shared_examples.rb @@ -33,6 +33,25 @@ ) end + if Gitlab.ee? + it 'returns the allowed custom statuses' do + stub_licensed_features(work_item_custom_status: true) + post_graphql(query, current_user: current_user) + + work_item_types = graphql_data_at(parent_key, :workItemTypes, :nodes) + custom_status_widgets = work_item_types.flat_map do |work_item_type| + work_item_type['widgetDefinitions'].select { |widget| widget['type'] == 'CUSTOM_STATUS' } + end + + expect(custom_status_widgets).to be_present + custom_status_widgets.each do |widget| + expect(widget['allowedCustomStatuses']).to be_present + expect(widget['allowedCustomStatuses']['nodes']).to all(include('id', 'name', 'iconName', 'color', + 'position')) + end + end + end + it 'prevents N+1 queries' do # Destroy 2 existing types WorkItems::Type.by_type([:issue, :task]).delete_all