diff --git a/app/controllers/concerns/integrations/params.rb b/app/controllers/concerns/integrations/params.rb index 65c33fbca0366f7dcca3f6ee86840c151aedb385..fc173a629229ca20bf26a64971a49d9b4b973040 100644 --- a/app/controllers/concerns/integrations/params.rb +++ b/app/controllers/concerns/integrations/params.rb @@ -100,6 +100,7 @@ module Params :user_key, :username, :webhook, + :workspace_url, :zentao_product_xid ].freeze diff --git a/app/models/concerns/integrations/base/integration.rb b/app/models/concerns/integrations/base/integration.rb index 0279ebbaf59d9cd68bb778b77ae9f367c1651eb0..5ebb7e37a973272d75605d7399783333fbf58451 100644 --- a/app/models/concerns/integrations/base/integration.rb +++ b/app/models/concerns/integrations/base/integration.rb @@ -11,7 +11,7 @@ module Integration INTEGRATION_NAMES = %w[ 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 matrix + gitlab_slack_application hangouts_chat harbor irker jira linear matrix 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 diff --git a/app/models/concerns/integrations/base/linear.rb b/app/models/concerns/integrations/base/linear.rb new file mode 100644 index 0000000000000000000000000000000000000000..0e49dc8c25d4eba483394e5deeff25ff4b725668 --- /dev/null +++ b/app/models/concerns/integrations/base/linear.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Integrations + module Base + module Linear + extend ActiveSupport::Concern + + include Base::IssueTracker + # TODO: Depends on https://gitlab.com/gitlab-org/gitlab-svgs/-/merge_requests/1219 + # include HasAvatar + + # Workspace keys in the URL must be between 3 and 32 characters long and + # contain only lowercase letters, numbers, and hyphens. + LINEAR_WORKSPACE_URI_REGEX = %r{\Ahttps://linear\.app/[a-z0-9\-]{3,32}/?\z} + + # References like FEAT-123. + # The project key part is between one and 7 characters long and uppercase letters + digits + LINEAR_ISSUE_REFERENCE = %r{\b(?[A-Z0-9]{1,7}-\d+)\b} + + class_methods do + def title + 'Linear' + end + + def description + s_("LinearIntegration|Use Linear as this project's issue tracker.") + end + + def help + build_help_page_url( + 'user/project/integrations/linear.md', description + ) + end + + def to_param + 'linear' + end + + def attribution_notice + s_('LinearIntegration|The Linear logo is a trademark of Linear Orbit Inc. in the U.S. and other countries.') + end + end + + included do + field :workspace_url, + title: -> { s_('LinearIntegration|Workspace URL') }, + placeholder: -> { s_('LinearIntegration|https://linear.app/example') }, + help: -> { s_('LinearIntegration|Linear workspace URL (for example, https://linear.app/example)') }, + required: true + + with_options if: :activated? do + validates :workspace_url, presence: true, public_url: true, format: { + with: LINEAR_WORKSPACE_URI_REGEX, + message: ->(_object, _data) { s_('LinearIntegration|URL must point to a workspace URL like https://linear.app/example') } + } + end + + def reference_pattern(*) + @reference_pattern ||= LINEAR_ISSUE_REFERENCE + end + + # Normally external issue trackers in GitLab save `project_url`, `issue_url` and `new_issue_url` + # in a separate DB table, but for the Linear integration we just use the `encrypted_properties` column + # + # That's why we overwrite `supports_data_fields?` and `handle_properties` + # + # For Linear only the workspace url is needed and the rest of the URLs can be derived + # + def supports_data_fields? + false + end + + def handle_properties + # no op + end + + def project_url + workspace_url + end + + def issues_url + @issues_url ||= Gitlab::Utils.append_path(workspace_url, '/issue/:id') + end + end + end + end +end diff --git a/app/models/integrations/linear.rb b/app/models/integrations/linear.rb new file mode 100644 index 0000000000000000000000000000000000000000..d7374ad48f1cf3e386d896154f47d75d443c23a0 --- /dev/null +++ b/app/models/integrations/linear.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Integrations + class Linear < Integration + include Integrations::Base::Linear + end +end diff --git a/app/models/project.rb b/app/models/project.rb index f1e90aecbe047199f4a33efa3bbe744fdb044f32..73b3ca17e3469e221db151d53b50403f05d0848f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -243,6 +243,7 @@ def self.integration_association_name(name) has_one :jenkins_integration, class_name: 'Integrations::Jenkins' has_one :jira_integration, class_name: 'Integrations::Jira' has_one :jira_cloud_app_integration, class_name: 'Integrations::JiraCloudApp' + has_one :linear_integration, class_name: 'Integrations::Linear' has_one :mattermost_integration, class_name: 'Integrations::Mattermost' has_one :mattermost_slash_commands_integration, class_name: 'Integrations::MattermostSlashCommands' has_one :matrix_integration, class_name: 'Integrations::Matrix' diff --git a/config/metrics/counts_all/20250622150122_projects_linear_active.yml b/config/metrics/counts_all/20250622150122_projects_linear_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..631070189b4e77e49dcfdeee57c6db4e320eef6a --- /dev/null +++ b/config/metrics/counts_all/20250622150122_projects_linear_active.yml @@ -0,0 +1,21 @@ +--- +data_category: optional +key_path: counts.projects_linear_active +description: Count of projects with active integrations for Linear +product_group: import +product_categories: + - integrations +value_type: number +status: active +time_frame: all +data_source: database +instrumentation_class: ActiveProjectIntegrationsMetric +options: + type: linear +tiers: + - free + - premium + - ultimate +performance_indicator_type: [] +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198297 diff --git a/config/metrics/counts_all/20250622150124_groups_linear_active.yml b/config/metrics/counts_all/20250622150124_groups_linear_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..a47d09a4c06cc614b02ce787d5de0877e1220d43 --- /dev/null +++ b/config/metrics/counts_all/20250622150124_groups_linear_active.yml @@ -0,0 +1,21 @@ +--- +data_category: optional +key_path: counts.groups_linear_active +description: Count of groups with active integrations for Linear +product_group: import +product_categories: + - integrations +value_type: number +status: active +time_frame: all +data_source: database +instrumentation_class: ActiveGroupIntegrationsMetric +options: + type: linear +tiers: + - free + - premium + - ultimate +performance_indicator_type: [] +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198297 diff --git a/config/metrics/counts_all/20250622150127_instances_linear_active.yml b/config/metrics/counts_all/20250622150127_instances_linear_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..31484c1856813322a8c874e290e8a9824f8ec795 --- /dev/null +++ b/config/metrics/counts_all/20250622150127_instances_linear_active.yml @@ -0,0 +1,21 @@ +--- +data_category: optional +key_path: counts.instances_linear_active +description: Count of active instance-level integrations for Linear +product_group: import +product_categories: + - integrations +value_type: number +status: active +time_frame: all +data_source: database +instrumentation_class: ActiveInstanceIntegrationsMetric +options: + type: linear +tiers: + - free + - premium + - ultimate +performance_indicator_type: [] +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198297 diff --git a/config/metrics/counts_all/20250622150129_projects_inheriting_linear_active.yml b/config/metrics/counts_all/20250622150129_projects_inheriting_linear_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..866a4b2a8d30b84d1e5b1d00ab688f8a75fee55c --- /dev/null +++ b/config/metrics/counts_all/20250622150129_projects_inheriting_linear_active.yml @@ -0,0 +1,21 @@ +--- +data_category: optional +key_path: counts.projects_inheriting_linear_active +description: Count of active projects inheriting integrations for Linear +product_group: import +product_categories: + - integrations +value_type: number +status: active +time_frame: all +data_source: database +instrumentation_class: ActiveProjectsInheritingIntegrationsMetric +options: + type: linear +tiers: + - free + - premium + - ultimate +performance_indicator_type: [] +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198297 diff --git a/config/metrics/counts_all/20250622150131_groups_inheriting_linear_active.yml b/config/metrics/counts_all/20250622150131_groups_inheriting_linear_active.yml new file mode 100644 index 0000000000000000000000000000000000000000..8f3bc73b0a80e8b32997b37f1b3cb51500b6a561 --- /dev/null +++ b/config/metrics/counts_all/20250622150131_groups_inheriting_linear_active.yml @@ -0,0 +1,21 @@ +--- +data_category: optional +key_path: counts.groups_inheriting_linear_active +description: Count of active groups inheriting integrations for Linear +product_group: import +product_categories: + - integrations +value_type: number +status: active +time_frame: all +data_source: database +instrumentation_class: ActiveGroupsInheritingIntegrationsMetric +options: + type: linear +tiers: + - free + - premium + - ultimate +performance_indicator_type: [] +milestone: '18.3' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198297 diff --git a/db/docs/integrations.yml b/db/docs/integrations.yml index ec141afe93f014c1e7980173ed526f77ed8ee993..1bed92a3c68d62e9d562eb0c1cd20e4dfe5d3688 100644 --- a/db/docs/integrations.yml +++ b/db/docs/integrations.yml @@ -33,6 +33,7 @@ classes: - Integrations::Jenkins - Integrations::Jira - Integrations::JiraCloudApp +- Integrations::Linear - Integrations::Matrix - Integrations::Mattermost - Integrations::MattermostSlashCommands diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 8eed056d29ba2b9b3b3296164c0cdf04f21764f4..dce744392e68382f449bd165b3edfd81d3ed593f 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -48414,6 +48414,7 @@ State of a Sentry error. | `JENKINS_SERVICE` | Jenkins integration. | | `JIRA_CLOUD_APP_SERVICE` | GitLab for Jira Cloud app integration. | | `JIRA_SERVICE` | Jira issues integration. | +| `LINEAR_SERVICE` | Linear integration. | | `MATRIX_SERVICE` | Matrix notifications integration. | | `MATTERMOST_SERVICE` | Mattermost notifications integration. | | `MATTERMOST_SLASH_COMMANDS_SERVICE` | Mattermost slash commands integration. | diff --git a/doc/api/group_integrations.md b/doc/api/group_integrations.md index fd9e503f0fc69681778c241f34c8359a8b62bfbb..d6adaf7b86b5a99b5bc72ad6c179a1b3c6666891 100644 --- a/doc/api/group_integrations.md +++ b/doc/api/group_integrations.md @@ -1183,6 +1183,45 @@ Get the Jira integration settings for a group. GET /groups/:id/integrations/jira ``` +## Linear + +{{< history >}} + +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198297) in GitLab 18.3. + +{{< /history >}} + +### Set up Linear + +Set up the Linear integration for a group. + +```plaintext +PUT /groups/:id/integrations/linear +``` + +Parameters: + +| Parameter | Type | Required | Description | +| ------------- | ------ | -------- | -------------- | +| `workspace_url` | string | yes | URL of the issue. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Linear + +Disable the Linear integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/linear +``` + +### Get Linear settings + +Get the Linear integration settings for a group. + +```plaintext +GET /groups/:id/integrations/linear +``` + ## Matrix notifications ### Set up Matrix notifications diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index 3d4d0e0472b17dc8189eff86da589647c7a710dc..2932dd09c37198d46719ed9a24a6257fd35a76e6 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -5274,6 +5274,41 @@ paths: tags: - integrations operationId: putApiV4GroupsIdIntegrationsJiraCloudApp + "/api/v4/groups/{id}/integrations/linear": + put: + summary: Create/Edit Linear integration + description: Set Linear integration. + produces: + - application/json + consumes: + - application/json + parameters: + - in: path + name: id + type: integer + format: int32 + required: true + - name: putApiV4GroupsIdIntegrationsLinear + in: body + required: true + schema: + "$ref": "#/definitions/putApiV4GroupsIdIntegrationsLinear" + responses: + '200': + description: Create/Edit Linear integration + schema: + "$ref": "#/definitions/API_Entities_IntegrationBasic" + '400': + description: Bad request + '401': + description: Unauthorized + '404': + description: Not found + '422': + description: Unprocessable entity + tags: + - integrations + operationId: putApiV4GroupsIdIntegrationsLinear "/api/v4/groups/{id}/integrations/matrix": put: summary: Create/Edit Matrix integration @@ -6290,6 +6325,7 @@ paths: - jenkins - jira - jira-cloud-app + - linear - matrix - mattermost-slash-commands - slack-slash-commands @@ -6370,6 +6406,7 @@ paths: - jenkins - jira - jira-cloud-app + - linear - matrix - mattermost-slash-commands - slack-slash-commands @@ -20348,6 +20385,41 @@ paths: tags: - integrations operationId: putApiV4ProjectsIdServicesJiraCloudApp + "/api/v4/projects/{id}/services/linear": + put: + summary: Create/Edit Linear integration + description: Set Linear integration. + produces: + - application/json + consumes: + - application/json + parameters: + - in: path + name: id + type: integer + format: int32 + required: true + - name: putApiV4ProjectsIdServicesLinear + in: body + required: true + schema: + "$ref": "#/definitions/putApiV4ProjectsIdServicesLinear" + responses: + '200': + description: Create/Edit Linear integration + schema: + "$ref": "#/definitions/API_Entities_IntegrationBasic" + '400': + description: Bad request + '401': + description: Unauthorized + '404': + description: Not found + '422': + description: Unprocessable entity + tags: + - integrations + operationId: putApiV4ProjectsIdServicesLinear "/api/v4/projects/{id}/services/matrix": put: summary: Create/Edit Matrix integration @@ -21364,6 +21436,7 @@ paths: - jenkins - jira - jira-cloud-app + - linear - matrix - mattermost-slash-commands - slack-slash-commands @@ -21444,6 +21517,7 @@ paths: - jenkins - jira - jira-cloud-app + - linear - matrix - mattermost-slash-commands - slack-slash-commands @@ -22381,6 +22455,41 @@ paths: tags: - integrations operationId: putApiV4ProjectsIdIntegrationsJiraCloudApp + "/api/v4/projects/{id}/integrations/linear": + put: + summary: Create/Edit Linear integration + description: Set Linear integration. + produces: + - application/json + consumes: + - application/json + parameters: + - in: path + name: id + type: integer + format: int32 + required: true + - name: putApiV4ProjectsIdIntegrationsLinear + in: body + required: true + schema: + "$ref": "#/definitions/putApiV4ProjectsIdIntegrationsLinear" + responses: + '200': + description: Create/Edit Linear integration + schema: + "$ref": "#/definitions/API_Entities_IntegrationBasic" + '400': + description: Bad request + '401': + description: Unauthorized + '404': + description: Not found + '422': + description: Unprocessable entity + tags: + - integrations + operationId: putApiV4ProjectsIdIntegrationsLinear "/api/v4/projects/{id}/integrations/matrix": put: summary: Create/Edit Matrix integration @@ -23397,6 +23506,7 @@ paths: - jenkins - jira - jira-cloud-app + - linear - matrix - mattermost-slash-commands - slack-slash-commands @@ -23477,6 +23587,7 @@ paths: - jenkins - jira - jira-cloud-app + - linear - matrix - mattermost-slash-commands - slack-slash-commands @@ -47704,6 +47815,22 @@ definitions: description: Indicates whether to inherit the default settings. Defaults to `false`. description: Create/Edit Jira Cloud App integration + putApiV4GroupsIdIntegrationsLinear: + type: object + properties: + workspace_url: + type: string + description: Linear workspace URL (for example, https://linear.app/example) + push_events: + type: boolean + description: Trigger event for pushes to the repository. + use_inherited_settings: + type: boolean + description: Indicates whether to inherit the default settings. Defaults to + `false`. + required: + - workspace_url + description: Create/Edit Linear integration putApiV4GroupsIdIntegrationsMatrix: type: object properties: @@ -54238,6 +54365,22 @@ definitions: description: Indicates whether to inherit the default settings. Defaults to `false`. description: Create/Edit Jira Cloud App integration + putApiV4ProjectsIdServicesLinear: + type: object + properties: + workspace_url: + type: string + description: Linear workspace URL (for example, https://linear.app/example) + push_events: + type: boolean + description: Trigger event for pushes to the repository. + use_inherited_settings: + type: boolean + description: Indicates whether to inherit the default settings. Defaults to + `false`. + required: + - workspace_url + description: Create/Edit Linear integration putApiV4ProjectsIdServicesMatrix: type: object properties: @@ -56064,6 +56207,22 @@ definitions: description: Indicates whether to inherit the default settings. Defaults to `false`. description: Create/Edit Jira Cloud App integration + putApiV4ProjectsIdIntegrationsLinear: + type: object + properties: + workspace_url: + type: string + description: Linear workspace URL (for example, https://linear.app/example) + push_events: + type: boolean + description: Trigger event for pushes to the repository. + use_inherited_settings: + type: boolean + description: Indicates whether to inherit the default settings. Defaults to + `false`. + required: + - workspace_url + description: Create/Edit Linear integration putApiV4ProjectsIdIntegrationsMatrix: type: object properties: diff --git a/doc/api/project_integrations.md b/doc/api/project_integrations.md index 5606399ad390dce92c5e0ad6d73a630f1b55d576..3e93c30444eb41702ba22104aa6be017c3b9ad50 100644 --- a/doc/api/project_integrations.md +++ b/doc/api/project_integrations.md @@ -1510,6 +1510,45 @@ Get the Jira issues integration settings for a project. GET /projects/:id/integrations/jira ``` +## Linear + +{{< history >}} + +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198297) in GitLab 18.3. + +{{< /history >}} + +### Set up Linear + +Set up the Linear integration for a group. + +```plaintext +PUT /projects/:id/integrations/linear +``` + +Parameters: + +| Parameter | Type | Required | Description | +| ------------- | ------ | -------- | -------------- | +| `workspace_url` | string | yes | URL of the issue. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Linear + +Disable the Linear integration for a group. Integration settings are reset. + +```plaintext +DELETE /projects/:id/integrations/linear +``` + +### Get Linear settings + +Get the Linear integration settings for a group. + +```plaintext +GET /projects/:id/integrations/linear +``` + ## Matrix notifications {{< history >}} diff --git a/doc/development/integrations/_index.md b/doc/development/integrations/_index.md index 5edc05084757eda72bc081aecba70106c6d14a38..ad488e5a2516f7165a1f5e267345d64032930f3d 100644 --- a/doc/development/integrations/_index.md +++ b/doc/development/integrations/_index.md @@ -204,7 +204,14 @@ To create metric definitions: 1. Replace `milestone` with the current milestone and `introduced_by_url` with the merge request link. 1. Verify all other attributes have correct values by checking the [metrics guide](../internal_analytics/metrics/metrics_dictionary.md#metrics-definition-and-validation). -For example, to create metric definitions for the Slack integration, you copy the metrics [1](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180122_projects_slack_active.yml), [2](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180124_groups_slack_active.yml), [3](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180127_instances_slack_active.yml), [4](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180127_instances_slack_active.yml), and [5](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180129_projects_inheriting_slack_active.yml)), then replace `Slack` with the name of the new integration. +For example, to create metric definitions for the Slack integration, you copy these metrics, and +then replace `Slack` with the name of the new integration: + +- [`20210216180122_projects_slack_active.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180122_projects_slack_active.yml) +- [`20210216180124_groups_slack_active.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180124_groups_slack_active.yml) +- [`20210216180127_instances_slack_active.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180127_instances_slack_active.yml) +- [`20210216180131_groups_inheriting_slack_active.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180131_groups_inheriting_slack_active.yml) +- [`20210216180129_projects_inheriting_slack_active.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180129_projects_inheriting_slack_active.yml) ### Security requirements diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md index fa1c2381fe1c99bd4b865cb604279e46aee7ef0b..06f102436f86a68b45ecf2c4ffe6a09feaef894d 100644 --- a/doc/integration/external-issue-tracker.md +++ b/doc/integration/external-issue-tracker.md @@ -52,6 +52,7 @@ 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](jira/_index.md) +- [Linear](../user/project/integrations/linear.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 2be8adfd7cc9f908ab810f215df833e0d00de1d6..d08a0fc40171528a5cd68509b2d9cd99a5ed8ef6 100644 --- a/doc/user/project/integrations/_index.md +++ b/doc/user/project/integrations/_index.md @@ -173,6 +173,7 @@ The following integrations add links to [external issue trackers](../../../integ | [ClickUp](clickup.md) | Use ClickUp as an issue tracker. | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | | [Custom issue tracker](custom_issue_tracker.md) | Use a custom issue tracker. | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | | [Engineering Workflow Management (EWM)](ewm.md) | Use EWM as an issue tracker. | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | +| [Linear](linear.md) | Use Linear as an issue tracker. | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | | [Phorge](phorge.md) | Use Phorge as an issue tracker. | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | | [Redmine](redmine.md) | Use Redmine as an issue tracker. | {{< icon name="dotted-circle" >}} No | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | | [YouTrack](youtrack.md) | Use JetBrains YouTrack as your project's issue tracker. | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes | {{< icon name="dotted-circle" >}} No | diff --git a/doc/user/project/integrations/linear.md b/doc/user/project/integrations/linear.md new file mode 100644 index 0000000000000000000000000000000000000000..6a3b335a1efcd68adc4e2960364e0aba3cfb94eb --- /dev/null +++ b/doc/user/project/integrations/linear.md @@ -0,0 +1,52 @@ +--- +stage: Plan +group: Project Management +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 +title: Linear +--- + +{{< details >}} + +- Tier: Free, Premium, Ultimate +- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated + +{{< /details >}} + +{{< history >}} + +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/198297) in GitLab 18.3. + +{{< /history >}} + +You can use [Linear](https://linear.app/) as an external issue tracker. +To enable the Linear integration in a project: + +1. On the left sidebar, select **Search or go to** and find your project. +1. Select **Settings > Integrations**. +1. Select **Linear**. +1. Under **Enable integration**, select the **Active** checkbox. +1. Fill in the required fields: + + - **Workspace URL**: The URL to the Linear Workspace project to link to this GitLab project. + +1. Optional. Select **Test settings**. +1. Select **Save changes**. + +After you have configured and enabled Linear, you see the Linear link on the GitLab project pages, +which takes you to your Linear workspace. + +For example, this is a configuration for a workspace named `example`: + +- Workspace URL: `https://linear.app/example` + +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](../../public_access.md#change-project-visibility), [features, and permissions](../settings/_index.md#configure-project-features-and-permissions). + +## Reference Linear issues in GitLab + +You can reference your Linear issues using: + +- `-`, for example `API-123`, where: + - `` is a team identifier + - `` is a number. diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index 3ac444c4f85ce4558e192a612dd1f6c0458e7aa5..adb8d79402636a3139959495c66846f33086bab2 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -115,6 +115,7 @@ def self.integrations 'jenkins' => ::Integrations::Jenkins.api_arguments, 'jira' => ::Integrations::Jira.api_arguments, 'jira-cloud-app' => ::Integrations::JiraCloudApp.api_arguments, + 'linear' => ::Integrations::Linear.api_arguments, 'matrix' => ::Integrations::Matrix.api_arguments, 'mattermost-slash-commands' => ::Integrations::MattermostSlashCommands.api_arguments, 'slack-slash-commands' => ::Integrations::SlackSlashCommands.api_arguments, @@ -173,6 +174,7 @@ def self.integration_classes ::Integrations::Jenkins, ::Integrations::Jira, ::Integrations::JiraCloudApp, + ::Integrations::Linear, ::Integrations::Matrix, ::Integrations::Mattermost, ::Integrations::MattermostSlashCommands, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index bda11643f21befd3955d8b56ceb7183cfc95cedb..e7753601841380164d20df59c0ede169413781a1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -37433,6 +37433,24 @@ msgstr[1] "" msgid "Line changes" msgstr "" +msgid "LinearIntegration|Linear workspace URL (for example, https://linear.app/example)" +msgstr "" + +msgid "LinearIntegration|The Linear logo is a trademark of Linear Orbit Inc. in the U.S. and other countries." +msgstr "" + +msgid "LinearIntegration|URL must point to a workspace URL like https://linear.app/example" +msgstr "" + +msgid "LinearIntegration|Use Linear as this project's issue tracker." +msgstr "" + +msgid "LinearIntegration|Workspace URL" +msgstr "" + +msgid "LinearIntegration|https://linear.app/example" +msgstr "" + msgid "Link" msgstr "" diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index 238b525dd1b2f0dba6ba63e7c082ff026186f6b3..d462ed11108a4061749303c32c4e57cb77bf8ebf 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -502,6 +502,13 @@ jira_cloud_app_service_ids { 'b:YXJpOmNsb3VkOmdyYXBoOjpzZXJ2aWNlLzI=' } end + factory :linear_integration, class: 'Integrations::Linear' do + project + active { true } + type { 'Integrations::Linear' } + workspace_url { 'https://linear.app/example' } + end + # this is for testing storing values inside properties, which is deprecated and will be removed in # https://gitlab.com/gitlab-org/gitlab/issues/29404 trait :without_properties_callback do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 99095ac909c317fceeb023bb909f82e2a7bf010f..5f11e3b641f1e5a269cdb9412f6869612e25c0d3 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -668,6 +668,7 @@ project: - ewm_integration - clickup_integration - external_wiki_integration +- linear_integration - mock_ci_integration - mock_monitoring_integration - squash_tm_integration diff --git a/spec/models/integrations/linear_spec.rb b/spec/models/integrations/linear_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b877e0fc8200b9f36b7cb9f1a40b2d6dce8e4eb1 --- /dev/null +++ b/spec/models/integrations/linear_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::Linear, :use_clean_rails_memory_store_caching, feature_category: :integrations do + it_behaves_like Integrations::Base::Linear +end diff --git a/spec/support/shared_examples/models/concerns/integrations/base/linear_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/base/linear_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..b67a3b1e40d1a2ca559cad3af882645960b351da --- /dev/null +++ b/spec/support/shared_examples/models/concerns/integrations/base/linear_shared_examples.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +RSpec.shared_examples Integrations::Base::Linear do + describe 'Validations' do + context 'when integration is active' do + before do + subject.activate! + end + + it { is_expected.to validate_presence_of(:workspace_url) } + + it { is_expected.to allow_value("https://linear.app/test").for(:workspace_url) } + + it { is_expected.not_to allow_value("linear.app/test").for(:workspace_url) } + it { is_expected.not_to allow_value("https://linear.app/test/bar/baz").for(:workspace_url) } + it { is_expected.not_to allow_value('https://example.com').for(:workspace_url) } + it { is_expected.not_to allow_value('example.com').for(:workspace_url) } + it { is_expected.not_to allow_value('ftp://example.com').for(:workspace_url) } + it { is_expected.not_to allow_value('herp-and-derp').for(:workspace_url) } + end + + context 'when integration is inactive' do + before do + subject.deactivate! + end + + it { is_expected.not_to validate_presence_of(:workspace_url) } + end + end + + describe '#reference_pattern' do + it 'matches full issue reference (team prefix + id)' do + expect(subject.reference_pattern.match('PRJ-123')[:issue]).to eq('PRJ-123') + expect(subject.reference_pattern.match('123-123')[:issue]).to eq('123-123') + expect(subject.reference_pattern.match('ABCDEFG-123')[:issue]).to eq('ABCDEFG-123') + end + + it 'does not match invalid references' do + # too long + expect(subject.reference_pattern.match('12345678-123')).to be_nil + # no dash + expect(subject.reference_pattern.match('ABCD1234')).to be_nil + # other leading characters + expect(subject.reference_pattern.match('abcABC-123')).to be_nil + # other following characters + expect(subject.reference_pattern.match('ABC-123abc')).to be_nil + end + end + + describe '#project_url' do + before do + subject.workspace_url = 'https://linear.app/test' + end + + it 'returns the project URL' do + expect(subject.project_url).to eq('https://linear.app/test') + end + end + + describe '#issue_url' do + before do + subject.workspace_url = 'https://linear.app/test' + end + + it 'returns the project URL' do + expect(subject.issue_url('PRJ-123')).to eq('https://linear.app/test/issue/PRJ-123') + end + end + + describe '#fields' do + it 'only returns the workspace_url field' do + expect(subject.fields.pluck(:name)).to eq(%w[workspace_url]) + end + end + + describe '#supports_data_fields?' do + it 'returns false' do + expect(subject.supports_data_fields?).to be(false) + end + end +end