From 990176067e691c97322b7eeb5625d662606a37e8 Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Thu, 17 Feb 2022 17:04:13 +0100 Subject: [PATCH 1/9] Add development guide for integrations --- doc/.vale/gitlab/Uppercase.yml | 1 + doc/.vale/gitlab/spelling-exceptions.txt | 2 + doc/development/integrations/index.md | 120 +++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 doc/development/integrations/index.md diff --git a/doc/.vale/gitlab/Uppercase.yml b/doc/.vale/gitlab/Uppercase.yml index c9021dc862e005..ccbb487dee9e47 100644 --- a/doc/.vale/gitlab/Uppercase.yml +++ b/doc/.vale/gitlab/Uppercase.yml @@ -171,6 +171,7 @@ exceptions: - SSH - SSL - SSO + - STI - SVG - SVN - TCP diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt index 98254c2259bc70..6dc0704963baa7 100644 --- a/doc/.vale/gitlab/spelling-exceptions.txt +++ b/doc/.vale/gitlab/spelling-exceptions.txt @@ -229,6 +229,7 @@ Gemfile Gemnasium Gemojione Getter +getters Getters gettext Git @@ -576,6 +577,7 @@ sharded sharding shfmt Shibboleth +Shimo Shopify Sidekiq Silverlight diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md new file mode 100644 index 00000000000000..8104bc9f45042e --- /dev/null +++ b/doc/development/integrations/index.md @@ -0,0 +1,120 @@ +--- +stage: Ecosystem +group: Integrations +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +description: "GitLab's development guidelines for Integrations" +--- + +# Integrations development guide **(FREE)** + +## Overview + +This page provides development guidelines for implementing [GitLab integrations](../../user/project/integrations/index.md), +which are part of our [main Rails project](https://gitlab.com/gitlab-org/gitlab). + +Also see our [direction page](https://about.gitlab.com/direction/ecosystem/integrations/) for an overview of our strategy around integrations. + +This guide is a work-in-progress, you're welcome to ping `@gitlab-org/ecosystem-stage/integrations` +if you need clarification or spot any outdated information. + +## Adding a new integration + +### Defining the integration + +1. Add a new model in `app/models/integrations` extending from `Integration`. + - For example `Integrations::FooBar` in `app/models/integrations/foo_bar.rb`. + - For certain types of integrations, you can also build on these base classes: + - `Integrations::BaseChatNotification` + - `Integrations::BaseIssueTracker` + - `Integrations::BaseMonitoring` + - `Integrations::BaseSlashCommands` +1. Add the integration as an association on `Project`: + + ```ruby + has_one :foo_bar_integration, class_name: 'Integrations::FooBar' + ``` + +1. Accommodate the current migration to [rename "services" to "integrations"](#rename-services-to-integrations): + - Add the integration's underscored name (`foo_bar`) to `Integration::INTEGRATION_NAMES`. + - Add the integration's camel-cased name (`FooBar`) to `Gitlab::Integrations::StiType::NAMESPACED_INTEGRATIONS`. +1. Make the integration available in the [Integrations API](../../api/integrations.md): + - Add the integration's full class (`::Integrations::FooBar`) to `API::Helpers::IntegrationsHelpers.integration_classes`. + - Add all properties which should be exposed in the API to `API::Helpers::IntegrationsHelpers.integrations`. + +### Defining properties + +Integrations can define arbitrary properties to store their configuration. + +The `Integration` model provides two approaches for defining properties: + +1. Using `Integration.prop_accessor`. + - These properties are stored as a serialized JSON hash in `integrations.properties`. +1. Using `Integration.data_field`. + - These properties are stored in a separate table and need some custom setup. + +In both cases the properties are exposed as getters/setters on the model. + +### Defining trigger events + +Integrations are triggered by calling their `#execute` method in response to events in GitLab, +which gets passed a payload hash with details about the event. + +This behaves similarly to [webhook events](../../user/project/integrations/webhook_events.md), +although integrations don't support exactly the same types of events. + +You can refer to `Integration.supported_events` for the list of supported events, and override +this in your integration class if you're only interested in certain events. + +### Customizing the frontend form + +By default the integration form provides a checkbox to enable/disable the integration, as well +as a checkbox for each of the events defined in `.supported_events`. + +To add additional properties to the form, you can define the metadata for them in `Integration#fields`. +Please refer to the existing integrations for the format and available options. + +To add a help text at the top of the form you can either override `Integration#help`, or provide +a template in `app/views/projects/services/$INTEGRATION_NAME/_help.html.haml`. + +## Availability of integrations + +By default integrations are available on the project, group, and instance level. +Note that most integrations only act in a project context, but can be still configured +from the group and instance levels. + +For some integrations it can make sense to only make it available on the project level. +To do that, the integration needs to be removed from `Integration::INTEGRATION_NAMES` and +added to `Integration::PROJECT_SPECIFIC_INTEGRATION_NAMES` instead. + +Similarly, an integration can be restricted to the development environment by moving it +to `Integration::DEV_INTEGRATION_NAMES`. + +While developing a new integration it's also recommend to gate the availability behind a +[feature flag](../feature_flags/index.md). + +## Ongoing migrations + +### [Rename "services" to "integrations"](https://gitlab.com/groups/gitlab-org/-/epics/2504) + +The "integrations" in GitLab were historically called "services", which frequently caused +confusion with our "service" classes in `app/services`. Sometimes we were also calling +them "project services" because they were initially only available on projects, which is +not the case anymore. + +So we decided to change the naming to "integrations", this is still an ongoing effort. + +Developers should be aware that we're still using the old class names for the STI column +`integrations.type`. So for example a class `Integrations::FooBar` still stores +the old name `FooBarService` in the database. This mapping is handled via `Gitlab::Integrations::StiType`. + +### [Consolidate integration settings](https://gitlab.com/groups/gitlab-org/-/epics/3955) + +We want to unify the way properties are defined, and migrate all integrations to the same approach. + +## Examples + +You can refer to the merge requests linked in these issues for examples of adding new integrations: + +- [Datadog](https://gitlab.com/gitlab-org/gitlab/-/issues/270123) +- [Shimo](https://gitlab.com/gitlab-org/gitlab/-/issues/343386) +- [ZenTao](https://gitlab.com/gitlab-org/gitlab/-/issues/338178) -- GitLab From f1b0f0d6887e05968032d194b898a309146b2cf7 Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Mon, 21 Feb 2022 15:05:46 +0100 Subject: [PATCH 2/9] Address review feedback --- doc/.vale/gitlab/Uppercase.yml | 2 + doc/development/integrations/index.md | 181 +++++++++++++++++++++----- 2 files changed, 147 insertions(+), 36 deletions(-) diff --git a/doc/.vale/gitlab/Uppercase.yml b/doc/.vale/gitlab/Uppercase.yml index ccbb487dee9e47..f1b06e10fe688f 100644 --- a/doc/.vale/gitlab/Uppercase.yml +++ b/doc/.vale/gitlab/Uppercase.yml @@ -51,6 +51,7 @@ exceptions: - EKS - ELB - EOL + - EWM - EXIF - FAQ - FIDO @@ -144,6 +145,7 @@ exceptions: - RSA - RDS - RSS + - RTC - RVM - SAAS - SAML diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index 8104bc9f45042e..5c1ef59824c9c4 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -17,9 +17,9 @@ Also see our [direction page](https://about.gitlab.com/direction/ecosystem/integ This guide is a work-in-progress, you're welcome to ping `@gitlab-org/ecosystem-stage/integrations` if you need clarification or spot any outdated information. -## Adding a new integration +## Add a new integration -### Defining the integration +### Define the integration 1. Add a new model in `app/models/integrations` extending from `Integration`. - For example `Integrations::FooBar` in `app/models/integrations/foo_bar.rb`. @@ -28,6 +28,11 @@ if you need clarification or spot any outdated information. - `Integrations::BaseIssueTracker` - `Integrations::BaseMonitoring` - `Integrations::BaseSlashCommands` + - For integrations that primarily trigger HTTP calls to external services, you can + also use the `Integrations::HasWebHook` concern. This reuses the [webhook functionality](../../user/project/integrations/webhooks.md) + in GitLab through an associated `ServiceHook` model, and automatically records request logs + which can be viewed in the integration settings. +1. Add the integration's underscored name (`'foo_bar'`) to `Integration::INTEGRATION_NAMES`. 1. Add the integration as an association on `Project`: ```ruby @@ -35,46 +40,121 @@ if you need clarification or spot any outdated information. ``` 1. Accommodate the current migration to [rename "services" to "integrations"](#rename-services-to-integrations): - - Add the integration's underscored name (`foo_bar`) to `Integration::INTEGRATION_NAMES`. - - Add the integration's camel-cased name (`FooBar`) to `Gitlab::Integrations::StiType::NAMESPACED_INTEGRATIONS`. -1. Make the integration available in the [Integrations API](../../api/integrations.md): - - Add the integration's full class (`::Integrations::FooBar`) to `API::Helpers::IntegrationsHelpers.integration_classes`. - - Add all properties which should be exposed in the API to `API::Helpers::IntegrationsHelpers.integrations`. + - Add the integration's camel-cased name (`'FooBar'`) to `Gitlab::Integrations::StiType::NAMESPACED_INTEGRATIONS`. -### Defining properties +### Define properties -Integrations can define arbitrary properties to store their configuration. +Integrations can define arbitrary properties to store their configuration with the class method `Integration.prop_accessor`. +The values are stored as a serialized JSON hash in the `integrations.properties` column. -The `Integration` model provides two approaches for defining properties: +They can be accessed by getters/setters directly on the model, just like other ActiveRecord attributes. +You should also define validations for all your properties. -1. Using `Integration.prop_accessor`. - - These properties are stored as a serialized JSON hash in `integrations.properties`. -1. Using `Integration.data_field`. - - These properties are stored in a separate table and need some custom setup. +There is also an alternative approach using `Integration.data_field` and separate tables per integration, +but we plan to [phase this out](#consolidate-integration-settings) and refactor all integrations to use `integrations.properties`. -In both cases the properties are exposed as getters/setters on the model. - -### Defining trigger events +### Define trigger events Integrations are triggered by calling their `#execute` method in response to events in GitLab, which gets passed a payload hash with details about the event. -This behaves similarly to [webhook events](../../user/project/integrations/webhook_events.md), -although integrations don't support exactly the same types of events. +The supported events have some overlap with [webhook events](../../user/project/integrations/webhook_events.md), +and receive the same payload. You can specify the events you're interested in by overriding +the class method `Integration.supported_events` in your model. + +The following events are supported for integrations: + +| Event type | Default | Value | Description +|:-----------------------------------------------------------------------------------------------|:--------|:---------------------|:-- +| Alert event | | `alert` | Trigger event when a new, unique alert is recorded. +| Commit event | ✓ | `commit` | Trigger event when a commit is created or updated. +| [Deployment event](../../user/project/integrations/webhook_events.md#deployment-events) | | `deployment` | Trigger event when a deployment starts or finishes. +| [Issue event](../../user/project/integrations/webhook_events.md#issue-events) | ✓ | `issue` | Trigger event when an issue is created, updated, or closed. +| [Confidential issue event](../../user/project/integrations/webhook_events.md#issue-events) | ✓ | `confidential_issue` | Trigger event when a confidential issue is created, updated, or closed. +| [Job event](../../user/project/integrations/webhook_events.md#job-events) | | `job` +| [Merge request event](../../user/project/integrations/webhook_events.md#merge-request-events) | ✓ | `merge_request` | Trigger event when a merge request is created, updated, or merged. +| [Comment event](../../user/project/integrations/webhook_events.md#comment-events) | | `comment` | Trigger event for new comments. +| [Confidential comment event](../../user/project/integrations/webhook_events.md#comment-events) | | `confidential_note` | Trigger event for new comments on confidential issues. +| [Pipeline event](../../user/project/integrations/webhook_events.md#pipeline-events) | | `pipeline` | Trigger event when a pipeline status changes. +| [Push event](../../user/project/integrations/webhook_events.md#push-events) | ✓ | `push` | Trigger event for pushes to the repository. +| [Tag push event](../../user/project/integrations/webhook_events.md#tag-events) | ✓ | `tag_push` | Trigger event for new tags pushed to the repository. +| Vulnerability event **(ULTIMATE)** | | `vulnerability` | Trigger event when a new, unique vulnerability is recorded. +| [Wiki page event](../../user/project/integrations/webhook_events.md#wiki-page-events) | ✓ | `wiki_page` | Trigger event when a wiki page is created or updated. + +### Customize the frontend form + +By default the integration form provides a checkbox to enable/disable the integration, as well +as checkboxes for each of the trigger events returned from `Integration#configurable_events`. -You can refer to `Integration.supported_events` for the list of supported events, and override -this in your integration class if you're only interested in certain events. +You can also add a help text at the top of the form by either overriding `Integration#help`, +or providing a template in `app/views/projects/services/$INTEGRATION_NAME/_help.html.haml`. -### Customizing the frontend form +To add your custom properties to the form, you can define the metadata for them in `Integration#fields`. -By default the integration form provides a checkbox to enable/disable the integration, as well -as a checkbox for each of the events defined in `.supported_events`. +This method should return an array of hashes for each field, where the keys can be: + +| Key | Required | Default | Description +|:----------------------------|:---------|:---------------------|:-- +| `type:` | ✓ | | The type of the form field, can be `text`, `textarea`, `password`, `checkbox` or `select`. +| `name:` | ✓ | | The property name for the form field, needs to match the defined name in the model. +| `required:` | | `false` | Specify if the form field is required or optional. +| `title:` | | `name` (capitalized) | The label for the form field. +| `placeholder:` | | | A placeholder for the form field. +| `help:` | | | A help text which is displayed below the form field. +| `checkbox_label:` | | `title` | For `type: 'checkbox'`: A custom label which is displayed next to the checkbox. +| `choices:` | | | For `type: 'select'`: a nested array of `[label, value]` tuples. +| `non_empty_password_title:` | | | For `type: 'password'`: An alternative label which is displayed when a value is already stored. +| `non_empty_password_help:` | | | For `type: 'password'`: An alternative help text which is displayed when a value is already stored. + +### Expose the integration in the API + +#### REST API + +To expose the integration in the [REST API](../../api/integrations.md): + +1. Add the integration's full class (`::Integrations::FooBar`) to `API::Helpers::IntegrationsHelpers.integration_classes`. +1. Add all properties which should be exposed to `API::Helpers::IntegrationsHelpers.integrations`. +1. Update the reference documentation in `doc/api/integrations.md`, add a new section for your integration and document all properties. + +You can also refer to our [REST API style guide](../api_styleguide.md). + +#### GraphQL API + +Integrations use the `Types::Projects::ServiceType` type by default, +which only exposes the `type` and `active` properties. + +To expose additional properties, you can write a class implementing `ServiceType`: + +```ruby +# in app/graphql/types/project/services/my_service_type.rb +module Types + module Projects + module Services + class MyServiceType < BaseObject + graphql_name 'MyService' + implements(Types::Projects::ServiceType) + authorize :read_project -To add additional properties to the form, you can define the metadata for them in `Integration#fields`. -Please refer to the existing integrations for the format and available options. + field :frobinity, + GraphQL::Types::Float, + null: true, + description: 'The level of frobinity.' -To add a help text at the top of the form you can either override `Integration#help`, or provide -a template in `app/views/projects/services/$INTEGRATION_NAME/_help.html.haml`. + field :foo_label, + GraphQL::Types::String, + null: true, + description: 'The foo label to apply.' + end + end + end +end +``` + +Each property you want to expose should have a field defined for it. Contact a member of the Integrations team to discuss the best authorization. + +Reference documentation for GraphQL is automatically generated. + +You can also refer to our [GraphQL API style guide](../api_graphql_styleguide.md). ## Availability of integrations @@ -92,7 +172,32 @@ to `Integration::DEV_INTEGRATION_NAMES`. While developing a new integration it's also recommend to gate the availability behind a [feature flag](../feature_flags/index.md). -## Ongoing migrations +## Documentation + +You can provide help texts in the integration form, including links to off-site documentation, +as described above in [Customize the frontend form](#customize-the-frontend-form). + +For more detailed documentation you can also provide a page in `doc/user/project/integrations`, +and link it from the [Integrations overview](../../user/project/integrations/overview.md). + +You can also refer to our general [documentation guidelines](../documentation/index.md). + +## Testing + +It is often sufficient to add tests for the integration model in `spec/models/integrations`, +and a factory with example settings in `spec/factories/integrations.rb`. + +Each integration is also tested as part of generalized tests. For example there are feature specs +which verify that the settings form is rendering correctly for all integrations. + +If your integration implements any custom behavior, especially in the frontend, this should be +covered by additional tests. + +You can also refer to our general [testing guidelines](../testing_guide/index.md). + +## Ongoing migrations and refactorings + +The Integrations team is in the process of some larger migrations which developers should be aware of: ### [Rename "services" to "integrations"](https://gitlab.com/groups/gitlab-org/-/epics/2504) @@ -101,11 +206,13 @@ confusion with our "service" classes in `app/services`. Sometimes we were also c them "project services" because they were initially only available on projects, which is not the case anymore. -So we decided to change the naming to "integrations", this is still an ongoing effort. +So we decided to change the naming from "services" / "project services" to "integrations". +This refactoring is an ongoing effort, and you can still see references to the old names in some places. -Developers should be aware that we're still using the old class names for the STI column +Developers should be especially aware that we're still using the old class names for the STI column `integrations.type`. So for example a class `Integrations::FooBar` still stores -the old name `FooBarService` in the database. This mapping is handled via `Gitlab::Integrations::StiType`. +the old name `FooBarService` in the database. This mapping is handled via `Gitlab::Integrations::StiType` +and should be mostly transparent to the rest of the app. ### [Consolidate integration settings](https://gitlab.com/groups/gitlab-org/-/epics/3955) @@ -113,8 +220,10 @@ We want to unify the way properties are defined, and migrate all integrations to ## Examples -You can refer to the merge requests linked in these issues for examples of adding new integrations: +You can refer to these issues for examples of adding new integrations: -- [Datadog](https://gitlab.com/gitlab-org/gitlab/-/issues/270123) -- [Shimo](https://gitlab.com/gitlab-org/gitlab/-/issues/343386) -- [ZenTao](https://gitlab.com/gitlab-org/gitlab/-/issues/338178) +- [Datadog](https://gitlab.com/gitlab-org/gitlab/-/issues/270123): Metrics collector, similar to the Prometheus integration. +- [EWM/RTC](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36662): External issue tracker. +- [Shimo](https://gitlab.com/gitlab-org/gitlab/-/issues/343386): External wiki, similar to the Confluence and External Wiki integrations. +- [Webex Teams](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31543): Chat notifications. +- [ZenTao](https://gitlab.com/gitlab-org/gitlab/-/issues/338178): External issue tracker with custom issue views, similar to the Jira integration. -- GitLab From 59a6bec6d1f29e4ba85523642e96573f4a972890 Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Wed, 23 Feb 2022 16:07:13 +0100 Subject: [PATCH 3/9] Add link in overview --- doc/development/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/development/index.md b/doc/development/index.md index 552555ab85c48f..a66e1e7cf496e9 100644 --- a/doc/development/index.md +++ b/doc/development/index.md @@ -275,6 +275,7 @@ See [database guidelines](database/index.md). ## Integration guides +- [Integrations development guide](integrations/index.md) - [Jira Connect app](integrations/jira_connect.md) - [Security Scanners](integrations/secure.md) - [Secure Partner Integration](integrations/secure_partner_integration.md) -- GitLab From f8badd828a162bfbc2d340aa23500616cc23f5b6 Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Wed, 23 Feb 2022 20:30:48 +0100 Subject: [PATCH 4/9] Tweak wording for data fields approach --- doc/development/integrations/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index 5c1ef59824c9c4..0c43539f71c108 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -50,8 +50,8 @@ The values are stored as a serialized JSON hash in the `integrations.properties` They can be accessed by getters/setters directly on the model, just like other ActiveRecord attributes. You should also define validations for all your properties. -There is also an alternative approach using `Integration.data_field` and separate tables per integration, -but we plan to [phase this out](#consolidate-integration-settings) and refactor all integrations to use `integrations.properties`. +There is also an alternative approach using `Integration.data_field`, where the values are +stored in a separate table per integration. At the moment we don't recommend using this for new integrations. ### Define trigger events @@ -216,7 +216,7 @@ and should be mostly transparent to the rest of the app. ### [Consolidate integration settings](https://gitlab.com/groups/gitlab-org/-/epics/3955) -We want to unify the way properties are defined, and migrate all integrations to the same approach. +We want to unify the way integration properties are defined. ## Examples -- GitLab From 7836be964ce822eb6867443dc320be2bea011cde Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Tue, 8 Mar 2022 19:41:09 +0100 Subject: [PATCH 5/9] Add examples and clarifications --- doc/development/integrations/index.md | 60 ++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index 0c43539f71c108..7ebc5ac2cc3990 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -50,6 +50,8 @@ The values are stored as a serialized JSON hash in the `integrations.properties` They can be accessed by getters/setters directly on the model, just like other ActiveRecord attributes. You should also define validations for all your properties. +You should always access the properties through their getters/setters, and not interact with the `properties` hash directly. + There is also an alternative approach using `Integration.data_field`, where the values are stored in a separate table per integration. At the moment we don't recommend using this for new integrations. @@ -81,6 +83,22 @@ The following events are supported for integrations: | Vulnerability event **(ULTIMATE)** | | `vulnerability` | Trigger event when a new, unique vulnerability is recorded. | [Wiki page event](../../user/project/integrations/webhook_events.md#wiki-page-events) | ✓ | `wiki_page` | Trigger event when a wiki page is created or updated. +Example for an integration which responds to commit and merge request events: + +```ruby +def self.supported_events + %w[commit merge_request] +end +``` + +An integration can also choose not to respond to events, and implement custom functionality some other way: + +```ruby +def self.supported_events + [] +end +``` + ### Customize the frontend form By default the integration form provides a checkbox to enable/disable the integration, as well @@ -106,13 +124,42 @@ This method should return an array of hashes for each field, where the keys can | `non_empty_password_title:` | | | For `type: 'password'`: An alternative label which is displayed when a value is already stored. | `non_empty_password_help:` | | | For `type: 'password'`: An alternative help text which is displayed when a value is already stored. +Example for a required `url` field, and optional `username` / `password` fields: + +```ruby +prop_accessor :url, :username, :password + +def fields + [ + { + type: 'text', + name: 'url', + title: s_('FooBarIntegration|Server URL'), + placeholder: 'https://example.com/', + required: true + }, + { + type: 'text', + name: 'username', + title: s_('FooBarIntegration|Username'), + }, + { + type: 'password', + name: 'password', + title: s_('FoobarIntegration|Password' + non_empty_password_title: s_('FooBarIntegration|Enter new password') + } + ] +end +``` + ### Expose the integration in the API #### REST API To expose the integration in the [REST API](../../api/integrations.md): -1. Add the integration's full class (`::Integrations::FooBar`) to `API::Helpers::IntegrationsHelpers.integration_classes`. +1. Add the integration's fully-qualified class path (`::Integrations::FooBar`) to `API::Helpers::IntegrationsHelpers.integration_classes`. 1. Add all properties which should be exposed to `API::Helpers::IntegrationsHelpers.integrations`. 1. Update the reference documentation in `doc/api/integrations.md`, add a new section for your integration and document all properties. @@ -166,11 +213,8 @@ For some integrations it can make sense to only make it available on the project To do that, the integration needs to be removed from `Integration::INTEGRATION_NAMES` and added to `Integration::PROJECT_SPECIFIC_INTEGRATION_NAMES` instead. -Similarly, an integration can be restricted to the development environment by moving it -to `Integration::DEV_INTEGRATION_NAMES`. - While developing a new integration it's also recommend to gate the availability behind a -[feature flag](../feature_flags/index.md). +[feature flag](../feature_flags/index.md) in `Integration.available_integration_names`. ## Documentation @@ -195,6 +239,12 @@ covered by additional tests. You can also refer to our general [testing guidelines](../testing_guide/index.md). +## Internationalization + +All UI strings should be prepared for translation by following our [internationalization guidelines](../i18n/externalization.md). + +The strings should use the integration name as [namespace](../i18n/externalization.md#namespaces), for example `s_('FooBarIntegration|My string')`. + ## Ongoing migrations and refactorings The Integrations team is in the process of some larger migrations which developers should be aware of: -- GitLab From 8d0c1c15f9eee5afa48318c25453d4a738f15457 Mon Sep 17 00:00:00 2001 From: Markus Koller Date: Wed, 9 Mar 2022 14:19:29 +0100 Subject: [PATCH 6/9] More examples and clarifications --- doc/development/integrations/index.md | 154 +++++++++++++++++--------- 1 file changed, 103 insertions(+), 51 deletions(-) diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index 7ebc5ac2cc3990..6c01736bc9f640 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -39,7 +39,7 @@ if you need clarification or spot any outdated information. has_one :foo_bar_integration, class_name: 'Integrations::FooBar' ``` -1. Accommodate the current migration to [rename "services" to "integrations"](#rename-services-to-integrations): +1. TEMPORARY: Accommodate the current migration to [rename "services" to "integrations"](#rename-services-to-integrations): - Add the integration's camel-cased name (`'FooBar'`) to `Gitlab::Integrations::StiType::NAMESPACED_INTEGRATIONS`. ### Define properties @@ -47,13 +47,30 @@ if you need clarification or spot any outdated information. Integrations can define arbitrary properties to store their configuration with the class method `Integration.prop_accessor`. The values are stored as a serialized JSON hash in the `integrations.properties` column. -They can be accessed by getters/setters directly on the model, just like other ActiveRecord attributes. +For example: + +```ruby +module Integrations + class FooBar < Integration + prop_accessor :url + prop_accessor :tags + end +end +``` + +`Integration.prop_accessor` installs accessor methods on the class. Here we would have `#url`, `#url=` and `#url_changed?`, to manage the `url` field. Fields stored in `Integration#properties` should be accessed by these accessors directly on the model, just like other ActiveRecord attributes. + +You should always access the properties through their getters, and not interact with the `properties` hash directly. +You _must_ not write to the `properties` hash - you _must_ use the generated setter method instead. Direct writes to this +hash are not persisted. + You should also define validations for all your properties. -You should always access the properties through their getters/setters, and not interact with the `properties` hash directly. +Also refer to the section [Customize the frontend form](#customize-the-frontend-form) below to see how these properties +are exposed in the frontend form for the integration. -There is also an alternative approach using `Integration.data_field`, where the values are -stored in a separate table per integration. At the moment we don't recommend using this for new integrations. +There is also an alternative approach using `Integration.data_field` which you may see in other integrations. +With data fields the values are stored in a separate table per integration. At the moment we don't recommend using this for new integrations. ### Define trigger events @@ -83,24 +100,36 @@ The following events are supported for integrations: | Vulnerability event **(ULTIMATE)** | | `vulnerability` | Trigger event when a new, unique vulnerability is recorded. | [Wiki page event](../../user/project/integrations/webhook_events.md#wiki-page-events) | ✓ | `wiki_page` | Trigger event when a wiki page is created or updated. -Example for an integration which responds to commit and merge request events: +#### Examples + +This example defines an integration which responds to `commit` and `merge_request` events: ```ruby -def self.supported_events - %w[commit merge_request] +module Integrations + class FooBar < Integration + def self.supported_events + %w[commit merge_request] + end + end end ``` An integration can also choose not to respond to events, and implement custom functionality some other way: ```ruby -def self.supported_events - [] +module Integrations + class FooBar < Integration + def self.supported_events + [] + end + end end ``` ### Customize the frontend form +The frontend form is generated dynamically based on metadata defined in the model. + By default the integration form provides a checkbox to enable/disable the integration, as well as checkboxes for each of the trigger events returned from `Integration#configurable_events`. @@ -111,45 +140,66 @@ To add your custom properties to the form, you can define the metadata for them This method should return an array of hashes for each field, where the keys can be: -| Key | Required | Default | Description -|:----------------------------|:---------|:---------------------|:-- -| `type:` | ✓ | | The type of the form field, can be `text`, `textarea`, `password`, `checkbox` or `select`. -| `name:` | ✓ | | The property name for the form field, needs to match the defined name in the model. -| `required:` | | `false` | Specify if the form field is required or optional. -| `title:` | | `name` (capitalized) | The label for the form field. -| `placeholder:` | | | A placeholder for the form field. -| `help:` | | | A help text which is displayed below the form field. -| `checkbox_label:` | | `title` | For `type: 'checkbox'`: A custom label which is displayed next to the checkbox. -| `choices:` | | | For `type: 'select'`: a nested array of `[label, value]` tuples. -| `non_empty_password_title:` | | | For `type: 'password'`: An alternative label which is displayed when a value is already stored. -| `non_empty_password_help:` | | | For `type: 'password'`: An alternative help text which is displayed when a value is already stored. - -Example for a required `url` field, and optional `username` / `password` fields: +| Key | Type | Required | Default | Description +|:---------------|:--------|:---------|:-----------------------------|:-- +| `type:` | string | true | | The type of the form field, can be `text`, `textarea`, `password`, `checkbox` or `select`. +| `name:` | string | true | | The property name for the form field. This must match a `prop_accessor` [defined on the class](#define-properties). +| `required:` | boolean | false | `false` | Specify if the form field is required or optional. +| `title:` | string | false | Capitalized value of `name:` | The label for the form field. +| `placeholder:` | string | false | | A placeholder for the form field. +| `help:` | string | false | | A help text which is displayed below the form field. + +#### Additional keys for `type: 'checkbox'` + +| Key | Type | Required | Default | Description +|:------------------|:-------|:---------|:------------------|:-- +| `checkbox_label:` | string | false | Value of `title:` | A custom label which is displayed next to the checkbox. + +#### Additional keys for `type: 'select'` + +| Key | Type | Required | Default | Description +|:-----------|:------|:---------|:--------|:-- +| `choices:` | array | true | | A nested array of `[label, value]` tuples. + +#### Additional keys for `type: 'password'` + +| Key | Type | Required | Default | Description +|:----------------------------|:-------|:---------|:------------------|:-- +| `non_empty_password_title:` | string | false | Value of `title:` | An alternative label which is displayed when a value is already stored. +| `non_empty_password_help:` | string | false | Value of `help:` | An alternative help text which is displayed when a value is already stored. + +#### Examples + +This example defines a required `url` field, and optional `username` / `password` fields: ```ruby -prop_accessor :url, :username, :password - -def fields - [ - { - type: 'text', - name: 'url', - title: s_('FooBarIntegration|Server URL'), - placeholder: 'https://example.com/', - required: true - }, - { - type: 'text', - name: 'username', - title: s_('FooBarIntegration|Username'), - }, - { - type: 'password', - name: 'password', - title: s_('FoobarIntegration|Password' - non_empty_password_title: s_('FooBarIntegration|Enter new password') - } - ] +module Integrations + class FooBar < Integration + prop_accessor :url, :username, :password + + def fields + [ + { + type: 'text', + name: 'url', + title: s_('FooBarIntegration|Server URL'), + placeholder: 'https://example.com/', + required: true + }, + { + type: 'text', + name: 'username', + title: s_('FooBarIntegration|Username'), + }, + { + type: 'password', + name: 'password', + title: s_('FoobarIntegration|Password' + non_empty_password_title: s_('FooBarIntegration|Enter new password') + } + ] + end + end end ``` @@ -159,7 +209,7 @@ end To expose the integration in the [REST API](../../api/integrations.md): -1. Add the integration's fully-qualified class path (`::Integrations::FooBar`) to `API::Helpers::IntegrationsHelpers.integration_classes`. +1. Add the integration's class (`::Integrations::FooBar`) to `API::Helpers::IntegrationsHelpers.integration_classes`. 1. Add all properties which should be exposed to `API::Helpers::IntegrationsHelpers.integrations`. 1. Update the reference documentation in `doc/api/integrations.md`, add a new section for your integration and document all properties. @@ -177,8 +227,8 @@ To expose additional properties, you can write a class implementing `ServiceType module Types module Projects module Services - class MyServiceType < BaseObject - graphql_name 'MyService' + class FooBarServiceType < BaseObject + graphql_name 'FooBarService' implements(Types::Projects::ServiceType) authorize :read_project @@ -197,7 +247,9 @@ module Types end ``` -Each property you want to expose should have a field defined for it. Contact a member of the Integrations team to discuss the best authorization. +Each property you want to expose should have a field defined for it. You can also expose any public instance method of the integration. + +Contact a member of the Integrations team to discuss the best authorization. Reference documentation for GraphQL is automatically generated. -- GitLab From 1d055ce453bf959d7b9d50d193c8ba89197ca3dc Mon Sep 17 00:00:00 2001 From: Alex Kalderimis Date: Wed, 9 Mar 2022 13:58:57 +0000 Subject: [PATCH 7/9] Update file location comment to match example --- doc/development/integrations/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index 6c01736bc9f640..a6fd3a752ce677 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -223,7 +223,7 @@ which only exposes the `type` and `active` properties. To expose additional properties, you can write a class implementing `ServiceType`: ```ruby -# in app/graphql/types/project/services/my_service_type.rb +# in app/graphql/types/project/services/foo_bar_service_type.rb module Types module Projects module Services -- GitLab From e904cfcb3dd83d693cff679857ed31636fb892c8 Mon Sep 17 00:00:00 2001 From: Kati Paizee Date: Thu, 10 Mar 2022 10:32:23 +0200 Subject: [PATCH 8/9] Tech writer review of integrations page --- doc/development/integrations/index.md | 74 +++++++++++++-------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index a6fd3a752ce677..0a3e6f15778242 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -7,14 +7,12 @@ description: "GitLab's development guidelines for Integrations" # Integrations development guide **(FREE)** -## Overview - This page provides development guidelines for implementing [GitLab integrations](../../user/project/integrations/index.md), which are part of our [main Rails project](https://gitlab.com/gitlab-org/gitlab). Also see our [direction page](https://about.gitlab.com/direction/ecosystem/integrations/) for an overview of our strategy around integrations. -This guide is a work-in-progress, you're welcome to ping `@gitlab-org/ecosystem-stage/integrations` +This guide is a work in progress. You're welcome to ping `@gitlab-org/ecosystem-stage/integrations` if you need clarification or spot any outdated information. ## Add a new integration @@ -22,7 +20,7 @@ if you need clarification or spot any outdated information. ### Define the integration 1. Add a new model in `app/models/integrations` extending from `Integration`. - - For example `Integrations::FooBar` in `app/models/integrations/foo_bar.rb`. + - For example, `Integrations::FooBar` in `app/models/integrations/foo_bar.rb`. - For certain types of integrations, you can also build on these base classes: - `Integrations::BaseChatNotification` - `Integrations::BaseIssueTracker` @@ -61,7 +59,7 @@ end `Integration.prop_accessor` installs accessor methods on the class. Here we would have `#url`, `#url=` and `#url_changed?`, to manage the `url` field. Fields stored in `Integration#properties` should be accessed by these accessors directly on the model, just like other ActiveRecord attributes. You should always access the properties through their getters, and not interact with the `properties` hash directly. -You _must_ not write to the `properties` hash - you _must_ use the generated setter method instead. Direct writes to this +You **must not** write to the `properties` hash, you **must** use the generated setter method instead. Direct writes to this hash are not persisted. You should also define validations for all your properties. @@ -69,7 +67,7 @@ You should also define validations for all your properties. Also refer to the section [Customize the frontend form](#customize-the-frontend-form) below to see how these properties are exposed in the frontend form for the integration. -There is also an alternative approach using `Integration.data_field` which you may see in other integrations. +There is an alternative approach using `Integration.data_field`, which you may see in other integrations. With data fields the values are stored in a separate table per integration. At the moment we don't recommend using this for new integrations. ### Define trigger events @@ -100,9 +98,9 @@ The following events are supported for integrations: | Vulnerability event **(ULTIMATE)** | | `vulnerability` | Trigger event when a new, unique vulnerability is recorded. | [Wiki page event](../../user/project/integrations/webhook_events.md#wiki-page-events) | ✓ | `wiki_page` | Trigger event when a wiki page is created or updated. -#### Examples +#### Event examples -This example defines an integration which responds to `commit` and `merge_request` events: +This example defines an integration that responds to `commit` and `merge_request` events: ```ruby module Integrations @@ -114,7 +112,7 @@ module Integrations end ``` -An integration can also choose not to respond to events, and implement custom functionality some other way: +An integration can also not respond to events, and implement custom functionality some other way: ```ruby module Integrations @@ -130,10 +128,12 @@ end The frontend form is generated dynamically based on metadata defined in the model. -By default the integration form provides a checkbox to enable/disable the integration, as well -as checkboxes for each of the trigger events returned from `Integration#configurable_events`. +By default, the integration form provides: + +- A checkbox to enable or disable the integration. +- Checkboxes for each of the trigger events returned from `Integration#configurable_events`. -You can also add a help text at the top of the form by either overriding `Integration#help`, +You can also add help text at the top of the form by either overriding `Integration#help`, or providing a template in `app/views/projects/services/$INTEGRATION_NAME/_help.html.haml`. To add your custom properties to the form, you can define the metadata for them in `Integration#fields`. @@ -142,18 +142,18 @@ This method should return an array of hashes for each field, where the keys can | Key | Type | Required | Default | Description |:---------------|:--------|:---------|:-----------------------------|:-- -| `type:` | string | true | | The type of the form field, can be `text`, `textarea`, `password`, `checkbox` or `select`. +| `type:` | string | true | | The type of the form field. Can be `text`, `textarea`, `password`, `checkbox`, or `select`. | `name:` | string | true | | The property name for the form field. This must match a `prop_accessor` [defined on the class](#define-properties). | `required:` | boolean | false | `false` | Specify if the form field is required or optional. | `title:` | string | false | Capitalized value of `name:` | The label for the form field. | `placeholder:` | string | false | | A placeholder for the form field. -| `help:` | string | false | | A help text which is displayed below the form field. +| `help:` | string | false | | A help text that displays below the form field. #### Additional keys for `type: 'checkbox'` | Key | Type | Required | Default | Description |:------------------|:-------|:---------|:------------------|:-- -| `checkbox_label:` | string | false | Value of `title:` | A custom label which is displayed next to the checkbox. +| `checkbox_label:` | string | false | Value of `title:` | A custom label that displays next to the checkbox. #### Additional keys for `type: 'select'` @@ -165,12 +165,12 @@ This method should return an array of hashes for each field, where the keys can | Key | Type | Required | Default | Description |:----------------------------|:-------|:---------|:------------------|:-- -| `non_empty_password_title:` | string | false | Value of `title:` | An alternative label which is displayed when a value is already stored. -| `non_empty_password_help:` | string | false | Value of `help:` | An alternative help text which is displayed when a value is already stored. +| `non_empty_password_title:` | string | false | Value of `title:` | An alternative label that displays when a value is already stored. +| `non_empty_password_help:` | string | false | Value of `help:` | An alternative help text that displays when a value is already stored. -#### Examples +#### Frontend form examples -This example defines a required `url` field, and optional `username` / `password` fields: +This example defines a required `url` field, and optional `username` and `password` fields: ```ruby module Integrations @@ -210,8 +210,8 @@ end To expose the integration in the [REST API](../../api/integrations.md): 1. Add the integration's class (`::Integrations::FooBar`) to `API::Helpers::IntegrationsHelpers.integration_classes`. -1. Add all properties which should be exposed to `API::Helpers::IntegrationsHelpers.integrations`. -1. Update the reference documentation in `doc/api/integrations.md`, add a new section for your integration and document all properties. +1. Add all properties that should be exposed to `API::Helpers::IntegrationsHelpers.integrations`. +1. Update the reference documentation in `doc/api/integrations.md`, add a new section for your integration, and document all properties. You can also refer to our [REST API style guide](../api_styleguide.md). @@ -257,23 +257,23 @@ You can also refer to our [GraphQL API style guide](../api_graphql_styleguide.md ## Availability of integrations -By default integrations are available on the project, group, and instance level. -Note that most integrations only act in a project context, but can be still configured +By default, integrations are available on the project, group, and instance level. +Most integrations only act in a project context, but can be still configured from the group and instance levels. For some integrations it can make sense to only make it available on the project level. -To do that, the integration needs to be removed from `Integration::INTEGRATION_NAMES` and +To do that, the integration must be removed from `Integration::INTEGRATION_NAMES` and added to `Integration::PROJECT_SPECIFIC_INTEGRATION_NAMES` instead. -While developing a new integration it's also recommend to gate the availability behind a +When developing a new integration, we also recommend you gate the availability behind a [feature flag](../feature_flags/index.md) in `Integration.available_integration_names`. ## Documentation -You can provide help texts in the integration form, including links to off-site documentation, +You can provide help text in the integration form, including links to off-site documentation, as described above in [Customize the frontend form](#customize-the-frontend-form). -For more detailed documentation you can also provide a page in `doc/user/project/integrations`, +For more detailed documentation, provide a page in `doc/user/project/integrations`, and link it from the [Integrations overview](../../user/project/integrations/overview.md). You can also refer to our general [documentation guidelines](../documentation/index.md). @@ -283,8 +283,8 @@ You can also refer to our general [documentation guidelines](../documentation/in It is often sufficient to add tests for the integration model in `spec/models/integrations`, and a factory with example settings in `spec/factories/integrations.rb`. -Each integration is also tested as part of generalized tests. For example there are feature specs -which verify that the settings form is rendering correctly for all integrations. +Each integration is also tested as part of generalized tests. For example, there are feature specs +that verify that the settings form is rendering correctly for all integrations. If your integration implements any custom behavior, especially in the frontend, this should be covered by additional tests. @@ -295,24 +295,24 @@ You can also refer to our general [testing guidelines](../testing_guide/index.md All UI strings should be prepared for translation by following our [internationalization guidelines](../i18n/externalization.md). -The strings should use the integration name as [namespace](../i18n/externalization.md#namespaces), for example `s_('FooBarIntegration|My string')`. +The strings should use the integration name as [namespace](../i18n/externalization.md#namespaces), for example, `s_('FooBarIntegration|My string')`. ## Ongoing migrations and refactorings -The Integrations team is in the process of some larger migrations which developers should be aware of: +The Integrations team is in the process of some larger migrations that developers should be aware of. ### [Rename "services" to "integrations"](https://gitlab.com/groups/gitlab-org/-/epics/2504) The "integrations" in GitLab were historically called "services", which frequently caused -confusion with our "service" classes in `app/services`. Sometimes we were also calling +confusion with our "service" classes in `app/services`. We sometimes also called them "project services" because they were initially only available on projects, which is not the case anymore. -So we decided to change the naming from "services" / "project services" to "integrations". -This refactoring is an ongoing effort, and you can still see references to the old names in some places. +We decided to change the naming from "services" and "project services" to "integrations". +This refactoring is an ongoing effort, and there are still references to the old names in some places. -Developers should be especially aware that we're still using the old class names for the STI column -`integrations.type`. So for example a class `Integrations::FooBar` still stores +Developers should be especially aware that we still use the old class names for the STI column +`integrations.type`. For example, a class `Integrations::FooBar` still stores the old name `FooBarService` in the database. This mapping is handled via `Gitlab::Integrations::StiType` and should be mostly transparent to the rest of the app. @@ -320,7 +320,7 @@ and should be mostly transparent to the rest of the app. We want to unify the way integration properties are defined. -## Examples +## Integration examples You can refer to these issues for examples of adding new integrations: -- GitLab From 98778cbb19b6df09866db64fe731fad9083cbef5 Mon Sep 17 00:00:00 2001 From: Kati Paizee Date: Thu, 10 Mar 2022 16:59:05 +0000 Subject: [PATCH 9/9] Apply 2 suggestion(s) to 1 file(s) --- doc/development/integrations/index.md | 31 ++++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index 0a3e6f15778242..34ac307c98ae4e 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -81,22 +81,22 @@ the class method `Integration.supported_events` in your model. The following events are supported for integrations: -| Event type | Default | Value | Description +| Event type | Default | Value | Trigger |:-----------------------------------------------------------------------------------------------|:--------|:---------------------|:-- -| Alert event | | `alert` | Trigger event when a new, unique alert is recorded. -| Commit event | ✓ | `commit` | Trigger event when a commit is created or updated. -| [Deployment event](../../user/project/integrations/webhook_events.md#deployment-events) | | `deployment` | Trigger event when a deployment starts or finishes. -| [Issue event](../../user/project/integrations/webhook_events.md#issue-events) | ✓ | `issue` | Trigger event when an issue is created, updated, or closed. -| [Confidential issue event](../../user/project/integrations/webhook_events.md#issue-events) | ✓ | `confidential_issue` | Trigger event when a confidential issue is created, updated, or closed. +| Alert event | | `alert` | A a new, unique alert is recorded. +| Commit event | ✓ | `commit` | A commit is created or updated. +| [Deployment event](../../user/project/integrations/webhook_events.md#deployment-events) | | `deployment` | A deployment starts or finishes. +| [Issue event](../../user/project/integrations/webhook_events.md#issue-events) | ✓ | `issue` | An issue is created, updated, or closed. +| [Confidential issue event](../../user/project/integrations/webhook_events.md#issue-events) | ✓ | `confidential_issue` | A confidential issue is created, updated, or closed. | [Job event](../../user/project/integrations/webhook_events.md#job-events) | | `job` -| [Merge request event](../../user/project/integrations/webhook_events.md#merge-request-events) | ✓ | `merge_request` | Trigger event when a merge request is created, updated, or merged. -| [Comment event](../../user/project/integrations/webhook_events.md#comment-events) | | `comment` | Trigger event for new comments. -| [Confidential comment event](../../user/project/integrations/webhook_events.md#comment-events) | | `confidential_note` | Trigger event for new comments on confidential issues. -| [Pipeline event](../../user/project/integrations/webhook_events.md#pipeline-events) | | `pipeline` | Trigger event when a pipeline status changes. -| [Push event](../../user/project/integrations/webhook_events.md#push-events) | ✓ | `push` | Trigger event for pushes to the repository. -| [Tag push event](../../user/project/integrations/webhook_events.md#tag-events) | ✓ | `tag_push` | Trigger event for new tags pushed to the repository. -| Vulnerability event **(ULTIMATE)** | | `vulnerability` | Trigger event when a new, unique vulnerability is recorded. -| [Wiki page event](../../user/project/integrations/webhook_events.md#wiki-page-events) | ✓ | `wiki_page` | Trigger event when a wiki page is created or updated. +| [Merge request event](../../user/project/integrations/webhook_events.md#merge-request-events) | ✓ | `merge_request` | A merge request is created, updated, or merged. +| [Comment event](../../user/project/integrations/webhook_events.md#comment-events) | | `comment` | A new comment is added. +| [Confidential comment event](../../user/project/integrations/webhook_events.md#comment-events) | | `confidential_note` | A new comment on a confidential issue is added. +| [Pipeline event](../../user/project/integrations/webhook_events.md#pipeline-events) | | `pipeline` | A pipeline status changes. +| [Push event](../../user/project/integrations/webhook_events.md#push-events) | ✓ | `push` | A push is made to the repository. +| [Tag push event](../../user/project/integrations/webhook_events.md#tag-events) | ✓ | `tag_push` | New tags are pushed to the repository. +| Vulnerability event **(ULTIMATE)** | | `vulnerability` | A new, unique vulnerability is recorded. +| [Wiki page event](../../user/project/integrations/webhook_events.md#wiki-page-events) | ✓ | `wiki_page` | A wiki page is created or updated. #### Event examples @@ -271,7 +271,8 @@ When developing a new integration, we also recommend you gate the availability b ## Documentation You can provide help text in the integration form, including links to off-site documentation, -as described above in [Customize the frontend form](#customize-the-frontend-form). +as described above in [Customize the frontend form](#customize-the-frontend-form). Refer to +our [usability guidelines](https://design.gitlab.com/usability/helping-users) for help text. For more detailed documentation, provide a page in `doc/user/project/integrations`, and link it from the [Integrations overview](../../user/project/integrations/overview.md). -- GitLab