Commit fd5d3167 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 23687bfe 1d5a4f90
......@@ -69,6 +69,9 @@ variables:
GET_SOURCES_ATTEMPTS: "3"
DEBIAN_VERSION: "bullseye"
TMP_TEST_FOLDER: "${CI_PROJECT_DIR}/tmp/tests"
GITLAB_WORKHORSE_FOLDER: "gitlab-workhorse"
TMP_TEST_GITLAB_WORKHORSE_PATH: "${TMP_TEST_FOLDER}/${GITLAB_WORKHORSE_FOLDER}"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/report-master.json
FLAKY_RSPEC_SUITE_REPORT_PATH: rspec/flaky/report-suite.json
RSPEC_TESTS_MAPPING_PATH: crystalball/mapping.json
......
......@@ -183,7 +183,7 @@
# rspec job parallel configs
############################
#######################################################
###############################################################
# EE/FOSS: default refs (MRs, default branch, schedules) jobs #
setup-test-env:
extends:
......@@ -194,40 +194,53 @@ setup-test-env:
variables:
SETUP_DB: "false"
script:
- source scripts/gitlab_workhorse_component_helpers.sh
- run_timed_command "download_and_extract_gitlab_workhorse_package" || true
- run_timed_command "scripts/setup-test-env"
- run_timed_command "select_gitlab_workhorse_essentials"
- echo -e "\e[0Ksection_start:`date +%s`:gitaly-test-build[collapsed=true]\r\e[0KCompiling Gitaly binaries"
- run_timed_command "scripts/gitaly-test-build" # Do not use 'bundle exec' here
- echo -e "\e[0Ksection_end:`date +%s`:gitaly-test-build\r\e[0K"
artifacts:
expire_in: 7d
paths:
- config/secrets.yml
- tmp/tests/gitaly/_build/bin/
- tmp/tests/gitaly/_build/deps/git/install
- tmp/tests/gitaly/config.toml
- tmp/tests/gitaly/gitaly2.config.toml
- tmp/tests/gitaly/internal/
- tmp/tests/gitaly/internal_gitaly2/
- tmp/tests/gitaly/internal_sockets/
- tmp/tests/gitaly/Makefile
- tmp/tests/gitaly/praefect.config.toml
- tmp/tests/gitaly/ruby/
- tmp/tests/gitlab-elasticsearch-indexer/bin/gitlab-elasticsearch-indexer
- tmp/tests/gitlab-shell/
- tmp/tests/gitlab-test-fork/
- tmp/tests/gitlab-test-fork_bare/
- tmp/tests/gitlab-test/
- tmp/tests/gitlab-workhorse/gitlab-zip-metadata
- tmp/tests/gitlab-workhorse/gitlab-zip-cat
- tmp/tests/gitlab-workhorse/gitlab-workhorse
- tmp/tests/gitlab-workhorse/gitlab-resize-image
- tmp/tests/gitlab-workhorse/config.toml
- tmp/tests/gitlab-workhorse/WORKHORSE_TREE
- tmp/tests/repositories/
- tmp/tests/second_storage/
- ${TMP_TEST_FOLDER}/gitaly/_build/bin/
- ${TMP_TEST_FOLDER}/gitaly/_build/deps/git/install
- ${TMP_TEST_FOLDER}/gitaly/config.toml
- ${TMP_TEST_FOLDER}/gitaly/gitaly2.config.toml
- ${TMP_TEST_FOLDER}/gitaly/internal/
- ${TMP_TEST_FOLDER}/gitaly/internal_gitaly2/
- ${TMP_TEST_FOLDER}/gitaly/internal_sockets/
- ${TMP_TEST_FOLDER}/gitaly/Makefile
- ${TMP_TEST_FOLDER}/gitaly/praefect.config.toml
- ${TMP_TEST_FOLDER}/gitaly/ruby/
- ${TMP_TEST_FOLDER}/gitlab-elasticsearch-indexer/bin/gitlab-elasticsearch-indexer
- ${TMP_TEST_FOLDER}/gitlab-shell/
- ${TMP_TEST_FOLDER}/gitlab-test-fork/
- ${TMP_TEST_FOLDER}/gitlab-test-fork_bare/
- ${TMP_TEST_FOLDER}/gitlab-test/
- ${TMP_TEST_FOLDER}/repositories/
- ${TMP_TEST_FOLDER}/second_storage/
- ${TMP_TEST_GITLAB_WORKHORSE_PATH}/
when: always
build-components:
extends:
- setup-test-env
- .rails:rules:build-components
script:
- source scripts/gitlab_workhorse_component_helpers.sh
- 'gitlab_workhorse_package_doesnt_exist || { echoinfo "INFO: Exiting early as package exists."; exit 0; }'
- run_timed_command "scripts/setup-test-env"
- run_timed_command "select_gitlab_workhorse_essentials"
- run_timed_command "create_gitlab_workhorse_package"
- run_timed_command "upload_gitlab_workhorse_package"
artifacts:
expire_in: 7d
paths:
- ${TMP_TEST_GITLAB_WORKHORSE_PATH}/
update-setup-test-env-cache:
extends:
- setup-test-env
......
......@@ -876,6 +876,16 @@
###############
# Rails rules #
###############
.rails:rules:build-components:
rules:
- <<: *if-dot-com-ee-schedule
- <<: *if-dot-com-gitlab-org-default-branch
changes:
- "workhorse/**/*"
- <<: *if-dot-com-gitlab-org-merge-request
when: manual
allow_failure: true
.rails:rules:setup-test-env:
rules:
- changes: *setup-test-env-patterns
......
......@@ -74,6 +74,7 @@ module Issuable
end
has_many :note_authors, -> { distinct }, through: :notes, source: :author
has_many :user_note_authors, -> { distinct.where("notes.system = false") }, through: :notes, source: :author
has_many :label_links, as: :target, inverse_of: :target
has_many :labels, through: :label_links
......
......@@ -362,3 +362,9 @@
- - :approve
- 0.0.62
- *2
- - :approve
- argparse
- :who: Lukas Eipert
:why: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79864#note_845406018
:versions: [2.0.1]
:when: 2022-02-24 10:44:26.669326000 Z
# 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
# 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
93960203e6703716f9c513dca340e17041a33792f9233dc4b7e35d1e19614191
\ No newline at end of file
406af18458c7f5ee8a4fa3860ed5fb87c358363926fed2830be8e8a55578822b
\ No newline at end of file
......@@ -14722,6 +14722,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))
);
......@@ -13,6 +13,7 @@ ignorecase: true
swap:
codequality: code quality
Customer [Pp]ortal: Customers Portal
disallow: prevent
frontmatter: front matter
GitLabber: GitLab team member
GitLabbers: GitLab team members
......
......@@ -26,7 +26,7 @@ Features behind flags can be gradually rolled out, typically:
1. The feature becomes enabled by default.
1. The feature flag is removed.
These features can be enabled and disabled to allow or disallow users to use
These features can be enabled and disabled to allow or prevent users from using
them. It can be done by GitLab administrators with access to GitLab Rails
console.
......
......@@ -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: <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"
}
```
......
......@@ -1366,6 +1366,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 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. |
| <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. |
......@@ -4756,6 +4757,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 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. |
| <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. |
......@@ -8873,6 +8875,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 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. |
| <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. |
......@@ -8908,6 +8911,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 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. |
| <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. |
......@@ -10415,6 +10419,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 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. |
| <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. |
......@@ -10450,6 +10455,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 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. |
| <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. |
......@@ -58,9 +58,9 @@ POST /projects/:id/approvals
| `id` | integer or string | yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding) |
| `approvals_before_merge` | integer | no | How many approvals are required before an MR can be merged. Deprecated in 12.0 in favor of Approval Rules API. |
| `reset_approvals_on_push` | boolean | no | Reset approvals on a new push |
| `disable_overriding_approvers_per_merge_request` | boolean | no | Allow/Disallow overriding approvers per MR |
| `merge_requests_author_approval` | boolean | no | Allow/Disallow authors from self approving merge requests; `true` means authors can self approve |
| `merge_requests_disable_committers_approval` | boolean | no | Allow/Disallow committers from self approving merge requests |
| `disable_overriding_approvers_per_merge_request` | boolean | no | Allow or prevent overriding approvers per MR |
| `merge_requests_author_approval` | boolean | no | Allow or prevent authors from self approving merge requests; `true` means authors can self approve |
| `merge_requests_disable_committers_approval` | boolean | no | Allow or prevent committers from self approving merge requests |
| `require_password_to_approve` | boolean | no | Require approver to enter a password to authenticate before adding the approval |
```json
......
......@@ -1130,6 +1130,17 @@ copy of `https://gitlab.com/gitlab-org/gitlab`, run in a terminal:
bin/pngquant compress doc/user/img
```
### Animated images
Sometimes an image with animation (such as an animated GIF)
can help the reader understand a complicated interaction with the user interface.
However, you should use them sparingly and avoid them when you can.
Do not use them to replace written descriptions of processes or the product.
If you include an animated image, follow the same size and naming conventions we use for images. If the animated image loops, add at least a three
second pause to the end of the loop.
## Videos
Adding GitLab YouTube video tutorials to the documentation is highly
......
......@@ -252,6 +252,11 @@ Do not use **Developer permissions**. A user who is assigned the Developer role
See [the Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/d/disable-disabled) for guidance on **disable**.
Use **inactive** or **off** instead. ([Vale](../testing.md#vale) rule: [`InclusionAbleism.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionAbleism.yml))
## disallow
Use **prevent** instead of **disallow**. ([Vale](../testing.md#vale) rule: [`Substitutions.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/Substitutions.yml))
## dropdown list
Use **dropdown list** to refer to the UI element. Do not use **dropdown** without **list** after it.
......
......@@ -122,7 +122,7 @@ Large parts of ActiveRecord's persistence API are built around the notion of cal
of these callbacks fire in response to model life cycle events such as `save` or `create`.
These callbacks cannot be used with bulk insertions, since they are meant to be called for
every instance that is saved or created. Since these events do not fire when
records are inserted in bulk, we currently disallow their use.
records are inserted in bulk, we currently prevent their use.
The specifics around which callbacks are explicitly allowed are defined in
[`BulkInsertSafe`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/concerns/bulk_insert_safe.rb).
......
......@@ -136,7 +136,7 @@ Mitigation strategies include:
1. Not allowing redirects to attacker controller resources:
[`Kubeclient::KubeClient`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/kubernetes/kube_client.rb#)
can be configured to disallow any redirects by passing in
can be configured to prevent any redirects by passing in
`http_max_redirects: 0` as an option.
1. Not exposing error messages: by doing so, we
prevent attackers from triggering errors to expose results from
......
......@@ -520,7 +520,7 @@ To install and run Snowplow Micro, complete these steps to modify the
### Troubleshoot
To control content security policy warnings when using an external host, modify `config/gitlab.yml`
to allow or disallow them. To allow them, add the relevant host for `connect_src`. For example, for
to allow or prevent them. To allow them, add the relevant host for `connect_src`. For example, for
`https://snowplow.trx.gitlab.net`:
```yaml
......
......@@ -74,7 +74,7 @@ The following are important notes about 2FA:
2FA enabled, 2FA is **not** required for those individually added members.
- If there are multiple 2FA requirements (for example, group + all users, or multiple
groups) the shortest grace period is used.
- It is possible to disallow subgroups from setting up their own 2FA requirements:
- It is possible to prevent subgroups from setting up their own 2FA requirements:
1. Go to the top-level group's **Settings > General**.
1. Expand the **Permissions and group features** section.
1. Uncheck the **Allow subgroups to set up their own two-factor authentication rule** field.
......
......@@ -65,7 +65,7 @@ This restriction also applies to projects forked from or to those groups.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34648) in GitLab 12.9.
Groups with group-managed accounts can disallow forking of projects to destinations outside the group.
Groups with group-managed accounts can prevent forking of projects to destinations outside the group.
To do so, enable the "Prohibit outer forks" option in **Settings > SAML SSO**.
When enabled **at the parent group level**, projects within the group can be forked
only to other destinations within the group (including its subgroups).
......
......@@ -80,6 +80,7 @@ class Groups::EpicsController < Groups::ApplicationController
def epic_params_attributes
[
:color,
:title,
:description,
:start_date_fixed,
......
......@@ -51,6 +51,11 @@ module Mutations
[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 @@ module Mutations
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
......@@ -160,6 +160,14 @@ module Types
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
......
......@@ -21,11 +21,19 @@ module EE
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 @@ module EE
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 @@ module EE
end
end
def text_color
if light_color?(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)
......
---
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
......@@ -91,6 +91,7 @@ module API
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 @@ module API
# 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 @@ module API
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 @@ module API
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)
......
......@@ -10,6 +10,8 @@ module EE
expose :id
expose :iid
expose :color
expose :text_color
expose :group_id
expose :parent_id
expose :parent_iid do |epic|
......
......@@ -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",
......
......@@ -62,7 +62,9 @@
"parent": { "type": "uri" },
"additionalProperties": false
}
}
},
"color": { "type": "string" },
"text_color": { "type": "string" }
},
"required": [
"id", "iid", "group_id", "title", "confidential", "_links"
......
......@@ -14,7 +14,7 @@ RSpec.describe GitlabSchema.types['Epic'] do
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
......
......@@ -147,7 +147,8 @@ RSpec.describe Gitlab::Graphql::Loaders::BulkEpicAggregateLoader do
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
......@@ -818,7 +818,8 @@ RSpec.describe Epic do
"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 @@ RSpec.describe Epic do
"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 @@ RSpec.describe Epic do
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
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :requires_admin, :actioncable, :orchestrated do
RSpec.describe 'Plan', :requires_admin, :actioncable, :orchestrated, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/293699', type: :bug } do
describe 'Assignees' do
let(:user1) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:user2) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }
......
#!/usr/bin/env bash
set -euo pipefail
export CURL_TOKEN_HEADER="${CURL_TOKEN_HEADER:-"JOB-TOKEN"}"
export GITLAB_WORKHORSE_BINARIES_LIST="gitlab-resize-image gitlab-zip-cat gitlab-zip-metadata gitlab-workhorse"
export GITLAB_WORKHORSE_PACKAGE_FILES_LIST="${GITLAB_WORKHORSE_BINARIES_LIST} WORKHORSE_TREE"
export GITLAB_WORKHORSE_TREE=${GITLAB_WORKHORSE_TREE:-$(git rev-parse HEAD:workhorse)}
export GITLAB_WORKHORSE_PACKAGE="workhorse-${GITLAB_WORKHORSE_TREE}.tar.gz"
export GITLAB_WORKHORSE_PACKAGE_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${GITLAB_WORKHORSE_FOLDER}/${GITLAB_WORKHORSE_TREE}/${GITLAB_WORKHORSE_PACKAGE}"
function gitlab_workhorse_archive_doesnt_exist() {
local package_url="${GITLAB_WORKHORSE_PACKAGE_URL}"
status=$(curl -I --silent --retry 3 --output /dev/null -w "%{http_code}" "${package_url}")
[[ "${status}" != "200" ]]
}
function create_gitlab_workhorse_package() {
local archive_filename="${GITLAB_WORKHORSE_PACKAGE}"
local folder_to_archive="${GITLAB_WORKHORSE_FOLDER}"
local workhorse_folder_path="${TMP_TEST_GITLAB_WORKHORSE_PATH}"
local tar_working_folder="${TMP_TEST_FOLDER}"
echoinfo "Running 'tar -czvf ${archive_filename} -C ${tar_working_folder} ${folder_to_archive}'"
tar -czvf ${archive_filename} -C ${tar_working_folder} ${folder_to_archive}
du -h ${archive_filename}
}
function extract_gitlab_workhorse_package() {
local tar_working_folder="${TMP_TEST_FOLDER}"
echoinfo "Extracting archive to ${tar_working_folder}"
tar -xzv -C ${tar_working_folder} < /dev/stdin
}
function upload_gitlab_workhorse_package() {
local archive_filename="${GITLAB_WORKHORSE_PACKAGE}"
local package_url="${GITLAB_WORKHORSE_PACKAGE_URL}"
local token_header="${CURL_TOKEN_HEADER}"
local token="${CI_JOB_TOKEN}"
echoinfo "Uploading ${archive_filename} to ${package_url} ..."
curl --fail --silent --retry 3 --header "${token_header}: ${token}" --upload-file "${archive_filename}" "${package_url}"
}
function read_curl_gitlab_workhorse_package() {
local package_url="${GITLAB_WORKHORSE_PACKAGE_URL}"
local token_header="${CURL_TOKEN_HEADER}"
local token="${CI_JOB_TOKEN}"
echoinfo "Downloading from ${package_url} ..."
curl --fail --silent --retry 3 --header "${token_header}: ${token}" "${package_url}"
}
function download_and_extract_gitlab_workhorse_package() {
read_curl_gitlab_workhorse_package | extract_gitlab_workhorse_package
}
function select_gitlab_workhorse_essentials() {
local tmp_path="${CI_PROJECT_DIR}/tmp/${GITLAB_WORKHORSE_FOLDER}"
local original_gitlab_workhorse_path="${TMP_TEST_GITLAB_WORKHORSE_PATH}"
mkdir -p ${tmp_path}
cd ${original_gitlab_workhorse_path} && mv ${GITLAB_WORKHORSE_PACKAGE_FILES_LIST} ${tmp_path} && cd -
rm -rf ${original_gitlab_workhorse_path}
# Move the temp folder to its final destination
mv ${tmp_path} ${TMP_TEST_FOLDER}
}
......@@ -83,7 +83,7 @@ function install_junit_merge_gem() {
function run_timed_command() {
local cmd="${1}"
local metric_name="${2}"
local metric_name="${2:-no}"
local timed_metric_file
local start=$(date +%s)
......@@ -97,7 +97,7 @@ function run_timed_command() {
if [[ $ret -eq 0 ]]; then
echosuccess "==> '${cmd}' succeeded in ${runtime} seconds."
if [[ -n "${metric_name}" ]]; then
if [[ "${metric_name}" != "no" ]]; then
timed_metric_file=$(timed_metric_file $metric_name)
echo "# TYPE ${metric_name} gauge" > "${timed_metric_file}"
echo "# UNIT ${metric_name} seconds" >> "${timed_metric_file}"
......@@ -132,9 +132,9 @@ function timed_metric_file() {
}
function echoerr() {
local header="${2}"
local header="${2:-no}"
if [ -n "${header}" ]; then
if [ "${header}" != "no" ]; then
printf "\n\033[0;31m** %s **\n\033[0m" "${1}" >&2;
else
printf "\033[0;31m%s\n\033[0m" "${1}" >&2;
......@@ -142,9 +142,9 @@ function echoerr() {
}
function echoinfo() {
local header="${2}"
local header="${2:-no}"
if [ -n "${header}" ]; then
if [ "${header}" != "no" ]; then
printf "\n\033[0;33m** %s **\n\033[0m" "${1}" >&2;
else
printf "\033[0;33m%s\n\033[0m" "${1}" >&2;
......@@ -152,9 +152,9 @@ function echoinfo() {
}
function echosuccess() {
local header="${2}"
local header="${2:-no}"
if [ -n "${header}" ]; then
if [ "${header}" != "no" ]; then
printf "\n\033[0;32m** %s **\n\033[0m" "${1}" >&2;
else
printf "\033[0;32m%s\n\033[0m" "${1}" >&2;
......
......@@ -55,6 +55,7 @@ issues:
- status_page_published_incident
- namespace
- note_authors
- user_note_authors
- issue_email_participants
- test_reports
- requirement
......@@ -200,6 +201,7 @@ merge_requests:
- user_mentions
- system_note_metadata
- note_authors
- user_note_authors
- cleanup_schedule
- compliance_violations
external_pull_requests:
......@@ -774,6 +776,7 @@ epic:
- resource_state_events
- user_mentions
- note_authors
- user_note_authors
- boards_epic_user_preferences
- epic_board_positions
epic_issue:
......
......@@ -857,6 +857,7 @@ Epic:
- health_status
- external_key
- confidential
- color
EpicIssue:
- id
- relative_position
......
......@@ -18,7 +18,6 @@ RSpec.describe Issuable do
it { is_expected.to have_many(:notes).dependent(:destroy) }
it { is_expected.to have_many(:todos) }
it { is_expected.to have_many(:labels) }
it { is_expected.to have_many(:note_authors).through(:notes) }
context 'Notes' do
let!(:note) { create(:note, noteable: issue, project: issue.project) }
......@@ -28,6 +27,23 @@ RSpec.describe Issuable do
expect(issue.notes).not_to be_authors_loaded
expect(scoped_issue.notes).to be_authors_loaded
end
describe 'note_authors' do
it { is_expected.to have_many(:note_authors).through(:notes) }
end
describe 'user_note_authors' do
let_it_be(:system_user) { create(:user) }
let!(:system_note) { create(:system_note, author: system_user, noteable: issue, project: issue.project) }
it 'filters the authors to those of user notes' do
authors = issue.user_note_authors
expect(authors).to include(note.author)
expect(authors).not_to include(system_user)
end
end
end
end
......
......@@ -1542,10 +1542,10 @@
dom-accessibility-api "^0.5.1"
pretty-format "^26.4.2"
"@tiptap/core@^2.0.0-beta.171":
version "2.0.0-beta.171"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.171.tgz#e681964c443383b81d2638c51fc3bbfda034a4fb"
integrity sha512-4CdJfcchmBOFooWPBMJ7AxJISeTstMFriQv0RyReMt0Dpef/c9UoU+NkKLwwv5VRUX0M8dL5SzEhkB8wIODqlA==
"@tiptap/core@^2.0.0-beta.174":
version "2.0.0-beta.174"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.174.tgz#cfdf16b7d7401e4b255dc69147d784f5f537b942"
integrity sha512-APQDto40PdvagG1HTwkKlieQS4Vp6GXNe7qgV1Qo2QCgJCLyxc/fXCTghtrOx0CQb+9JT7fjSLZxbSyUFXjx7Q==
dependencies:
"@types/prosemirror-commands" "^1.0.4"
"@types/prosemirror-keymap" "^1.0.4"
......@@ -1567,10 +1567,10 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.26.tgz#e5ae4b7bd9376db37407a23e22080c7b11287f3b"
integrity sha512-A6yjcYovONJfOjQFk6vDYXswaCdCtCwjL7w9VTB0R2DLTuJvvRt9DWN0IDcMrj5G+aMgDq4GUUTitv+2Y8krDg==
"@tiptap/extension-bold@^2.0.0-beta.25":
version "2.0.0-beta.25"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.25.tgz#ec19e7c862d25bae49609c5d6a873f372c506dee"
integrity sha512-ZNdgFYDxKo8lAp0Pqzu45I0JH3ah8/X5TCYg9zNg3QwLUFT16g2LlWDMUDGT5pH9aXxgtFaEdoVacu0EyhlPnQ==
"@tiptap/extension-bold@^2.0.0-beta.26":
version "2.0.0-beta.26"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.26.tgz#aa1c7850df28cec8e0614fde437183bd4ae3e66b"
integrity sha512-pnO0I5sEQM3pmowjMGQ74adLzvc6HqGyLyqMizaGMicPu9uTYlSdId+qckYEEgPwPMaEShtv2Vg+ZHs7KVqfcg==
"@tiptap/extension-bubble-menu@^2.0.0-beta.55":
version "2.0.0-beta.55"
......@@ -1665,15 +1665,15 @@
dependencies:
prosemirror-state "^1.3.4"
"@tiptap/extension-image@^2.0.0-beta.25":
version "2.0.0-beta.25"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.25.tgz#7fb001a6449a9a841ae4f42c258ad6a06022b523"
integrity sha512-RgW5jFVS2QNDvFhBOz7H1hY6LjYcbVAa/mE4F4c3RPg3o7GJZXNoL9s+k0QkEM2GXAvY6fX+OICMBn8TSENXKA==
"@tiptap/extension-image@^2.0.0-beta.27":
version "2.0.0-beta.27"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.27.tgz#62152240cfa7ead03080c38485c1ebda4a603d18"
integrity sha512-kdJ7V39yNdVWUco/RBe7WgvFevd81l+pU6+Je9HpelqBBP953wDttzLMuAWQB4AeLv9WhKSlORHiFv2SKsV5NA==
"@tiptap/extension-italic@^2.0.0-beta.25":
version "2.0.0-beta.25"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.25.tgz#c2ec95cc5baf855134883c5e261da4ab0d3b9479"
integrity sha512-7PvhioTX9baVp5+AmmZU0qna+dFPZCRlSEN/GciH57N77d2uhJ/ZW5iQWTbvy5HBNddQB4Jts1UDIaC7WASrGA==
"@tiptap/extension-italic@^2.0.0-beta.26":
version "2.0.0-beta.26"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.26.tgz#b00c9e32b81b1bd94eaed24bb2a22e44d5dc54a3"
integrity sha512-vejGe2ra4K5ipFOn1U9viqF9X9nPTX8WSJpSOux+9UbKjHpANy7bz69tp66OIi/Wh5L/MMDc+luH/04qfVnpZw==
"@tiptap/extension-link@^2.0.0-beta.36":
version "2.0.0-beta.36"
......@@ -5130,11 +5130,6 @@ entities@^2.0.0, entities@~2.1.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
entities@~2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==
envinfo@^7.7.3:
version "7.8.1"
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
......@@ -7866,13 +7861,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
linkify-it@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
dependencies:
uc.micro "^1.0.1"
linkify-it@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8"
......@@ -8192,7 +8180,7 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
markdown-it@12.3.2:
markdown-it@12.3.2, markdown-it@^12.0.0:
version "12.3.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90"
integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==
......@@ -8203,17 +8191,6 @@ markdown-it@12.3.2:
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-it@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==
dependencies:
argparse "^1.0.7"
entities "~2.0.0"
linkify-it "^2.0.0"
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdownlint-cli@0.31.0:
version "0.31.0"
resolved "https://registry.yarnpkg.com/markdownlint-cli/-/markdownlint-cli-0.31.0.tgz#a44264a71066475228292b7af19d3d18b827676d"
......@@ -9667,12 +9644,12 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.1.4,
prosemirror-state "^1.0.0"
w3c-keyname "^2.2.0"
prosemirror-markdown@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.6.0.tgz#141c88e03c8892f2e93cf58b1382ab0b6088d012"
integrity sha512-y/gRpJIIrNArtkyMax7ypYafb+ZMjddbVHI+AwlcUfCLCCXK57cOmfBMKYVq9kdEKJYVdYHdoyWsVNn1nWLHUg==
prosemirror-markdown@1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.7.1.tgz#811a4846da1b3bb661ceb4b37efb89cc4f0cd4b8"
integrity sha512-d1lNRPlbwuncErkR/fv2n3ZEhAoS+0udByM2mZkyDHeY3ux3bUnj7J/ep1XS0FiXUjffixTKI0IAax3H82JbEg==
dependencies:
markdown-it "^10.0.0"
markdown-it "^12.0.0"
prosemirror-model "^1.0.0"
prosemirror-model@^1.0.0, prosemirror-model@^1.13.1, prosemirror-model@^1.16.0, prosemirror-model@^1.16.1, prosemirror-model@^1.2.0, prosemirror-model@^1.8.1:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment