diff --git a/db/migrate/20211021115409_add_color_to_epics.rb b/db/migrate/20211021115409_add_color_to_epics.rb new file mode 100644 index 0000000000000000000000000000000000000000..14b38209f308f1d115f5c8c6db307d03580360f8 --- /dev/null +++ b/db/migrate/20211021115409_add_color_to_epics.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddColorToEpics < Gitlab::Database::Migration[1.0] + # rubocop:disable Migration/AddLimitToTextColumns + # limit is added in 20211021124715_add_text_limit_to_epics_color + def change + add_column :epics, :color, :text, default: '#1068bf' + end + # rubocop:enable Migration/AddLimitToTextColumns +end diff --git a/db/migrate/20211021124715_add_text_limit_to_epics_color.rb b/db/migrate/20211021124715_add_text_limit_to_epics_color.rb new file mode 100644 index 0000000000000000000000000000000000000000..7844575c521aaca6393838c9bfd84f1506df9354 --- /dev/null +++ b/db/migrate/20211021124715_add_text_limit_to_epics_color.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddTextLimitToEpicsColor < Gitlab::Database::Migration[1.0] + disable_ddl_transaction! + + def up + add_text_limit :epics, :color, 7 + end + + def down + remove_text_limit :epics, :color + end +end diff --git a/db/schema_migrations/20211021115409 b/db/schema_migrations/20211021115409 new file mode 100644 index 0000000000000000000000000000000000000000..bcbed29837790ec7747c4141780acdf6dac27737 --- /dev/null +++ b/db/schema_migrations/20211021115409 @@ -0,0 +1 @@ +93960203e6703716f9c513dca340e17041a33792f9233dc4b7e35d1e19614191 \ No newline at end of file diff --git a/db/schema_migrations/20211021124715 b/db/schema_migrations/20211021124715 new file mode 100644 index 0000000000000000000000000000000000000000..2d03c608bacdd092124a651d1ee3685037d9aa3e --- /dev/null +++ b/db/schema_migrations/20211021124715 @@ -0,0 +1 @@ +406af18458c7f5ee8a4fa3860ed5fb87c358363926fed2830be8e8a55578822b \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index cebe5aef9a596f220ce70fc714414e38ca35e629..5bed81335567526be05be15dc139e9ffbec9baec 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14137,6 +14137,8 @@ CREATE TABLE epics ( due_date_sourcing_epic_id integer, confidential boolean DEFAULT false NOT NULL, external_key character varying(255), + color text DEFAULT '#1068bf'::text, + CONSTRAINT check_ca608c40b3 CHECK ((char_length(color) <= 7)), CONSTRAINT check_fcfb4a93ff CHECK ((lock_version IS NOT NULL)) ); diff --git a/doc/api/epics.md b/doc/api/epics.md index deb74cf21e91deacbfd56fd6561be4246f4d2abb..de20af08915233e037d1b087868248a43d893265 100644 --- a/doc/api/epics.md +++ b/doc/api/epics.md @@ -131,6 +131,7 @@ Example response: "labels": [], "upvotes": 4, "downvotes": 0, + "color": "#1068bf", "_links":{ "self": "http://gitlab.example.com/api/v4/groups/7/epics/4", "epic_issues": "http://gitlab.example.com/api/v4/groups/7/epics/4/issues", @@ -179,6 +180,7 @@ Example response: "labels": [], "upvotes": 4, "downvotes": 0, + "color": "#1068bf", "_links":{ "self": "http://gitlab.example.com/api/v4/groups/17/epics/35", "epic_issues": "http://gitlab.example.com/api/v4/groups/17/epics/35/issues", @@ -252,6 +254,7 @@ Example response: "labels": [], "upvotes": 4, "downvotes": 0, + "color": "#1068bf", "subscribed": true, "_links":{ "self": "http://gitlab.example.com/api/v4/groups/7/epics/5", @@ -283,6 +286,7 @@ POST /groups/:id/epics | `title` | string | yes | The title of the epic | | `labels` | string | no | The comma-separated list of labels | | `description` | string | no | The description of the epic. Limited to 1,048,576 characters. | +| `color` | string | no | The color of the epic. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7641) in GitLab 14.8, behind a feature flag named `epic_highlight_color` (disabled by default) | | `confidential` | boolean | no | Whether the epic should be confidential | | `created_at` | string | no | When the epic was created. Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z` . Requires administrator or project/group owner privileges ([available](https://gitlab.com/gitlab-org/gitlab/-/issues/255309) in GitLab 13.5 and later) | | `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (in GitLab 11.3 and later) | @@ -340,6 +344,7 @@ Example response: "labels": [], "upvotes": 4, "downvotes": 0, + "color": "#1068bf", "_links":{ "self": "http://gitlab.example.com/api/v4/groups/7/epics/6", "epic_issues": "http://gitlab.example.com/api/v4/groups/7/epics/6/issues", @@ -381,6 +386,7 @@ PUT /groups/:id/epics/:epic_iid | `state_event` | string | no | State event for an epic. Set `close` to close the epic and `reopen` to reopen it (in GitLab 11.4 and later) | | `title` | string | no | The title of an epic | | `updated_at` | string | no | When the epic was updated. Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z` . Requires administrator or project/group owner privileges ([available](https://gitlab.com/gitlab-org/gitlab/-/issues/255309) in GitLab 13.5 and later) | +| `color` | string | no | The color of the epic. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7641) in GitLab 14.8, behind a feature flag named `epic_highlight_color` (disabled by default) | ```shell curl --request PUT --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/groups/1/epics/5?title=New%20Title&parent_id=29" @@ -430,7 +436,8 @@ Example response: "closed_at": "2018-08-18T12:22:05.239Z", "labels": [], "upvotes": 4, - "downvotes": 0 + "downvotes": 0, + "color": "#1068bf" } ``` diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index ba80160110f8f17a039f9ba263aeb912d760dc3a..be62a995bb9367f8cf6a25a20070f470f7309683 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1366,6 +1366,7 @@ Input type: `CreateEpicInput` | ---- | ---- | ----------- | | `addLabelIds` | [`[ID!]`](#id) | IDs of labels to be added to the epic. | | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `color` | [`String`](#string) | Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | | `confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. | | `description` | [`String`](#string) | Description of the epic. | | `dueDateFixed` | [`String`](#string) | End date of the epic. | @@ -4756,6 +4757,7 @@ Input type: `UpdateEpicInput` | ---- | ---- | ----------- | | `addLabelIds` | [`[ID!]`](#id) | IDs of labels to be added to the epic. | | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `color` | [`String`](#string) | Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | | `confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. | | `description` | [`String`](#string) | Description of the epic. | | `dueDateFixed` | [`String`](#string) | End date of the epic. | @@ -8850,6 +8852,7 @@ Represents an epic on an issue board. | `author` | [`UserCore!`](#usercore) | Author of the epic. | | `awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of award emojis associated with the epic. (see [Connections](#connections)) | | `closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. | +| `color` | [`String!`](#string) | Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | | `confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. | | `createdAt` | [`Time`](#time) | Timestamp of when the epic was created. | | `descendantCounts` | [`EpicDescendantCount`](#epicdescendantcount) | Number of open and closed descendant epics and issues. | @@ -8885,6 +8888,7 @@ Represents an epic on an issue board. | `startDateIsFixed` | [`Boolean`](#boolean) | Indicates if the start date has been manually set. | | `state` | [`EpicState!`](#epicstate) | State of the epic. | | `subscribed` | [`Boolean!`](#boolean) | Indicates the currently logged in user is subscribed to the epic. | +| `textColor` | [`String!`](#string) | Text color generated for the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | | `title` | [`String`](#string) | Title of the epic. | | `titleHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `title`. | | `updatedAt` | [`Time`](#time) | Timestamp of when the epic was updated. | @@ -10378,6 +10382,7 @@ Represents an epic. | `author` | [`UserCore!`](#usercore) | Author of the epic. | | `awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of award emojis associated with the epic. (see [Connections](#connections)) | | `closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. | +| `color` | [`String!`](#string) | Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | | `confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. | | `createdAt` | [`Time`](#time) | Timestamp of when the epic was created. | | `descendantCounts` | [`EpicDescendantCount`](#epicdescendantcount) | Number of open and closed descendant epics and issues. | @@ -10413,6 +10418,7 @@ Represents an epic. | `startDateIsFixed` | [`Boolean`](#boolean) | Indicates if the start date has been manually set. | | `state` | [`EpicState!`](#epicstate) | State of the epic. | | `subscribed` | [`Boolean!`](#boolean) | Indicates the currently logged in user is subscribed to the epic. | +| `textColor` | [`String!`](#string) | Text color generated for the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | | `title` | [`String`](#string) | Title of the epic. | | `titleHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `title`. | | `updatedAt` | [`Time`](#time) | Timestamp of when the epic was updated. | diff --git a/ee/app/controllers/groups/epics_controller.rb b/ee/app/controllers/groups/epics_controller.rb index 0b4407dda0891337e875c72a3664f97a48948308..c85c9c0d851a4cf899e80a158ed2a20333cab406 100644 --- a/ee/app/controllers/groups/epics_controller.rb +++ b/ee/app/controllers/groups/epics_controller.rb @@ -80,6 +80,7 @@ def epic_params def epic_params_attributes [ + :color, :title, :description, :start_date_fixed, diff --git a/ee/app/graphql/mutations/concerns/mutations/shared_epic_arguments.rb b/ee/app/graphql/mutations/concerns/mutations/shared_epic_arguments.rb index e6e87aecabff64dbd20546704e2b47209a48198c..3880bab08325a92f21163dcfd984d24c0ba4f0ee 100644 --- a/ee/app/graphql/mutations/concerns/mutations/shared_epic_arguments.rb +++ b/ee/app/graphql/mutations/concerns/mutations/shared_epic_arguments.rb @@ -51,6 +51,11 @@ module SharedEpicArguments [GraphQL::Types::ID], required: false, description: 'IDs of labels to be removed from the epic.' + + argument :color, + GraphQL::Types::String, + required: false, + description: 'Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.' end def validate_arguments!(args) @@ -58,6 +63,8 @@ def validate_arguments!(args) raise Gitlab::Graphql::Errors::ArgumentError, 'The list of epic attributes is empty' end + + args.delete(:color) unless Feature.enabled?(:epic_color_highlight) end end end diff --git a/ee/app/graphql/types/epic_type.rb b/ee/app/graphql/types/epic_type.rb index 080eaccca7c38b1bfcd66526957623ec68c2b12e..0be711dd718a726c2b07ec76f3ca5f0faae3dbb8 100644 --- a/ee/app/graphql/types/epic_type.rb +++ b/ee/app/graphql/types/epic_type.rb @@ -160,6 +160,14 @@ class EpicType < BaseObject resolver: ::Resolvers::EpicAncestorsResolver, description: 'Ancestors (parents) of the epic.' + field :color, GraphQL::Types::String, null: false, + description: 'Color of the epic.', + feature_flag: :epic_color_highlight + + field :text_color, GraphQL::Types::String, null: false, + description: 'Text color generated for the epic.', + feature_flag: :epic_color_highlight + markdown_field :title_html, null: true markdown_field :description_html, null: true diff --git a/ee/app/models/ee/epic.rb b/ee/app/models/ee/epic.rb index b787f4134e436eb8a0ad5abea06a97c178242151..426fd9d258d826192bd87a3e6428cc1d648f94a3 100644 --- a/ee/app/models/ee/epic.rb +++ b/ee/app/models/ee/epic.rb @@ -21,11 +21,19 @@ module Epic include Todoable include SortableTitle + DEFAULT_COLOR = '#1068bf' + + default_value_for :color, allows_nil: false, value: DEFAULT_COLOR + enum state_id: { opened: ::Epic.available_states[:opened], closed: ::Epic.available_states[:closed] } + validates :color, color: true, allow_blank: false + + before_validation :strip_whitespace_from_color + alias_attribute :state, :state_id belongs_to :closed_by, class_name: 'User' @@ -202,6 +210,20 @@ def set_fixed_due_date def usage_ping_record_epic_creation ::Gitlab::UsageDataCounters::EpicActivityUniqueCounter.track_epic_created_action(author: author) end + + def light_color?(color) + if color.length == 4 + r, g, b = color[1, 4].scan(/./).map { |v| (v * 2).hex } + else + r, g, b = color[1, 7].scan(/.{2}/).map(&:hex) + end + + (r + g + b) > 500 + end + + def strip_whitespace_from_color + color.strip! + end end class_methods do @@ -347,6 +369,14 @@ def keyset_pagination_for(column_name:, direction: 'ASC') end end + def text_color + if light_color?(color) + '#333333' + else + '#FFFFFF' + end + end + def resource_parent group end diff --git a/ee/app/serializers/epic_entity.rb b/ee/app/serializers/epic_entity.rb index 5947f74a7044b7efd5c92996224469334f15c802..529dae4fd4fe008bfe64b73bb962aee167af8a4d 100644 --- a/ee/app/serializers/epic_entity.rb +++ b/ee/app/serializers/epic_entity.rb @@ -23,6 +23,8 @@ class EpicEntity < IssuableEntity expose :state expose :lock_version expose :confidential + expose :color + expose :text_color expose :web_url do |epic| group_epic_path(epic.group, epic) diff --git a/ee/config/feature_flags/development/epic_color_highlight.yml b/ee/config/feature_flags/development/epic_color_highlight.yml new file mode 100644 index 0000000000000000000000000000000000000000..346109e5c18db982bbe5d1b8d6c07ecb90f4ee56 --- /dev/null +++ b/ee/config/feature_flags/development/epic_color_highlight.yml @@ -0,0 +1,8 @@ +--- +name: epic_color_highlight +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79940 +rollout_issue_url: +milestone: '14.9' +type: development +group: group::product planning +default_enabled: false diff --git a/ee/lib/api/epics.rb b/ee/lib/api/epics.rb index da9f3de2cde3b38a1a39ddaf167b5a365a38a5ee..72efa9ebd4a2c65081494cf777d3df78a385858e 100644 --- a/ee/lib/api/epics.rb +++ b/ee/lib/api/epics.rb @@ -91,6 +91,7 @@ class Epics < ::API::Base params do requires :title, type: String, desc: 'The title of an epic' optional :description, type: String, desc: 'The description of an epic' + optional :color, type: String, desc: 'The color of an epic' optional :confidential, type: Boolean, desc: 'Indicates if the epic is confidential' optional :created_at, type: DateTime, desc: 'Date time when the epic was created. Available only for admins and project owners.' optional :start_date, as: :start_date_fixed, type: String, desc: 'The start date of an epic' @@ -105,6 +106,7 @@ class Epics < ::API::Base # Setting created_at is allowed only for admins and owners params.delete(:created_at) unless current_user.can?(:set_epic_created_at, user_group) + params.delete(:color) unless Feature.enabled?(:epic_color_highlight) epic = ::Epics::CreateService.new(group: user_group, current_user: current_user, params: declared_params(include_missing: false)).execute if epic.valid? @@ -120,6 +122,7 @@ class Epics < ::API::Base params do requires :epic_iid, type: Integer, desc: 'The internal ID of an epic' optional :title, type: String, desc: 'The title of an epic' + optional :color, type: String, desc: 'The color of an epic' optional :description, type: String, desc: 'The description of an epic' optional :confidential, type: Boolean, desc: 'Indicates if the epic is confidential' optional :updated_at, type: DateTime, desc: 'Date time when the epic was updated. Available only for admins and project owners.' @@ -132,13 +135,14 @@ class Epics < ::API::Base optional :remove_labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names' optional :state_event, type: String, values: %w[reopen close], desc: 'State event for an epic' optional :parent_id, type: Integer, desc: 'The id of a parent epic' - at_least_one_of :title, :description, :start_date_fixed, :start_date_is_fixed, :due_date_fixed, :due_date_is_fixed, :labels, :add_labels, :remove_labels, :state_event, :confidential, :parent_id + at_least_one_of :add_labels, :color, :confidential, :description, :due_date_fixed, :due_date_is_fixed, :labels, :parent_id, :remove_labels, :start_date_fixed, :start_date_is_fixed, :state_event, :title end put ':id/(-/)epics/:epic_iid' do authorize_can_admin_epic! # Setting updated_at is allowed only for admins and owners params.delete(:updated_at) unless current_user.can?(:set_epic_updated_at, user_group) + params.delete(:color) unless Feature.enabled?(:epic_color_highlight) update_params = declared_params(include_missing: false) update_params.delete(:epic_iid) diff --git a/ee/lib/ee/api/entities/epic.rb b/ee/lib/ee/api/entities/epic.rb index c3aace2fd0e6f1ce141f249fb1bb321578a05b1a..2359a5f95383b85e621a4620f43737e7c7b94567 100644 --- a/ee/lib/ee/api/entities/epic.rb +++ b/ee/lib/ee/api/entities/epic.rb @@ -10,6 +10,8 @@ class Epic < Grape::Entity expose :id expose :iid + expose :color + expose :text_color expose :group_id expose :parent_id expose :parent_iid do |epic| diff --git a/ee/spec/fixtures/api/schemas/entities/epic.json b/ee/spec/fixtures/api/schemas/entities/epic.json index 3d076dd692a2da43002f5ef158433c69a31f0868..cb181229afd4921cb75ca051d3b31b00257c6047 100644 --- a/ee/spec/fixtures/api/schemas/entities/epic.json +++ b/ee/spec/fixtures/api/schemas/entities/epic.json @@ -27,7 +27,9 @@ "can_create_note": { "type": "boolean" } }, "create_note_path": { "type": "string" }, - "preview_note_path": { "type": "string" } + "preview_note_path": { "type": "string" }, + "color": { "type": "string" }, + "text_color": { "type": "string" } }, "required": [ "id", diff --git a/ee/spec/fixtures/api/schemas/public_api/v4/epic.json b/ee/spec/fixtures/api/schemas/public_api/v4/epic.json index 38decdaef021d3741b273fdcb31d806dd478b587..71a354a096f854193d0d722359b5a6dc211d8873 100644 --- a/ee/spec/fixtures/api/schemas/public_api/v4/epic.json +++ b/ee/spec/fixtures/api/schemas/public_api/v4/epic.json @@ -62,7 +62,9 @@ "parent": { "type": "uri" }, "additionalProperties": false } - } + }, + "color": { "type": "string" }, + "text_color": { "type": "string" } }, "required": [ "id", "iid", "group_id", "title", "confidential", "_links" diff --git a/ee/spec/graphql/types/epic_type_spec.rb b/ee/spec/graphql/types/epic_type_spec.rb index b695046638c434efe65af7d854204705de66adea..1f1faf7c7b1fa45bc1ee8dbfed677a48b26f9324 100644 --- a/ee/spec/graphql/types/epic_type_spec.rb +++ b/ee/spec/graphql/types/epic_type_spec.rb @@ -14,7 +14,7 @@ notes discussions relative_position subscribed participants descendant_counts descendant_weight_sum upvotes downvotes user_notes_count user_discussions_count health_status current_user_todos - award_emoji events ancestors + award_emoji events ancestors color text_color ] end diff --git a/ee/spec/lib/gitlab/graphql/loaders/bulk_epic_aggregate_loader_spec.rb b/ee/spec/lib/gitlab/graphql/loaders/bulk_epic_aggregate_loader_spec.rb index 808fc5038a617a974a8cbeed33f74c201553dc0d..080fa4177c044ee7467c403788d664f7eedbd74f 100644 --- a/ee/spec/lib/gitlab/graphql/loaders/bulk_epic_aggregate_loader_spec.rb +++ b/ee/spec/lib/gitlab/graphql/loaders/bulk_epic_aggregate_loader_spec.rb @@ -147,7 +147,8 @@ def result_for(epic, issues_state:, issues_count:, issues_weight_sum:) issues_weight_sum: issues_weight_sum, parent_id: epic.parent_id, issues_state_id: issues_state, - epic_state_id: Epic.available_states[epic.state_id] + epic_state_id: Epic.available_states[epic.state_id], + color: ::EE::Epic::DEFAULT_COLOR }.stringify_keys end end diff --git a/ee/spec/models/epic_spec.rb b/ee/spec/models/epic_spec.rb index 5f0d2f20d357a184a3efd83acc94fba8eddd0dc8..95ad4aca4e575bdb30a9d80c590e3dbb692509e3 100644 --- a/ee/spec/models/epic_spec.rb +++ b/ee/spec/models/epic_spec.rb @@ -818,7 +818,8 @@ def as_item(item) "issues_count" => 2, "issues_state_id" => 1, "issues_weight_sum" => 5, - "parent_id" => epic1.id + "parent_id" => epic1.id, + "color" => ::EE::Epic::DEFAULT_COLOR }, { "epic_state_id" => 2, "id" => epic3.id, @@ -826,7 +827,8 @@ def as_item(item) "issues_count" => 1, "issues_state_id" => 2, "issues_weight_sum" => 0, - "parent_id" => epic2.id + "parent_id" => epic2.id, + "color" => ::EE::Epic::DEFAULT_COLOR }] expect(result).to match_array(expected) end @@ -842,4 +844,22 @@ def as_item(item) create(:epic) end end + + context 'with coloured epics' do + using RSpec::Parameterized::TableSyntax + + where(:epic_color, :expected_text_color) do + ::EE::Epic::DEFAULT_COLOR | '#FFFFFF' + '#FFFFFF' | '#333333' + '#000000' | '#FFFFFF' + end + + with_them do + it 'returns correct text color' do + epic = build(:epic, color: epic_color) + + expect(epic.text_color).to eq(expected_text_color) + end + end + end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index f019883a91edd048e54576b2b149c59f849a66aa..e06fcb0cd3f9f44aa3bbb901e9b05380f8796a8d 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -857,6 +857,7 @@ Epic: - health_status - external_key - confidential + - color EpicIssue: - id - relative_position