Skip to content
Snippets Groups Projects
Verified Commit 669e3685 authored by Andrew Smith's avatar Andrew Smith
Browse files

Implement colour attribute for epics

Refs gitlab-org/gitlab#7641

Changelog: added
parent e7921d56
No related branches found
No related tags found
No related merge requests found
Showing with 95 additions and 2 deletions
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
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
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddTextLimitToEpicsColor < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
add_text_limit :epics, :color, 7
end
def down
# Down is required as `add_text_limit` is not reversible
remove_text_limit :epics, :color
end
end
93960203e6703716f9c513dca340e17041a33792f9233dc4b7e35d1e19614191
\ No newline at end of file
406af18458c7f5ee8a4fa3860ed5fb87c358363926fed2830be8e8a55578822b
\ No newline at end of file
......@@ -14067,6 +14067,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))
);
 
......@@ -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 for the epic |
| `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 for the epic |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_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"
}
```
......
......@@ -1354,6 +1354,7 @@ Input type: `CreateEpicInput`
| ---- | ---- | ----------- |
| <a id="mutationcreateepicaddlabelids"></a>`addLabelIds` | [`[ID!]`](#id) | IDs of labels to be added to the epic. |
| <a id="mutationcreateepicclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreateepiccolor"></a>`color` | [`String`](#string) | Color for the epic. |
| <a id="mutationcreateepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
| <a id="mutationcreateepicdescription"></a>`description` | [`String`](#string) | Description of the epic. |
| <a id="mutationcreateepicduedatefixed"></a>`dueDateFixed` | [`String`](#string) | End date of the epic. |
......@@ -4707,6 +4708,7 @@ Input type: `UpdateEpicInput`
| ---- | ---- | ----------- |
| <a id="mutationupdateepicaddlabelids"></a>`addLabelIds` | [`[ID!]`](#id) | IDs of labels to be added to the epic. |
| <a id="mutationupdateepicclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationupdateepiccolor"></a>`color` | [`String`](#string) | Color for the epic. |
| <a id="mutationupdateepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
| <a id="mutationupdateepicdescription"></a>`description` | [`String`](#string) | Description of the epic. |
| <a id="mutationupdateepicduedatefixed"></a>`dueDateFixed` | [`String`](#string) | End date of the epic. |
......@@ -8746,6 +8748,7 @@ Represents an epic on an issue board.
| <a id="boardepicauthor"></a>`author` | [`UserCore!`](#usercore) | Author of the epic. |
| <a id="boardepicawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of award emojis associated with the epic. (see [Connections](#connections)) |
| <a id="boardepicclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. |
| <a id="boardepiccolor"></a>`color` | [`String!`](#string) | Color assigned to the epic. |
| <a id="boardepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
| <a id="boardepiccreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the epic was created. |
| <a id="boardepicdescendantcounts"></a>`descendantCounts` | [`EpicDescendantCount`](#epicdescendantcount) | Number of open and closed descendant epics and issues. |
......@@ -8781,6 +8784,7 @@ Represents an epic on an issue board.
| <a id="boardepicstartdateisfixed"></a>`startDateIsFixed` | [`Boolean`](#boolean) | Indicates if the start date has been manually set. |
| <a id="boardepicstate"></a>`state` | [`EpicState!`](#epicstate) | State of the epic. |
| <a id="boardepicsubscribed"></a>`subscribed` | [`Boolean!`](#boolean) | Indicates the currently logged in user is subscribed to the epic. |
| <a id="boardepictextcolor"></a>`textColor` | [`String!`](#string) | Text color assigned to the epic. |
| <a id="boardepictitle"></a>`title` | [`String`](#string) | Title of the epic. |
| <a id="boardepictitlehtml"></a>`titleHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `title`. |
| <a id="boardepicupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp of when the epic was updated. |
......@@ -10263,6 +10267,7 @@ Represents an epic.
| <a id="epicauthor"></a>`author` | [`UserCore!`](#usercore) | Author of the epic. |
| <a id="epicawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of award emojis associated with the epic. (see [Connections](#connections)) |
| <a id="epicclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. |
| <a id="epiccolor"></a>`color` | [`String!`](#string) | Color assigned to the epic. |
| <a id="epicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
| <a id="epiccreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the epic was created. |
| <a id="epicdescendantcounts"></a>`descendantCounts` | [`EpicDescendantCount`](#epicdescendantcount) | Number of open and closed descendant epics and issues. |
......@@ -10298,6 +10303,7 @@ Represents an epic.
| <a id="epicstartdateisfixed"></a>`startDateIsFixed` | [`Boolean`](#boolean) | Indicates if the start date has been manually set. |
| <a id="epicstate"></a>`state` | [`EpicState!`](#epicstate) | State of the epic. |
| <a id="epicsubscribed"></a>`subscribed` | [`Boolean!`](#boolean) | Indicates the currently logged in user is subscribed to the epic. |
| <a id="epictextcolor"></a>`textColor` | [`String!`](#string) | Text color assigned to the epic. |
| <a id="epictitle"></a>`title` | [`String`](#string) | Title of the epic. |
| <a id="epictitlehtml"></a>`titleHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `title`. |
| <a id="epicupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp of when the epic was updated. |
......@@ -83,6 +83,7 @@ def epic_params
def epic_params_attributes
[
:color,
:title,
:description,
:start_date_fixed,
......
......@@ -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 for the epic.'
end
def validate_arguments!(args)
......
......@@ -161,6 +161,12 @@ class EpicType < BaseObject
resolver: ::Resolvers::EpicAncestorsResolver,
description: 'Ancestors (parents) of the epic.'
field :color, GraphQL::Types::String, null: false,
description: 'Color assigned to the epic.'
field :text_color, GraphQL::Types::String, null: false,
description: 'Text color assigned to the epic.'
def has_children?
Gitlab::Graphql::Aggregations::Epics::LazyEpicAggregate.new(context, object.id, COUNT) do |node, _aggregate_object|
node.children.any?
......
......@@ -21,11 +21,17 @@ module Epic
include Todoable
include SortableTitle
DEFAULT_COLOR = '#1068bf'
default_value_for :color, DEFAULT_COLOR
enum state_id: {
opened: ::Epic.available_states[:opened],
closed: ::Epic.available_states[:closed]
}
validates :color, color: true, allow_blank: false
alias_attribute :state, :state_id
belongs_to :closed_by, class_name: 'User'
......@@ -202,6 +208,16 @@ 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
end
class_methods do
......@@ -347,6 +363,18 @@ def keyset_pagination_for(column_name:, direction: 'ASC')
end
end
def color
super || DEFAULT_COLOR
end
def text_color
if light_color?(self.color)
'#333333'
else
'#FFFFFF'
end
end
def resource_parent
group
end
......
......@@ -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)
......
......@@ -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'
......@@ -120,6 +121,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,7 +134,7 @@ 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!
......
......@@ -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|
......
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