diff --git a/app/models/integration.rb b/app/models/integration.rb index 50a3f53cfdcc21a7fd5ee5ad3c46992a9a4b05a1..51750ad6ab7e71be5f5bf4aae2811f3236747b96 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -22,7 +22,7 @@ class Integration < ApplicationRecord asana assembla bamboo bugzilla buildkite campfire clickup confluence custom_issue_tracker datadog diffblue_cover discord drone_ci emails_on_push ewm external_wiki gitlab_slack_application hangouts_chat harbor irker jira - mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email + mattermost mattermost_slash_commands microsoft_teams packagist phorge pipelines_email pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands squash_tm teamcity telegram unify_circuit webex_teams youtrack zentao ].freeze diff --git a/app/models/integrations/phorge.rb b/app/models/integrations/phorge.rb new file mode 100644 index 0000000000000000000000000000000000000000..822cbd8d07748d763e46ec7fe8496049294c5390 --- /dev/null +++ b/app/models/integrations/phorge.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Integrations + class Phorge < BaseIssueTracker + include HasIssueTrackerFields + + validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? + + # See https://we.phorge.it/source/phorge/browse/master/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php + # for a canonical source of the regular expression used to parse Phorge + # object references. + # + # > The "(?<![#@-])" prevents us from linking "#abcdef" or similar, and + # > "ABC-T1" (see T5714), and from matching "@T1" as a task (it is a user) + # > (see T9479). + # + # Note that object references in Phorge are prefixed with letters unique + # to their underlying application, so T123 (a Maniphest task) is + # distinct from D123 (a Differential patch). Keeping the T as part of + # the task ID is appropriate here as it leaves room for expanding + # reference parsing/linking to other types of Phorge entities. + # + # Also note, a prefix of # is being allowed here due to: 1) an assumed + # likelihood of use; and b) lack of collision with native GitLab issues + # since all Phorge identifiers have the application specific alpha prefix. + def reference_pattern(*) + @reference_pattern ||= /\b(?<![@-])(?<issue>T\d+)\b/ + end + + def self.title + 'Phorge' + end + + def self.description + s_("IssueTracker|Use Phorge as this project's issue tracker.") + end + + # rubocop:disable Rails/OutputSafety -- It is fine to call html_safe here + def self.help + docs_link = ActionController::Base.helpers.link_to _('Learn more.'), + Rails.application.routes.url_helpers.help_page_url('user/project/integrations/phorge'), + target: '_blank', + rel: 'noopener noreferrer' + + # rubocop:disable Gitlab/Rails/SafeFormat -- See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145863#note_1845580057 + format( + s_("IssueTracker|Use Phorge as this project's issue tracker. %{docs_link}").html_safe, + docs_link: docs_link.html_safe + ) + # rubocop:enable Gitlab/Rails/SafeFormat + end + # rubocop:enable Rails/OutputSafety + + def self.to_param + 'phorge' + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index eb1a6922db5a312027a4088a48335dbefb256b07..76dbd0547bbb998bd41c0ca1ea4878133365db6f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -234,6 +234,7 @@ def self.integration_association_name(name) has_one :mock_ci_integration, class_name: 'Integrations::MockCi' has_one :mock_monitoring_integration, class_name: 'Integrations::MockMonitoring' has_one :packagist_integration, class_name: 'Integrations::Packagist' + has_one :phorge_integration, class_name: 'Integrations::Phorge' has_one :pipelines_email_integration, class_name: 'Integrations::PipelinesEmail' has_one :pivotaltracker_integration, class_name: 'Integrations::Pivotaltracker' has_one :prometheus_integration, class_name: 'Integrations::Prometheus', inverse_of: :project diff --git a/config/metrics/counts_all/20240229180548_projects_phorge_active.yml b/config/metrics/counts_all/20240229180548_projects_phorge_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..20da23f37a960ce0cd51c67bab016359d4b40b0a --- /dev/null +++ b/config/metrics/counts_all/20240229180548_projects_phorge_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.projects_phorge_active +description: Count of groups with active integrations for Phorge +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.11" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145863 +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/20240301170823_projects_inheriting_phorge_active.yml b/config/metrics/counts_all/20240301170823_projects_inheriting_phorge_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..2ad59539695ee3a1d26498156540a30902e46c3c --- /dev/null +++ b/config/metrics/counts_all/20240301170823_projects_inheriting_phorge_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.projects_inheriting_phorge_active +description: Count of active projects inheriting integrations for Phorge +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.11" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145863 +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/20240301170844_instances_phorge_active.yml b/config/metrics/counts_all/20240301170844_instances_phorge_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..9ec941ab7814241ddc3671fbdb21bd238a2dce56 --- /dev/null +++ b/config/metrics/counts_all/20240301170844_instances_phorge_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.instances_phorge_active +description: Count of active instance-level integrations for Phorge +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.11" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145863 +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/20240301170915_groups_phorge_active.yml b/config/metrics/counts_all/20240301170915_groups_phorge_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..0471127bd23651be06a57c0cd7f04edfea546df5 --- /dev/null +++ b/config/metrics/counts_all/20240301170915_groups_phorge_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.groups_phorge_active +description: Count of groups with active integrations for Phorge +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.11" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145863 +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/20240301171403_groups_inheriting_phorge_active.yml b/config/metrics/counts_all/20240301171403_groups_inheriting_phorge_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..cb2fa8f789b12e3a43a4e5723a8f8b851033a56c --- /dev/null +++ b/config/metrics/counts_all/20240301171403_groups_inheriting_phorge_active.yml @@ -0,0 +1,21 @@ +--- +key_path: counts.groups_inheriting_phorge_active +description: Count of active groups inheriting integrations for Phorge +product_section: dev +product_stage: manage +product_group: integrations +value_type: number +status: active +milestone: "16.11" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145863 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt index 3543bdd77b27c1cb3119e9d01ab73e6ec4cdc18d..34acfe0c713beed413d90ad6198469a640155053 100644 --- a/doc/.vale/gitlab/spelling-exceptions.txt +++ b/doc/.vale/gitlab/spelling-exceptions.txt @@ -561,6 +561,7 @@ Mailroom Makefile Makefiles malloc +Maniphest Markdown markdownlint Marketo @@ -687,6 +688,7 @@ Phabricator phaser phasers phpenv +Phorge PHPUnit PIDs pipenv diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 7f29adac9f09f69372c06e284e434d8002658851..92188c9d643006fe291580fae013e02e928eefb3 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -33145,6 +33145,7 @@ State of a Sentry error. | <a id="servicetypemattermost_slash_commands_service"></a>`MATTERMOST_SLASH_COMMANDS_SERVICE` | MattermostSlashCommandsService type. | | <a id="servicetypemicrosoft_teams_service"></a>`MICROSOFT_TEAMS_SERVICE` | MicrosoftTeamsService type. | | <a id="servicetypepackagist_service"></a>`PACKAGIST_SERVICE` | PackagistService type. | +| <a id="servicetypephorge_service"></a>`PHORGE_SERVICE` | PhorgeService type. | | <a id="servicetypepipelines_email_service"></a>`PIPELINES_EMAIL_SERVICE` | PipelinesEmailService type. | | <a id="servicetypepivotaltracker_service"></a>`PIVOTALTRACKER_SERVICE` | PivotaltrackerService type. | | <a id="servicetypeprometheus_service"></a>`PROMETHEUS_SERVICE` | PrometheusService type. | diff --git a/doc/api/integrations.md b/doc/api/integrations.md index 338c689fffbc8cca02ee1f2db42a568df9c97553..c1fb27b2545d09a73034fd9eb34e68d41eefee16 100644 --- a/doc/api/integrations.md +++ b/doc/api/integrations.md @@ -1417,6 +1417,41 @@ Get the Packagist integration settings for a project. GET /projects/:id/integrations/packagist ``` +## Phorge + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145863) in GitLab 16.11. + +### Set up Phorge + +Set up the Phorge integration for a project. + +```plaintext +PUT /projects/:id/integrations/phorge +``` + +Parameters: + +| Parameter | Type | Required | Description | +|-----------------|--------|----------|-----------------------| +| `issues_url` | string | true | URL of the issue. | +| `project_url` | string | true | URL of the project. | + +### Disable Phorge + +Disable the Phorge integration for a project. Integration settings are reset. + +```plaintext +DELETE /projects/:id/integrations/phorge +``` + +### Get Phorge settings + +Get the Phorge integration settings for a project. + +```plaintext +GET /projects/:id/integrations/phorge +``` + ## Pipeline status emails ### Set up pipeline status emails diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md index c624597e00ae223ee43ed3cb077fd9de4603ab29..a8f5391038fbafd27eca0a106028ce0b1573e128 100644 --- a/doc/integration/external-issue-tracker.md +++ b/doc/integration/external-issue-tracker.md @@ -46,5 +46,6 @@ You can configure any of the following external issue trackers: - [Custom issue tracker](../user/project/integrations/custom_issue_tracker.md) - [Engineering Workflow Management (EWM)](../user/project/integrations/ewm.md) - [Jira](../integration/jira/index.md) +- [Phorge](../user/project/integrations/phorge.md) - [Redmine](../user/project/integrations/redmine.md) - [YouTrack](../user/project/integrations/youtrack.md) diff --git a/doc/user/project/integrations/index.md b/doc/user/project/integrations/index.md index ef6cb2a94ab36b2b14c287a40c615e671e5d7fd5..e88cc36cc9fdc2fb699d2b546bb887f03ea27d62 100644 --- a/doc/user/project/integrations/index.md +++ b/doc/user/project/integrations/index.md @@ -145,6 +145,7 @@ To use custom settings for a project or group integration: | [Mattermost slash commands](mattermost_slash_commands.md) | Run slash commands from a Mattermost chat environment. | **{dotted-circle}** No | | [Microsoft Teams notifications](microsoft_teams.md) | Receive event notifications in Microsoft Teams. | **{dotted-circle}** No | | Packagist | Update your PHP dependencies in Packagist. | **{check-circle}** Yes | +| [Phorge](phorge.md) | Use Phorge as an issue tracker. | **{dotted-circle}** No | | [Pipeline status emails](pipeline_status_emails.md) | Send the pipeline status to a list of recipients by email. | **{dotted-circle}** No | | [Pivotal Tracker](pivotal_tracker.md) | Add commit messages as comments to Pivotal Tracker stories. | **{dotted-circle}** No | | [Pumble](pumble.md) | Send event notifications to a Pumble channel. | **{dotted-circle}** No | diff --git a/doc/user/project/integrations/phorge.md b/doc/user/project/integrations/phorge.md new file mode 100644 index 0000000000000000000000000000000000000000..670f486e08b8fc5cdb133f6660a17bd0b6d7d333 --- /dev/null +++ b/doc/user/project/integrations/phorge.md @@ -0,0 +1,35 @@ +--- +stage: Manage +group: Import and Integrate +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Phorge + +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** GitLab.com, Self-managed, GitLab Dedicated + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145863) in GitLab 16.11. + +You can use [Phorge](https://we.phorge.it/) as an external issue tracker in GitLab. + +## Configure the integration + +To configure Phorge in a GitLab project: + +1. On the left sidebar, select **Search or go to** and find your project. +1. Select **Settings > Integrations**. +1. Select **Phorge**. +1. Under **Enable integration**, select the **Active** checkbox. +1. In **Project URL**, enter the URL to the Phorge project. +1. In **Issue URL**, enter the URL to the Phorge project issue. + The URL must contain `:id`. GitLab replaces this token with the Maniphest task ID (for example, `T123`). +1. In **New issue URL**, enter the URL to a new Phorge project issue. + To prefill tags related to this project, you can use `?tags=`. +1. Optional. Select **Test settings**. +1. Select **Save changes**. + +In that GitLab project, you can see a link to your Phorge project. +You can now reference your Phorge issues and tasks in GitLab with +`T<ID>`, where `<ID>` is a Maniphest task ID (for example, `T123`). diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index 769fb49af47d3e6f4abd24571162165660a7b23e..d7f1687a71b784798840e76bc6ae02e719c6a6e8 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -397,6 +397,7 @@ def self.integrations desc: 'The server' } ], + 'phorge' => ::Integrations::Phorge.api_fields, 'pipelines-email' => [ { required: true, @@ -658,6 +659,7 @@ def self.integration_classes ::Integrations::MattermostSlashCommands, ::Integrations::MicrosoftTeams, ::Integrations::Packagist, + ::Integrations::Phorge, ::Integrations::PipelinesEmail, ::Integrations::Pivotaltracker, ::Integrations::Prometheus, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2711e54096aa177f980c031314c9a7b3dfc0e614..f7391410ad60bc84c026f78421acfb6f5b3ce66d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -27973,6 +27973,12 @@ msgstr "" msgid "IssueTracker|Use JetBrains YouTrack as this project's issue tracker. %{docs_link}" msgstr "" +msgid "IssueTracker|Use Phorge as this project's issue tracker." +msgstr "" + +msgid "IssueTracker|Use Phorge as this project's issue tracker. %{docs_link}" +msgstr "" + msgid "IssueTracker|Use Redmine as the issue tracker. %{docs_link}" msgstr "" diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index 5978755e5f5d4f2a790d653a41760b300c4478f5..a7cb82cc886aea6fd3c72b15a3e84c5578e60cd9 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -82,6 +82,12 @@ server { 'https://packagist.example.comp' } end + factory :phorge_integration, class: 'Integrations::Phorge' do + project + active { true } + issue_tracker + end + factory :prometheus_integration, class: 'Integrations::Prometheus' do project active { 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 02cec948127bf3b254627d5f3488f91073137b21..693776b612c2ce2c9ae8c83666b5a0b9530320a7 100644 --- a/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb +++ b/spec/features/projects/integrations/user_activates_issue_tracker_spec.rb @@ -92,4 +92,5 @@ def fill_form(disable: false, skip_new_issue_url: false) 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 + it_behaves_like 'external issue tracker activation', tracker: 'Phorge' end diff --git a/spec/graphql/types/projects/service_type_enum_spec.rb b/spec/graphql/types/projects/service_type_enum_spec.rb index 40376afc7f78f3cf5ef287be0d1f60a0d3cdba4a..36c00212579d2d225c7aff623f6123a592f81f09 100644 --- a/spec/graphql/types/projects/service_type_enum_spec.rb +++ b/spec/graphql/types/projects/service_type_enum_spec.rb @@ -32,6 +32,7 @@ def core_service_enums MATTERMOST_SLASH_COMMANDS_SERVICE MICROSOFT_TEAMS_SERVICE PACKAGIST_SERVICE + PHORGE_SERVICE PIPELINES_EMAIL_SERVICE PIVOTALTRACKER_SERVICE PROMETHEUS_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 acc59c85cbf852884e3e484ae111b5db3f86b522..9ff298c32d29a1ff6d5a0c7d34bb6ae997d62573 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 @@ -325,6 +325,23 @@ end end + context "phorge project" do + before_all do + create(:phorge_integration, project: project) + end + + before do + project.update!(issues_enabled: false) + end + + context "with right markdown" do + let(:issue) { ExternalIssue.new("T123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + end + context 'checking N+1' do let_it_be(:integration) { create(:redmine_integration, project: project) } let_it_be(:issue1) { ExternalIssue.new("#123", project) } diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 69f32b4145f8092694284f204ea562d1781c6fb2..620980217d532532d382972ef07c4ae67826b9df 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -587,6 +587,7 @@ project: - harbor_integration - irker_integration - packagist_integration +- phorge_integration - pivotaltracker_integration - prometheus_integration - assembla_integration diff --git a/spec/models/integrations/phorge_spec.rb b/spec/models/integrations/phorge_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..895578ed9a8f7ff9f181a7ffe950ac0f4bd49bc0 --- /dev/null +++ b/spec/models/integrations/phorge_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::Phorge, feature_category: :integrations do + describe 'Validations' do + subject { build(:phorge_integration, active: active) } + + context 'when integration is active' do + let(:active) { true } + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url + end + + context 'when integration is inactive' do + let(:active) { false } + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end + + describe '#reference_pattern' do + using RSpec::Parameterized::TableSyntax + + let(:reference_pattern) { build(:phorge_integration).reference_pattern } + + subject { reference_pattern } + + context 'when text contains a Phorge Maniphest task reference' do + where(:text, :reference) do + 'Referencing T111' | 'T111' + 'Referencing T222, mid sentence' | 'T222' + 'Referencing (T333) in parentheses' | 'T333' + 'Referencing #T444 with a hash prefix' | 'T444' + end + + with_them do + it { is_expected.to match(text) } + + it 'captures the task reference' do + expect(reference_pattern.match(text)[:issue]).to eq(reference) + end + end + end + + context 'when text contains something resembling but is not a Phorge Maniphest task reference' do + where(:text) do + [ + 'See docs for Model-T1', + 'cc user @T1' + ] + end + + with_them do + it { is_expected.not_to match(text) } + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 6366e5c986322dd4626c445a51c7d8511925c613..c4f98d6581f8db5a69150668774fab6a649b4c9a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -66,6 +66,7 @@ it { is_expected.to have_one(:pumble_integration) } it { is_expected.to have_one(:webex_teams_integration) } it { is_expected.to have_one(:packagist_integration) } + it { is_expected.to have_one(:phorge_integration) } it { is_expected.to have_one(:pushover_integration) } it { is_expected.to have_one(:apple_app_store_integration) } it { is_expected.to have_one(:google_play_integration) }