Draft: POC: Configurable work item types
What does this MR do and why?
This is a work in progress POC for configurable work item types which consists of two parts:
- Move WorkItems::Type to WorkItems::SystemDefined::Type and all related classes/methods
- Create/list/view work items works
- Legacy issue view works for incidents
- Legacy Service Desk list works
- Related GraphQL queries work
- change type works
- Issue and epic boards work
-
🚧 custom fields -
🚧 some note related errors... I likely missed a preload somewhere -
🚧 some of the settings
- Introduce custom work item types WorkItems::Custom::Type and related classes
WorkItems::SystemDefined::Type
This is the system-defined version of the WorkItems::Type model. Right now it uses a minimal set of attributes and only has the methods that were needed to make work items (list, detail, create, boards) work.
Some notable details:
def key
name.parameterize.underscore.to_sym
end
wit.key
=> :issue
This is the base type. I chose to not implement the epic?, issue? etc. methods, so we can see in this MR which places we need to change. I changed the method calls to something along the lines of wit.key == :issue. Not because I like this more, but so we have this change recorded in the MR.
I intentionally used the name key, so I needed to change occurrences of calls to base_type. Again this is not because I like key more, it's to record these changes in the MR. With custom types the term base_type could make sense, so let's use whatever feels right.
# Could be an idea to fetch the "base type" to reduce the length of the expression.
# WorkItems::SystemDefined::Type[:issue] vs. WorkItems::SystemDefined::Type.by_base_type(:issue)
def [](key)
return if key.nil?
# Accept several input formats
key = key.parameterize.underscore.to_sym unless key.is_a?(Symbol)
all.find { |item| item.key == key }
end
This would allow us to fetch a system-defined type by calling WorkItems::SystemDefined::Type[:issue] instead of WorkItems::SystemDefined::Type.find(1) or some other method name with base_type. I think that could be a pattern for fixed items models that has some kind of additional identifier apart from the id, so one can access it in a more readable and short way.
def by_ids_ordered_by_name(ids)
where(id: ids).sort_by { |type| type.name.downcase }
end
Scopes are not ideal right now because we cannot chain them (scope.order...). I'm unsure whether we should introduce an intermediate class that handles queries better. For now, let's just add class methods that do what we need.
def icon_name
"issue-type-#{parameterized_name}"
end
Derived from the name. I think we should move the icon names to an enum or something different because icon names can change and might not be based on the name in the future. That's especially true for custom types where we should store the integer value of an enum in the database instead of the icon name.
def widgets(_resource_parent)
end
There's no point in enabled_widgets any more
WorkItems::SystemDefined::WidgetDefinition
This is the system-defined version of WorkItems::WidgetDefinition. Right now its structure is similar to it's legacy model version.
Some notable details:
auto_generate_ids!
Fixed items models must have an id. This enforcement is especially useful for items that are used in database associations. That's not true for widget definitions. We don't really care about the id of the widget definition. To make the definition of items easier, I added the option to auto generate ids for items, so they don't need to be defined any more. This makes dynamic item definitions very readable.
WIDGET_TYPES = %i[
assignees
...
]
Contains a list of all available widgets. This is the SSOT for which widgets exist.
ITEMS = Type::ITEMS.flat_map do |type_item|
type_key = type_item[:name].parameterize.underscore.to_sym
config_module = WidgetConfigurations.const_get(type_key.to_s.camelize, false)
config_module::WIDGETS.map do |widget_type|
{
widget_type: widget_type,
work_item_type_id: type_item[:id],
widget_options: config_module::WIDGET_OPTIONS[widget_type]
}.compact
end
end.freeze
Individual widget definitions for system defined types are located in WidgetConfigurations:: modules to get around adding several hundred lines of items here and encapsulate things. See for example WorkItems::WidgetConfidgurations::Issue. It contains a list of widgets for the type and a list of widget options if needed. We'll walk through all system-defined types and create records for each available widget for a type.
Issue
I added a getter and setter to Issue for work_item_type and chose not to use the belongs_to_fixed_items association because we'll need to resolve system-defined and custom types anyway.
References
Screenshots or screen recordings
| Before | After |
|---|---|
How to set up and validate locally
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.