diff --git a/.rubocop_todo/lint/mixed_regexp_capture_types.yml b/.rubocop_todo/lint/mixed_regexp_capture_types.yml index 70f3773eb3172a6f0a0aefebe51997444cb94780..b2af57158dee348f351395259076006462f6d045 100644 --- a/.rubocop_todo/lint/mixed_regexp_capture_types.yml +++ b/.rubocop_todo/lint/mixed_regexp_capture_types.yml @@ -2,6 +2,7 @@ Lint/MixedRegexpCaptureTypes: Exclude: - 'app/models/alert_management/alert.rb' + - 'app/models/integrations/clickup.rb' - 'app/models/integrations/ewm.rb' - 'app/uploaders/file_uploader.rb' - 'ee/lib/gitlab/code_owners/reference_extractor.rb' diff --git a/.rubocop_todo/style/numbered_parameters.yml b/.rubocop_todo/style/numbered_parameters.yml index fc08515bc2f7b274ccb4eaf4de429ebfef294a0a..d73fab2a86aba928df09c24d10e5882fc9015977 100644 --- a/.rubocop_todo/style/numbered_parameters.yml +++ b/.rubocop_todo/style/numbered_parameters.yml @@ -15,6 +15,7 @@ Style/NumberedParameters: - 'app/models/concerns/integrations/reset_secret_fields.rb' - 'app/models/hooks/web_hook.rb' - 'app/models/integration.rb' + - 'app/models/integrations/clickup.rb' - 'app/models/integrations/datadog.rb' - 'app/models/integrations/youtrack.rb' - 'app/models/project.rb' diff --git a/app/models/integration.rb b/app/models/integration.rb index 860739fe5aa4ff1aaa21be20c8d5ce4809e068bf..0f7168620329f0f530499831107d8c5f9202653a 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -18,7 +18,7 @@ class Integration < ApplicationRecord self.inheritance_column = :type_new INTEGRATION_NAMES = %w[ - asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord + asana assembla bamboo bugzilla buildkite campfire clickup confluence custom_issue_tracker datadog discord drone_ci emails_on_push ewm external_wiki hangouts_chat harbor irker jira mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands squash_tm teamcity diff --git a/app/models/integrations/clickup.rb b/app/models/integrations/clickup.rb new file mode 100644 index 0000000000000000000000000000000000000000..7cc05d41e14e96012ced78339d6a09e154826a2a --- /dev/null +++ b/app/models/integrations/clickup.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Integrations + class Clickup < BaseIssueTracker + include HasIssueTrackerFields + + validates :project_url, :issues_url, presence: true, public_url: true, if: :activated? + + def reference_pattern(*) + @reference_pattern ||= /((#|CU-)(?[a-z0-9]+)|(?[A-Z0-9_]{2,10}-\d+))\b/ + end + + def title + 'ClickUp' + end + + def description + s_("IssueTracker|Use Clickup as this project's issue tracker.") + end + + def help + docs_link = ActionController::Base.helpers.link_to _('Learn more.'), + Rails.application.routes.url_helpers.help_page_url('user/project/integrations/clickup'), + target: '_blank', + rel: 'noopener noreferrer' + format(s_( + "IssueTracker|Use ClickUp as this project's issue tracker. %{docs_link}" + ).html_safe, docs_link: docs_link.html_safe) + end + + def self.to_param + 'clickup' + end + + def fields + super.select { _1.name.in?(%w[project_url issues_url]) } + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index d8818d9c174e3d95fef96e1fd584f82e3ceea7d9..b36e33b16bbe4c5ab8432fd5d675b7c6d2ecffb8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -185,6 +185,7 @@ def self.integration_association_name(name) has_one :bugzilla_integration, class_name: 'Integrations::Bugzilla' has_one :buildkite_integration, class_name: 'Integrations::Buildkite' has_one :campfire_integration, class_name: 'Integrations::Campfire' + has_one :clickup_integration, class_name: 'Integrations::Clickup' has_one :confluence_integration, class_name: 'Integrations::Confluence' has_one :custom_issue_tracker_integration, class_name: 'Integrations::CustomIssueTracker' has_one :datadog_integration, class_name: 'Integrations::Datadog' diff --git a/config/metrics/counts_all/20230515153810_groups_clickup_active.yml b/config/metrics/counts_all/20230515153810_groups_clickup_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..b5d999e76fd3856a100d22ebc7adc968dc2577bf --- /dev/null +++ b/config/metrics/counts_all/20230515153810_groups_clickup_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.groups_clickup_active +description: Count of groups with active integrations for ClickUp +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.1" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20230515153826_groups_inheriting_clickup_active.yml b/config/metrics/counts_all/20230515153826_groups_inheriting_clickup_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..66a1efdc47be02d5a283c5902088b643da350868 --- /dev/null +++ b/config/metrics/counts_all/20230515153826_groups_inheriting_clickup_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.groups_inheriting_clickup_active +description: Count of active groups inheriting integrations for ClickUp +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.1" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20230515153827_instances_clickup_active.yml b/config/metrics/counts_all/20230515153827_instances_clickup_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..de631475e5d5fa145394edc864f9d5e49c86b0c8 --- /dev/null +++ b/config/metrics/counts_all/20230515153827_instances_clickup_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.instances_clickup_active +description: Count of active instance-level integrations for ClickUp +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.1" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20230515153829_projects_clickup_active.yml b/config/metrics/counts_all/20230515153829_projects_clickup_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..4f7c8775614a1bc4523595a02dc7c4ff6563c1b7 --- /dev/null +++ b/config/metrics/counts_all/20230515153829_projects_clickup_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.projects_clickup_active +description: Count of projects with active integrations for ClickUp +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.1" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20230515153834_projects_inheriting_clickup_active.yml b/config/metrics/counts_all/20230515153834_projects_inheriting_clickup_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..1e45317f2a5a4f8f9cc5f5a0d7a4b6ddd83e4802 --- /dev/null +++ b/config/metrics/counts_all/20230515153834_projects_inheriting_clickup_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.projects_inheriting_clickup_active +description: Count of active projects inheriting integrations for ClickUp +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.1" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index c068397c50b2cab20dee842d62b6576673c5df2d..56bce1dfde2e41d7791022606a74d35fc0f96b3e 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -25276,6 +25276,7 @@ State of a Sentry error. | `BUGZILLA_SERVICE` | BugzillaService type. | | `BUILDKITE_SERVICE` | BuildkiteService type. | | `CAMPFIRE_SERVICE` | CampfireService type. | +| `CLICKUP_SERVICE` | ClickupService type. | | `CONFLUENCE_SERVICE` | ConfluenceService type. | | `CUSTOM_ISSUE_TRACKER_SERVICE` | CustomIssueTrackerService type. | | `DATADOG_SERVICE` | DatadogService type. | diff --git a/doc/api/integrations.md b/doc/api/integrations.md index 16ca7d74510ad138fb9c7a2475c3ed6e88b10f81..7e2e7232338a5620517430d24373f36ccfe9ccc2 100644 --- a/doc/api/integrations.md +++ b/doc/api/integrations.md @@ -334,6 +334,43 @@ Get Campfire integration settings for a project. GET /projects/:id/integrations/campfire ``` +## ClickUp + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732) in GitLab 16.1. + +ClickUp issue tracker. + +### Create or edit ClickUp integration + +Set up ClickUp integration for a project. + +```plaintext +PUT /projects/:id/integrations/clickup +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `issues_url` | string | true | Issue URL | +| `project_url` | string | true | Project URL | + +### Disable ClickUp integration + +Disable the ClickUp integration for a project. Integration settings are reset. + +```plaintext +DELETE /projects/:id/integrations/clickup +``` + +### Get ClickUp integration settings + +Get ClickUp integration settings for a project. + +```plaintext +GET /projects/:id/integrations/clickup +``` + ## Datadog Datadog system monitoring. diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md index c63c2e3fd24c890cbca51c4fa2a317fa6c3dfdfb..04073ad611f003cb8d81682743daa84657cd5674 100644 --- a/doc/integration/external-issue-tracker.md +++ b/doc/integration/external-issue-tracker.md @@ -28,6 +28,7 @@ To enable an external issue tracker, you must configure the appropriate [integra The following external issue tracker integrations are available: - [Bugzilla](../user/project/integrations/bugzilla.md) +- [ClickUp](../user/project/integrations/clickup.md) - [Custom Issue Tracker](../user/project/integrations/custom_issue_tracker.md) - [Engineering Workflow Management](../user/project/integrations/ewm.md) - [Jira](../integration/jira/index.md) diff --git a/doc/user/project/integrations/clickup.md b/doc/user/project/integrations/clickup.md new file mode 100644 index 0000000000000000000000000000000000000000..255f0c3f56b57a4fed74d64b8683b9d60d6a8017 --- /dev/null +++ b/doc/user/project/integrations/clickup.md @@ -0,0 +1,53 @@ +--- +stage: Manage +group: Import and Integrate +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# ClickUp **(FREE)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120732) in GitLab 16.1. + +You can use [ClickUp](https://clickup.com/) as an external issue tracker. +To enable the ClickUp integration in a project: + +1. On the top bar, select **Main menu > Projects** and find your project. +1. On the left sidebar, select **Settings > Integrations**. +1. Select **ClickUp**. +1. Select the checkbox under **Enable integration**. +1. Fill in the required fields: + + - **Project URL**: The URL to the ClickUp project to link to this GitLab project. + - **Issue URL**: The URL to the ClickUp project issue to link to this GitLab project. + The URL must contain `:id`. GitLab replaces this ID with the issue number. + +1. Select **Save changes** or optionally select **Test settings**. + +After you have configured and enabled ClickUp, you see the ClickUp link on the GitLab project pages, +which takes you to your ClickUp project. + +For example, this is a configuration for a project named `gitlab-ci`: + +- Project URL: `https://app.clickup.com/1234567` +- Issue URL: `https://app.clickup.com/t/:id` + +You can also disable [GitLab internal issue tracking](../issues/index.md) in this project. +For more information about the steps and consequences of disabling GitLab issues, see +[Configure project visibility, features, and permissions](../settings/index.md#configure-project-visibility-features-and-permissions). + +## Reference ClickUp issues in GitLab + +You can reference your ClickUp issues using: + +- `#`, where `` is a alphanumerical string (example `#8wrtcd932`). +- `CU-`, where `` is a alphanumerical string (example `CU-8wrtcd932`). +- `-`, for example `API_32-143`, where: + - `` is a ClickUp list custom prefix ID. + - `` is a number. + +In links, the `CU-` part is ignored and it links to the global URL of the issue. When a custom +prefix is used in a ClickUp list, the prefix part is part of the link. + +We suggest using the `CU-` format (`CU-`) if you have both internal and external issue +trackers enabled. If you use the shorter format, and an issue with the same ID exists in the +internal issue tracker, the internal issue is linked. diff --git a/doc/user/project/integrations/index.md b/doc/user/project/integrations/index.md index f7019b2eeb2ee463e36520dee32b1592512268ce..3bdb6ada00e968b283134568e095fabaf4f48929 100644 --- a/doc/user/project/integrations/index.md +++ b/doc/user/project/integrations/index.md @@ -50,6 +50,7 @@ You can configure the following integrations. | [Bugzilla](bugzilla.md) | Use Bugzilla as the issue tracker. | **{dotted-circle}** No | | Buildkite | Run CI/CD pipelines with Buildkite. | **{check-circle}** Yes | | Campfire | Connect to chat. | **{dotted-circle}** No | +| [ClickUp](clickup.md) | Use ClickUp as the issue tracker. | **{dotted-circle}** No | | [Confluence Workspace](../../../api/integrations.md#confluence-integration) | Use Confluence Cloud Workspace as an internal wiki. | **{dotted-circle}** No | | [Custom issue tracker](custom_issue_tracker.md) | Use a custom issue tracker. | **{dotted-circle}** No | | [Datadog](../../../integration/datadog.md) | Trace your GitLab pipelines with Datadog. | **{check-circle}** Yes | diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index 701418931f359de68f09369c01dda3055b0b7a01..6345c72ca32a1e726d87d81bd5ab32175144d5b0 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -839,6 +839,20 @@ def self.integrations desc: 'The issues URL' } ], + 'clickup' => [ + { + required: true, + name: :project_url, + type: String, + desc: 'The project URL' + }, + { + required: true, + name: :issues_url, + type: String, + desc: 'The issues URL' + } + ], 'slack' => [ chat_notification_settings, chat_notification_flags, @@ -968,6 +982,7 @@ def self.integration_classes ::Integrations::Bugzilla, ::Integrations::Buildkite, ::Integrations::Campfire, + ::Integrations::Clickup, ::Integrations::Confluence, ::Integrations::CustomIssueTracker, ::Integrations::Datadog, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 84729dba6ef8c8bfda45c2cc6322d2f44ced038e..97dac5edbd3331627826b6e155efb78dad6908f1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24788,6 +24788,12 @@ msgstr "" msgid "IssueTracker|Use Bugzilla as this project's issue tracker. %{docs_link}" msgstr "" +msgid "IssueTracker|Use ClickUp as this project's issue tracker. %{docs_link}" +msgstr "" + +msgid "IssueTracker|Use Clickup as this project's issue tracker." +msgstr "" + msgid "IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker." msgstr "" diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index 10568d7f1cd32d6ebf38c5d8e9061bd873936d12..74545c6eec16b8bcdc967b68d52a1f3ee084127d 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -197,6 +197,12 @@ issue_tracker end + factory :clickup_integration, class: 'Integrations::Clickup' do + project + active { true } + issue_tracker + end + trait :issue_tracker do transient do create_data { true } diff --git a/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb b/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb index d2c48cb2af0bd10264301e3ecdb0bb47b9edfc13..9fc91e03c948b083c534718035bd3a7b5f973faf 100644 --- a/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb +++ b/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb @@ -89,4 +89,5 @@ def fill_form(disable: false, skip_new_issue_url: false) it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla' it_behaves_like 'external issue tracker activation', tracker: 'Custom issue tracker' it_behaves_like 'external issue tracker activation', tracker: 'EWM', skip_test: true + it_behaves_like 'external issue tracker activation', tracker: 'ClickUp', skip_new_issue_url: true end diff --git a/spec/graphql/types/projects/service_type_enum_spec.rb b/spec/graphql/types/projects/service_type_enum_spec.rb index 8b444a08c3b0e7f5f54d775b58290917d09040f3..a5b1ba24a44a909d34f330b9881922c2d2691f02 100644 --- a/spec/graphql/types/projects/service_type_enum_spec.rb +++ b/spec/graphql/types/projects/service_type_enum_spec.rb @@ -15,6 +15,7 @@ def core_service_enums BUGZILLA_SERVICE BUILDKITE_SERVICE CAMPFIRE_SERVICE + CLICKUP_SERVICE CONFLUENCE_SERVICE CUSTOM_ISSUE_TRACKER_SERVICE DATADOG_SERVICE diff --git a/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb index d40041d890e86c877138def592387f60abcbccec..79500f433946a45ebd90d368f81bad1647795f27 100644 --- a/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb @@ -184,6 +184,44 @@ end end + context "clickup project" do + before_all do + create(:clickup_integration, project: project) + end + + before do + project.update!(issues_enabled: false) + end + + context "with right markdown" do + let(:issue) { ExternalIssue.new("PRJ-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "with underscores in the prefix" do + let(:issue) { ExternalIssue.new("PRJ_1-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "with a hash prefix and alphanumeric" do + let(:issue) { ExternalIssue.new("#abcd123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "with prefix and alphanumeric" do + let(:issue) { ExternalIssue.new("CU-abcd123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + end + context "jira project" do let_it_be(:service) { create(:jira_integration, project: project) } diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8a2602ea9f6296dbc613bb239c5d3bbedd778116..344a26f7a3e5aef8be943580d4d179c2a24ebdb2 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -581,6 +581,7 @@ project: - custom_issue_tracker_integration - bugzilla_integration - ewm_integration +- clickup_integration - external_wiki_integration - mock_ci_integration - mock_monitoring_integration diff --git a/spec/models/integrations/clickup_spec.rb b/spec/models/integrations/clickup_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..f83fb3ddabc286341c4cc1a9a8f06d45517fd689 --- /dev/null +++ b/spec/models/integrations/clickup_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::Clickup, feature_category: :integrations do + describe 'Validations' do + context 'when integration is active' do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + end + + context 'when integration is inactive' do + before do + subject.active = false + end + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + end + end + + describe '#reference_pattern' do + it 'does allow project prefix on the reference' do + expect(subject.reference_pattern.match('PRJ-123')[:issue]).to eq('PRJ-123') + end + + it 'allows a hash with an alphanumeric key on the reference' do + expect(subject.reference_pattern.match('#abcd123')[:issue]).to eq('abcd123') + end + + it 'allows a global prefix with an alphanumeric key on the reference' do + expect(subject.reference_pattern.match('CU-abcd123')[:issue]).to eq('abcd123') + end + end + + describe '#fields' do + it 'only returns the project_url and issues_url fields' do + expect(subject.fields.pluck(:name)).to eq(%w[project_url issues_url]) + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 855c5f66554ccda25d8fbbd4f2e6791e6b4876aa..09d8546484192a4ffeeb2410c1b1ec156d4cdfa0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -76,6 +76,7 @@ it { is_expected.to have_one(:harbor_integration) } it { is_expected.to have_one(:redmine_integration) } it { is_expected.to have_one(:youtrack_integration) } + it { is_expected.to have_one(:clickup_integration) } it { is_expected.to have_one(:custom_issue_tracker_integration) } it { is_expected.to have_one(:bugzilla_integration) } it { is_expected.to have_one(:ewm_integration) }