Add table(s) for mapping widgets to work_items

Add table(s) necessary for storing information about what widgets are available for each work item type.

Proposal (based on discussion in #374092 (comment 1134564199))

Table schema should be aligned with future customization of work item types (#335110), this also means that we should avoid changing work_item_type_id when user customizes/changes work item type (otherwise we would have to update work_item_type_id for all existing work_items for this type).

erDiagram
    work_item_types {
      int id
      string name
      int namespace_id
      boolean disabled
    }
    work_item_types ||--|o work_item_type_overrides : ""
    work_item_type_overrides {
      int id
      string name
      int namespace_id
      boolean disabled
    }
    work_item_types ||--|{ widget_definitions : ""
    widget_definitions {
      int id
      int namespace_id
      int work_item_type_id
      int widget_definition_enum
      int position
      string name
      boolean disabled
    }

Then content of table would look like this:

Widget mapping to a class

Model enum definition of mapping widget ids to classes (this is not dynamic, needs to be updated only with rare situation of adding new widget):

widget_definition_enum = {
hierarchy: 0,
weight: 1,
start_due_date: 2,
text_field: 3,
integer_field: 4
} 

work_item_types

  • pre-filled with default types (types with namespace_id=null)
  • users can add custom work item types (these will have namespace_id set)
id name namespace_id disabled
0 issue
1 task
2 requirement
3 custom type 1 1
4 custom type 2 1
5 alternative issue true
6 alternative epic true

work_item_type_overrides table

  • used for user's customization of default work item types (changing its name, or disabling them)
namespace_id work_item_type_id name disabled
1 0 ticket false
1 1 task true

widget_definitions

  • widget_definitions is pre-filled with widget definitions for default types (ids 0-2: issue has two widgets - hierarchy, weight, task has one widget - Weight)
id namespace_id work_item_type_id widget_definition_enum position name disabled
0 0 0 0 Hierarchy false
1 0 1 1 Weight false
2 1 0 0 Hierarchy false
3 1 1 0 0 Hierarchy true
4 1 1 1 0 MyWeight false
5 1 3 1 0 Weight false
6 1 3 3 0 MyTextField1 false
7 1 3 4 0 MyNumField 1 false

Getting all work item types for a namespace: select * from work_item_types where namespace_id is null or namespace_id=1

Getting all widgets for a work item type: select * from widget_definitions where work_item_type_id=1 and (namespace_id is null or namespace_id=1) where disabled=false

Important Note: here we would have to prioritize namespaced record over default record when loading them (IOW this would load records 2,3,4, but we would discard 2 in favor of 3 as id=3 is namespace-specific)

Scenarios:

  • user creates new namespace and works with pre-defined work item types: no new records are added
  • user wants to use default issue type but change some of its widgets: for task he wants to disable hierarchy and rename weight widget to "MyWeight"): adds rows with ids 3 and 4
  • user wants to add his own work item type (custom type 1) with its own set of widgets (Weight, MyTextField1 and MyNumField 1): adds rows with ids 5,6,7 to widget_definitions
  • user wants to rename default type Issue to Ticket: first row in work_item_overrides
  • user wants to disable default type Task: second row in work_item_overrides
  • we want to provide multiple predefined work item type sets - e.g. Issue and Epic types used by default. But user can enable usage of Alternative issue and Alternative epic: alternative types are disabled by default, user can enable them in work_item_overrides and disable Issue/Epic there
Original proposal
A possible schema is in Brett's comment in #335110 (comment 622008798).
  • the schema may contain some attributes which we don't use (yet?) - e.g. row_order or disabled - these might be added later when needed.
  • work_item_type has many widgets - we may need an association table for this (or alternatively store list of widget references into an array field but this may be less flexible)

So maybe for now models might look like this (for current needs while extensible in future)?

class WorkItems::Type # already existing model
  has_many :type_widgets
  has_many :widgets, through: :type_widgets
end

class WorkItems::TypeWidgets # TODO - better name?
  belongs_to :work_item_type
  belongs_to :widget_definition
end

class WorkItems::WidgetDefinition
  has_many :type_widgets
  has_many :work_item_types, through: :type_widgets

  # attributes: id, name, widget_class
  # widget_class - defines widget's "class" we we know to which widget class (https://gitlab.com/gitlab-org/gitlab/-/tree/master/app/models/work_items/widgets) - doesn't have to be class name, can be also just enum (and mapping to classes can be statically defined)
end

Related to #370599

Edited by Jan Provaznik