Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-02-04 00:09:18 +00:00
parent a1ed241c82
commit 89f3fa0b96
54 changed files with 639 additions and 463 deletions

View File

@ -111,5 +111,5 @@ Use the following resources to find the appropriate labels:
- https://about.gitlab.com/handbook/product/categories/features/
-->
/label ~devops:: ~group: ~Category:
/label ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate"
/label ~feature

View File

@ -14,7 +14,7 @@
/label ~"feature" ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Starter"/~"GitLab Premium"/~"GitLab Ultimate"
/label ~"feature" ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate"
<!--- Use the following resources to find the appropriate labels:

View File

@ -1 +1 @@
9c2da9436f6a41a244a30deef6f48798f877e909
2c7c204731f6e4f1c8cdb3d8a705caf7acf6689d

View File

@ -15,7 +15,7 @@ gem 'default_value_for', '~> 3.4.0'
# Supported DBs
gem 'pg', '~> 1.1'
gem 'rugged', '~> 0.28'
gem 'rugged', '~> 1.0.1'
gem 'grape-path-helpers', '~> 1.6.1'
gem 'faraday', '~> 1.0'
@ -266,7 +266,7 @@ gem 'babosa', '~> 1.0.2'
gem 'loofah', '~> 2.2'
# Working with license
gem 'licensee', '~> 8.9'
gem 'licensee', '~> 9.14.1'
# Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.7'

View File

@ -267,6 +267,7 @@ GEM
doorkeeper-openid_connect (1.7.5)
doorkeeper (>= 5.2, < 5.5)
json-jwt (>= 1.11.0)
dotenv (2.7.6)
dry-configurable (0.12.0)
concurrent-ruby (~> 1.0)
dry-core (~> 0.5, >= 0.5.0)
@ -674,8 +675,12 @@ GEM
toml (= 0.2.0)
with_env (= 1.1.0)
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
licensee (9.14.1)
dotenv (~> 2.0)
octokit (~> 4.17)
reverse_markdown (~> 1.0)
rugged (>= 0.24, < 2.0)
thor (>= 0.19, < 2.0)
listen (3.2.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
@ -756,7 +761,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
octokit (4.15.0)
octokit (4.20.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
oj (3.10.6)
@ -990,6 +995,8 @@ GEM
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
retriable (3.1.2)
reverse_markdown (1.4.0)
nokogiri
rexml (3.2.4)
rinku (2.0.0)
rotp (2.1.2)
@ -1072,7 +1079,7 @@ GEM
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (2.0.0)
rugged (0.28.4.1)
rugged (1.0.1)
safe_yaml (1.0.4)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
@ -1417,7 +1424,7 @@ DEPENDENCIES
lefthook (~> 0.7)
letter_opener_web (~> 1.3.4)
license_finder (~> 6.0)
licensee (~> 8.9)
licensee (~> 9.14.1)
lockbox (~> 0.3.3)
lograge (~> 0.5)
loofah (~> 2.2)
@ -1501,7 +1508,7 @@ DEPENDENCIES
ruby-progressbar (~> 1.10)
ruby_parser (~> 3.15)
rubyzip (~> 2.0.0)
rugged (~> 0.28)
rugged (~> 1.0.1)
sanitize (~> 5.2.1)
sassc-rails (~> 2.1.0)
scss_lint (~> 0.59.0)

View File

@ -13,6 +13,9 @@ export const ERROR_INSTANCE_REQUIRED_FOR_EXTENSION = __(
export const EDITOR_READY_EVENT = 'editor-ready';
export const EDITOR_TYPE_CODE = 'vs.editor.ICodeEditor';
export const EDITOR_TYPE_DIFF = 'vs.editor.IDiffEditor';
//
// EXTENSIONS' CONSTANTS
//

View File

@ -6,7 +6,12 @@ import { registerLanguages } from '~/ide/utils';
import { joinPaths } from '~/lib/utils/url_utility';
import { uuids } from '~/diffs/utils/uuids';
import { clearDomElement } from './utils';
import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX, EDITOR_READY_EVENT } from './constants';
import {
EDITOR_LITE_INSTANCE_ERROR_NO_EL,
URI_PREFIX,
EDITOR_READY_EVENT,
EDITOR_TYPE_DIFF,
} from './constants';
export default class EditorLite {
constructor(options = {}) {
@ -29,15 +34,12 @@ export default class EditorLite {
monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME);
}
static updateModelLanguage(path, instance) {
if (!instance) return;
const model = instance.getModel();
static getModelLanguage(path) {
const ext = `.${path.split('.').pop()}`;
const language = monacoLanguages
.getLanguages()
.find((lang) => lang.extensions.indexOf(ext) !== -1);
const id = language ? language.id : 'plaintext';
monacoEditor.setModelLanguage(model, id);
return language ? language.id : 'plaintext';
}
static pushToImportsArray(arr, toImport) {
@ -102,17 +104,91 @@ export default class EditorLite {
});
}
static createEditorModel({ blobPath, blobContent, blobGlobalId, instance } = {}) {
let model = null;
static createEditorModel({
blobPath,
blobContent,
blobOriginalContent,
blobGlobalId,
instance,
isDiff,
} = {}) {
if (!instance) {
return null;
}
const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
const uri = Uri.file(uriFilePath);
const existingModel = monacoEditor.getModel(uri);
model = existingModel || monacoEditor.createModel(blobContent, undefined, uri);
instance.setModel(model);
return model;
const model = existingModel || monacoEditor.createModel(blobContent, undefined, uri);
if (!isDiff) {
instance.setModel(model);
return model;
}
const diffModel = {
original: monacoEditor.createModel(
blobOriginalContent,
EditorLite.getModelLanguage(model.uri.path),
),
modified: model,
};
instance.setModel(diffModel);
return diffModel;
}
static convertMonacoToELInstance = (inst) => {
const editorLiteInstanceAPI = {
updateModelLanguage: (path) => {
return EditorLite.instanceUpdateLanguage(inst, path);
},
use: (exts = []) => {
return EditorLite.instanceApplyExtension(inst, exts);
},
};
const handler = {
get(target, prop, receiver) {
if (Reflect.has(editorLiteInstanceAPI, prop)) {
return editorLiteInstanceAPI[prop];
}
return Reflect.get(target, prop, receiver);
},
};
return new Proxy(inst, handler);
};
static instanceUpdateLanguage(inst, path) {
const lang = EditorLite.getModelLanguage(path);
const model = inst.getModel();
return monacoEditor.setModelLanguage(model, lang);
}
static instanceApplyExtension(inst, exts = []) {
const extensions = [].concat(exts);
extensions.forEach((extension) => {
EditorLite.mixIntoInstance(extension, inst);
});
return inst;
}
static instanceRemoveFromRegistry(editor, instance) {
const index = editor.instances.findIndex((inst) => inst === instance);
editor.instances.splice(index, 1);
}
static instanceDisposeModels(editor, instance, model) {
const instanceModel = instance.getModel() || model;
if (!instanceModel) {
return;
}
if (instance.getEditorType() === EDITOR_TYPE_DIFF) {
const { original, modified } = instanceModel;
if (original) {
original.dispose();
}
if (modified) {
modified.dispose();
}
} else {
instanceModel.dispose();
}
}
/**
@ -128,26 +204,38 @@ export default class EditorLite {
el = undefined,
blobPath = '',
blobContent = '',
blobOriginalContent = '',
blobGlobalId = uuids()[0],
extensions = [],
isDiff = false,
...instanceOptions
} = {}) {
EditorLite.prepareInstance(el);
const instance = monacoEditor.create(el, {
...this.options,
...instanceOptions,
});
const createEditorFn = isDiff ? 'createDiffEditor' : 'create';
const instance = EditorLite.convertMonacoToELInstance(
monacoEditor[createEditorFn].call(this, el, {
...this.options,
...instanceOptions,
}),
);
const model = EditorLite.createEditorModel({ blobGlobalId, blobPath, blobContent, instance });
let model;
if (instanceOptions.model !== null) {
model = EditorLite.createEditorModel({
blobGlobalId,
blobOriginalContent,
blobPath,
blobContent,
instance,
isDiff,
});
}
instance.onDidDispose(() => {
const index = this.instances.findIndex((inst) => inst === instance);
this.instances.splice(index, 1);
model.dispose();
EditorLite.instanceRemoveFromRegistry(this, instance);
EditorLite.instanceDisposeModels(this, instance, model);
});
instance.updateModelLanguage = (path) => EditorLite.updateModelLanguage(path, instance);
instance.use = (args) => this.use(args, instance);
EditorLite.manageDefaultExtensions(instance, el, extensions);
@ -155,23 +243,21 @@ export default class EditorLite {
return instance;
}
createDiffInstance(args) {
return this.createInstance({
...args,
isDiff: true,
});
}
dispose() {
this.instances.forEach((instance) => instance.dispose());
}
use(exts = [], instance = null) {
const extensions = Array.isArray(exts) ? exts : [exts];
const initExtensions = (inst) => {
extensions.forEach((extension) => {
EditorLite.mixIntoInstance(extension, inst);
});
};
if (instance) {
initExtensions(instance);
} else {
this.instances.forEach((inst) => {
initExtensions(inst);
});
}
use(exts) {
this.instances.forEach((inst) => {
inst.use(exts);
});
return this;
}
}

View File

@ -17,7 +17,7 @@ module OperationsHelper
'prometheus_authorization_key' => @project.alerting_setting&.token,
'prometheus_api_url' => prometheus_service.api_url,
'prometheus_url' => notify_project_prometheus_alerts_url(@project, format: :json),
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'),
'alerts_setup_url' => help_page_path('operations/incident_management/integrations.md', anchor: 'configuration'),
'alerts_usage_url' => project_alert_management_index_path(@project),
'disabled' => disabled.to_s,
'project_path' => @project.full_path,

View File

@ -0,0 +1,5 @@
---
title: 'Editor Lite: support for Diff Instance'
merge_request: 51470
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Update Rugged to v1.0.1
merge_request: 53212
author:
type: changed

View File

@ -310,7 +310,7 @@
- dompurify
- Apache-2.0
- :who: Lukas Eipert
:why: "https://github.com/cure53/DOMPurify/blob/main/LICENSE and https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31928#note_346604841"
:why: https://github.com/cure53/DOMPurify/blob/main/LICENSE and https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31928#note_346604841
:versions: []
:when: 2020-08-13 13:42:46.508082000 Z
- - :permit
@ -325,3 +325,9 @@
:why: Used to generate documentation. https://pypi.org/project/docutils/0.13.1/
:versions: []
:when: 2020-10-05 20:22:55.955189491 Z
- - :permit
- WTFPL
- :who: Stan Hu
:why: https://github.com/xijo/reverse_markdown/blob/master/LICENSE
:versions: []
:when: 2021-02-03 08:47:28.792907000 Z

View File

@ -149,6 +149,7 @@ exceptions:
- TODO
- TOML
- TTL
- UDP
- UNIX
- URI
- URL

View File

@ -1,7 +0,0 @@
---
redirect_to: 'postgresql/external.md'
---
# Configure GitLab using an external PostgreSQL service
This content has been moved to a [new location](postgresql/external.md).

View File

@ -106,9 +106,10 @@ POST /projects/:id/badges
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `link_url` | string | yes | URL of the badge link |
| `image_url` | string | yes | URL of the badge image |
| `name` | string | no | Name of the badge |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "link_url=https://gitlab.com/gitlab-org/gitlab-foss/commits/master&image_url=https://shields.io/my/badge1&position=0" "https://gitlab.example.com/api/v4/projects/:id/badges"
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "link_url=https://gitlab.com/gitlab-org/gitlab-foss/commits/master&image_url=https://shields.io/my/badge1&name=mybadge" "https://gitlab.example.com/api/v4/projects/:id/badges"
```
Example response:
@ -116,6 +117,7 @@ Example response:
```json
{
"id": 1,
"name": "mybadge",
"link_url": "https://gitlab.com/gitlab-org/gitlab-foss/commits/master",
"image_url": "https://shields.io/my/badge1",
"rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-foss/commits/master",
@ -138,6 +140,7 @@ PUT /projects/:id/badges/:badge_id
| `badge_id` | integer | yes | The badge ID |
| `link_url` | string | no | URL of the badge link |
| `image_url` | string | no | URL of the badge image |
| `name` | string | no | Name of the badge |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id"
@ -148,6 +151,7 @@ Example response:
```json
{
"id": 1,
"name": "mybadge",
"link_url": "https://gitlab.com/gitlab-org/gitlab-foss/commits/master",
"image_url": "https://shields.io/my/badge",
"rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-foss/commits/master",

View File

@ -56,7 +56,7 @@ POST projects/:id/access_tokens
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `name` | String | yes | The name of the project access token |
| `scopes` | Array[String] | yes | [List of scopes](../user/project/settings/project_access_tokens.md#limiting-scopes-of-a-project-access-token) |
| `scopes` | Array\[String] | yes | [List of scopes](../user/project/settings/project_access_tokens.md#limiting-scopes-of-a-project-access-token) |
| `expires_at` | Date | no | The token expires at midnight UTC on that date |
```shell

View File

@ -65,7 +65,7 @@ GET /users?active=true
GET /users?blocked=true
```
GitLab supports bot users such as the [alert bot](../operations/incident_management/alert_integrations.md)
GitLab supports bot users such as the [alert bot](../operations/incident_management/integrations.md)
or the [support bot](../user/project/service_desk.md#support-bot-user).
To exclude these users from the users' list, you can use the parameter `exclude_internal=true`
([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241144) in GitLab 13.4).

View File

@ -3,3 +3,6 @@ redirect_to: 'styleguide/index.md'
---
This document was moved to [another location](styleguide/index.md).
<!-- This redirect file can be deleted after <2022-02-13>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->

View File

@ -1,3 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/components/status/'
---

View File

@ -1,3 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/components/dropdown/'
---

View File

@ -456,7 +456,7 @@ Cluster.
| Attribute | Type | Required | Description |
|:----------|:-------|:---------|:------------|
| `alert` | Hash | yes | Alerts detail. Same format as [3rd party alert](../operations/incident_management/alert_integrations.md#customize-the-alert-payload-outside-of-gitlab). |
| `alert` | Hash | yes | Alerts detail. Same format as [3rd party alert](../operations/incident_management/integrations.md#customize-the-alert-payload-outside-of-gitlab). |
```plaintext
POST internal/kubernetes/modules/cilium_alert

View File

@ -4,10 +4,5 @@ redirect_to: 'https://about.gitlab.com/handbook/product/product-intelligence-gui
This document was moved to [another location](https://about.gitlab.com/handbook/product/product-intelligence-guide/).
<!-- Needed by the Product Intelligence group
Since our new standard for redirects otherwise lies within the gitlab-docs repo,
as long as we need a redirect to the handbook, we need to retain this file.
-->
<!-- This redirect file can be deleted after December 1, 2021. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/product-foundations/motion/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/motion/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/product-foundations/illustration/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/illustration/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/resources/design-resources/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/resources/design-resources/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://design.gitlab.com/'
---
The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).

View File

@ -1,3 +0,0 @@
---
redirect_to: 'documentation/index.md'
---

View File

@ -1,5 +0,0 @@
---
redirect_to: installation.md#gitlab-directory-structure
---
This page was moved to [another location](installation.md#gitlab-directory-structure).

View File

@ -39,6 +39,11 @@ GitLab can be integrated with the following external services to enhance securit
GitLab also provides features to improve the security of your own application. For more details see [GitLab Secure](../user/application_security/index.md).
## Security partners
GitLab has integrated with several security partners. For more information, see
[Security partners integration](security_partners/index.md).
## Continuous integration
GitLab can be integrated with the following external service for continuous integration:

View File

@ -0,0 +1,13 @@
---
stage: Protect
group: Container Security
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/#designated-technical-writers
type: index
---
# Security partner integrations
You can integrate GitLab with its security partners. This page has information on how do this with
each security partner:
- [Anchore](https://docs.anchore.com/current/docs/using/integration/ci_cd/gitlab/)

View File

@ -1,199 +1,8 @@
---
stage: Monitor
group: Health
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
redirect_to: 'integrations.md'
---
# Alert integrations **(FREE)**
This document was moved to [another location](integrations.md).
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.8.
GitLab can accept alerts from any source via a webhook receiver. This can be configured
generically or, in GitLab versions 13.1 and greater, you can configure
[External Prometheus instances](../metrics/alerts.md#external-prometheus-instances)
to use this endpoint.
## Integrations list
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/245331) in [GitLab Core](https://about.gitlab.com/pricing/) 13.5.
With Maintainer or higher [permissions](../../user/permissions.md), you can view
the list of configured alerts integrations by navigating to
**Settings > Operations** in your project's sidebar menu, and expanding **Alerts** section.
The list displays the integration name, type, and status (enabled or disabled):
![Current Integrations](img/integrations_list_v13_5.png)
## Configuration
GitLab can receive alerts via a HTTP endpoint that you configure,
or the [Prometheus integration](#external-prometheus-integration).
### Single HTTP Endpoint **(FREE)**
Enabling the HTTP Endpoint in a GitLab projects activates it to
receive alert payloads in JSON format. You can always
[customize the payload](#customize-the-alert-payload-outside-of-gitlab) to your liking.
1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md)
for a project.
1. Navigate to **Settings > Operations** in your project.
1. Expand the **Alerts** section, and in the **Integration** dropdown menu, select **Generic**.
1. Toggle the **Active** alert setting to display the **URL** and **Authorization Key**
for the webhook configuration.
### HTTP Endpoints **PREMIUM**
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4442) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6.
In [GitLab Premium](https://about.gitlab.com/pricing/), you can create multiple
unique HTTP endpoints to receive alerts from any external source in JSON format,
and you can [customize the payload](#customize-the-alert-payload-outside-of-gitlab).
1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md)
for a project.
1. Navigate to **Settings > Operations** in your project.
1. Expand the **Alerts** section.
1. For each endpoint you want to create:
1. In the **Integration** dropdown menu, select **HTTP Endpoint**.
1. Name the integration.
1. Toggle the **Active** alert setting to display the **URL** and **Authorization Key**
for the webhook configuration. You must also input the URL and Authorization Key
in your external service.
1. _(Optional)_ To generate a test alert to test the new integration, enter a
sample payload, then click **Save and test alert payload**. Valid JSON is required.
1. Click **Save Integration**.
The new HTTP Endpoint displays in the [integrations list](#integrations-list).
You can edit the integration by selecting the **{pencil}** pencil icon on the right
side of the integrations list.
### External Prometheus integration
For GitLab versions 13.1 and greater, please read
[External Prometheus Instances](../metrics/alerts.md#external-prometheus-instances)
to configure alerts for this integration.
## Customize the alert payload outside of GitLab
For all integration types, you can customize the payload by sending the following
parameters. All fields are optional. If the incoming alert does not contain a value for the `Title` field, a default value of `New: Incident` will be applied.
| Property | Type | Description |
| ------------------------- | --------------- | ----------- |
| `title` | String | The title of the incident. |
| `description` | String | A high-level summary of the problem. |
| `start_time` | DateTime | The time of the incident. If none is provided, a timestamp of the issue is used. |
| `end_time` | DateTime | For existing alerts only. When provided, the alert is resolved and the associated incident is closed. |
| `service` | String | The affected service. |
| `monitoring_tool` | String | The name of the associated monitoring tool. |
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
| `gitlab_environment_name` | String | The name of the associated GitLab [environment](../../ci/environments/index.md). Required to [display alerts on a dashboard](../../user/operations_dashboard/index.md#adding-a-project-to-the-dashboard). |
You can also add custom fields to the alert's payload. The values of extra
parameters aren't limited to primitive types (such as strings or numbers), but
can be a nested JSON object. For example:
```json
{ "foo": { "bar": { "baz": 42 } } }
```
NOTE:
Ensure your requests are smaller than the
[payload application limits](../../administration/instance_limits.md#generic-alert-json-payloads).
Example request:
```shell
curl --request POST \
--data '{"title": "Incident title"}' \
--header "Authorization: Bearer <authorization_key>" \
--header "Content-Type: application/json" \
<url>
```
The `<authorization_key>` and `<url>` values can be found when configuring an alert integration.
Example payload:
```json
{
"title": "Incident title",
"description": "Short description of the incident",
"start_time": "2019-09-12T06:00:55Z",
"service": "service affected",
"monitoring_tool": "value",
"hosts": "value",
"severity": "high",
"fingerprint": "d19381d4e8ebca87b55cda6e8eee7385",
"foo": {
"bar": {
"baz": 42
}
}
}
```
## Triggering test alerts
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Core in 13.2.
After a [project maintainer or owner](../../user/permissions.md)
configures an integration, you can trigger a test
alert to confirm your integration works properly.
1. Sign in as a user with Developer or greater [permissions](../../user/permissions.md).
1. Navigate to **Settings > Operations** in your project.
1. Click **Alerts endpoint** to expand the section.
1. Enter a sample payload in **Alert test payload** (valid JSON is required).
1. Click **Test alert payload**.
GitLab displays an error or success message, depending on the outcome of your test.
## Automatic grouping of identical alerts **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
In GitLab versions 13.2 and greater, GitLab groups alerts based on their
payload. When an incoming alert contains the same payload as another alert
(excluding the `start_time` and `hosts` attributes), GitLab groups these alerts
together and displays a counter on the [Alert Management List](incidents.md)
and details pages.
If the existing alert is already `resolved`, GitLab creates a new alert instead.
![Alert Management List](img/alert_list_v13_1.png)
## Link to your Opsgenie Alerts
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
WARNING:
We are building deeper integration with Opsgenie and other alerting tools through
[HTTP endpoint integrations](#single-http-endpoint) so you can see alerts in
the GitLab interface. As a result, the previous direct link to Opsgenie Alerts from
the GitLab alerts list is deprecated in
GitLab versions [13.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/273657).
You can monitor alerts using a GitLab integration with [Opsgenie](https://www.atlassian.com/software/opsgenie).
If you enable the Opsgenie integration, you can't have other GitLab alert
services
active at the same time.
To enable Opsgenie integration:
1. Sign in as a user with Maintainer or Owner [permissions](../../user/permissions.md).
1. Navigate to **Operations > Alerts**.
1. In the **Integrations** select box, select **Opsgenie**.
1. Select the **Active** toggle.
1. In the **API URL** field, enter the base URL for your Opsgenie integration,
such as `https://app.opsgenie.com/alert/list`.
1. Select **Save changes**.
After you enable the integration, navigate to the Alerts list page at
**Operations > Alerts**, and then select **View alerts in Opsgenie**.
<!-- This redirect file can be deleted after <2021-05-03>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->

View File

@ -12,7 +12,7 @@ Incident Management enables developers to easily triage and view the alerts and
generated by their application. By surfacing alerts and incidents where the code is
being developed, efficiency and awareness can be increased. Check out the following sections for more information:
- [Integrate your monitoring tools](alert_integrations.md).
- [Integrate your monitoring tools](integrations.md).
- Receive [notifications](paging.md) for triggered alerts.
- Triage [Alerts](alerts.md) and [Incidents](incidents.md).
- Inform stakeholders with [Status Page](status_page.md).

View File

@ -4,9 +4,19 @@ group: Health
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
---
# Integrations **(FREE)**
# Alert integrations **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/245331) in GitLab Free 13.5.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.8.
GitLab can accept alerts from any source via a webhook receiver. This can be configured
generically or, in GitLab versions 13.1 and greater, you can configure
[External Prometheus instances](../metrics/alerts.md#external-prometheus-instances)
to use this endpoint.
## Integrations list
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/245331) in [GitLab Core](https://about.gitlab.com/pricing/) 13.5.
With Maintainer or higher [permissions](../../user/permissions.md), you can view
the list of configured alerts integrations by navigating to
@ -14,3 +24,176 @@ the list of configured alerts integrations by navigating to
The list displays the integration name, type, and status (enabled or disabled):
![Current Integrations](img/integrations_list_v13_5.png)
## Configuration
GitLab can receive alerts via a HTTP endpoint that you configure,
or the [Prometheus integration](#external-prometheus-integration).
### Single HTTP Endpoint **(FREE)**
Enabling the HTTP Endpoint in a GitLab projects activates it to
receive alert payloads in JSON format. You can always
[customize the payload](#customize-the-alert-payload-outside-of-gitlab) to your liking.
1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md)
for a project.
1. Navigate to **Settings > Operations** in your project.
1. Expand the **Alerts** section, and in the **Integration** dropdown menu, select **Generic**.
1. Toggle the **Active** alert setting to display the **URL** and **Authorization Key**
for the webhook configuration.
### HTTP Endpoints **PREMIUM**
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4442) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6.
In [GitLab Premium](https://about.gitlab.com/pricing/), you can create multiple
unique HTTP endpoints to receive alerts from any external source in JSON format,
and you can [customize the payload](#customize-the-alert-payload-outside-of-gitlab).
1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md)
for a project.
1. Navigate to **Settings > Operations** in your project.
1. Expand the **Alerts** section.
1. For each endpoint you want to create:
1. In the **Integration** dropdown menu, select **HTTP Endpoint**.
1. Name the integration.
1. Toggle the **Active** alert setting to display the **URL** and **Authorization Key**
for the webhook configuration. You must also input the URL and Authorization Key
in your external service.
1. _(Optional)_ To generate a test alert to test the new integration, enter a
sample payload, then click **Save and test alert payload**. Valid JSON is required.
1. Click **Save Integration**.
The new HTTP Endpoint displays in the [integrations list](#integrations-list).
You can edit the integration by selecting the **{pencil}** pencil icon on the right
side of the integrations list.
### External Prometheus integration
For GitLab versions 13.1 and greater, please read
[External Prometheus Instances](../metrics/alerts.md#external-prometheus-instances)
to configure alerts for this integration.
## Customize the alert payload outside of GitLab
For all integration types, you can customize the payload by sending the following
parameters. All fields are optional. If the incoming alert does not contain a value for the `Title` field, a default value of `New: Incident` will be applied.
| Property | Type | Description |
| ------------------------- | --------------- | ----------- |
| `title` | String | The title of the incident. |
| `description` | String | A high-level summary of the problem. |
| `start_time` | DateTime | The time of the incident. If none is provided, a timestamp of the issue is used. |
| `end_time` | DateTime | For existing alerts only. When provided, the alert is resolved and the associated incident is closed. |
| `service` | String | The affected service. |
| `monitoring_tool` | String | The name of the associated monitoring tool. |
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
| `gitlab_environment_name` | String | The name of the associated GitLab [environment](../../ci/environments/index.md). Required to [display alerts on a dashboard](../../user/operations_dashboard/index.md#adding-a-project-to-the-dashboard). |
You can also add custom fields to the alert's payload. The values of extra
parameters aren't limited to primitive types (such as strings or numbers), but
can be a nested JSON object. For example:
```json
{ "foo": { "bar": { "baz": 42 } } }
```
NOTE:
Ensure your requests are smaller than the
[payload application limits](../../administration/instance_limits.md#generic-alert-json-payloads).
Example request:
```shell
curl --request POST \
--data '{"title": "Incident title"}' \
--header "Authorization: Bearer <authorization_key>" \
--header "Content-Type: application/json" \
<url>
```
The `<authorization_key>` and `<url>` values can be found when configuring an alert integration.
Example payload:
```json
{
"title": "Incident title",
"description": "Short description of the incident",
"start_time": "2019-09-12T06:00:55Z",
"service": "service affected",
"monitoring_tool": "value",
"hosts": "value",
"severity": "high",
"fingerprint": "d19381d4e8ebca87b55cda6e8eee7385",
"foo": {
"bar": {
"baz": 42
}
}
}
```
## Triggering test alerts
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Core in 13.2.
After a [project maintainer or owner](../../user/permissions.md)
configures an integration, you can trigger a test
alert to confirm your integration works properly.
1. Sign in as a user with Developer or greater [permissions](../../user/permissions.md).
1. Navigate to **Settings > Operations** in your project.
1. Click **Alerts endpoint** to expand the section.
1. Enter a sample payload in **Alert test payload** (valid JSON is required).
1. Click **Test alert payload**.
GitLab displays an error or success message, depending on the outcome of your test.
## Automatic grouping of identical alerts **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
In GitLab versions 13.2 and greater, GitLab groups alerts based on their
payload. When an incoming alert contains the same payload as another alert
(excluding the `start_time` and `hosts` attributes), GitLab groups these alerts
together and displays a counter on the [Alert Management List](incidents.md)
and details pages.
If the existing alert is already `resolved`, GitLab creates a new alert instead.
![Alert Management List](img/alert_list_v13_1.png)
## Link to your Opsgenie Alerts
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
WARNING:
We are building deeper integration with Opsgenie and other alerting tools through
[HTTP endpoint integrations](#single-http-endpoint) so you can see alerts in
the GitLab interface. As a result, the previous direct link to Opsgenie Alerts from
the GitLab alerts list is deprecated in
GitLab versions [13.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/273657).
You can monitor alerts using a GitLab integration with [Opsgenie](https://www.atlassian.com/software/opsgenie).
If you enable the Opsgenie integration, you can't have other GitLab alert
services
active at the same time.
To enable Opsgenie integration:
1. Sign in as a user with Maintainer or Owner [permissions](../../user/permissions.md).
1. Navigate to **Operations > Alerts**.
1. In the **Integrations** select box, select **Opsgenie**.
1. Select the **Active** toggle.
1. In the **API URL** field, enter the base URL for your Opsgenie integration,
such as `https://app.opsgenie.com/alert/list`.
1. Select **Save changes**.
After you enable the integration, navigate to the Alerts list page at
**Operations > Alerts**, and then select **View alerts in Opsgenie**.

View File

@ -83,7 +83,7 @@ You can display alerts with a `gitlab_environment_name` of `production`
In GitLab versions 13.1 and greater, you can configure your manually configured
Prometheus server to use the
[Generic alerts integration](../incident_management/alert_integrations.md).
[Generic alerts integration](../incident_management/integrations.md).
## Trigger actions from alerts **(ULTIMATE)**

View File

@ -1,3 +0,0 @@
---
redirect_to: '../offline/index.md'
---

View File

@ -3,3 +3,6 @@ redirect_to: 'usage_trends.md'
---
This document was moved to [another location](usage_trends.md).
<!-- This redirect file can be deleted after <2022-03-20>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->

View File

@ -4,5 +4,5 @@ redirect_to: 'https://docs.gitlab.com/runner/install/kubernetes-agent.html'
This document was moved to [another location](https://docs.gitlab.com/runner/install/kubernetes-agent.html).
<!-- This redirect file can be deleted after <2021-04-22>. -->
<!-- This redirect file can be deleted after <2022-02-01>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->

View File

@ -39,7 +39,7 @@ Click on the service links to see further configuration instructions and details
| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients | No |
| External Wiki | Replaces the link to the internal wiki with a link to an external wiki | No |
| Flowdock | Flowdock is a collaboration web app for technical teams | No |
| [Generic alerts](../../../operations/incident_management/alert_integrations.md) **(ULTIMATE)** | Receive alerts on GitLab from any source | No |
| [Generic alerts](../../../operations/incident_management/integrations.md) **(ULTIMATE)** | Receive alerts on GitLab from any source | No |
| [GitHub](github.md) **(PREMIUM)** | Sends pipeline notifications to GitHub | No |
| [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat | No |
| [HipChat](hipchat.md) | Private group chat and IM | No |

View File

@ -361,7 +361,7 @@ module API
with: Entities::MergeRequestChanges,
current_user: current_user,
project: user_project,
access_raw_diffs: params.fetch(:access_raw_diffs, false)
access_raw_diffs: to_boolean(params.fetch(:access_raw_diffs, false))
end
desc 'Get the merge request pipelines' do

View File

@ -1,7 +1,8 @@
/* eslint-disable max-classes-per-file */
import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
import { editor as monacoEditor, languages as monacoLanguages } from 'monaco-editor';
import waitForPromises from 'helpers/wait_for_promises';
import Editor from '~/editor/editor_lite';
import { joinPaths } from '~/lib/utils/url_utility';
import EditorLite from '~/editor/editor_lite';
import { EditorLiteExtension } from '~/editor/extensions/editor_lite_extension_base';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import {
@ -13,6 +14,8 @@ import {
describe('Base editor', () => {
let editorEl;
let editor;
let defaultArguments;
const blobOriginalContent = 'Foo Foo';
const blobContent = 'Foo Bar';
const blobPath = 'test.md';
const blobGlobalId = 'snippet_777';
@ -21,15 +24,19 @@ describe('Base editor', () => {
beforeEach(() => {
setFixtures('<div id="editor" data-editor-loading></div>');
editorEl = document.getElementById('editor');
editor = new Editor();
defaultArguments = { el: editorEl, blobPath, blobContent, blobGlobalId };
editor = new EditorLite();
});
afterEach(() => {
editor.dispose();
editorEl.remove();
monacoEditor.getModels().forEach((model) => {
model.dispose();
});
});
const createUri = (...paths) => Uri.file([URI_PREFIX, ...paths].join('/'));
const uriFilePath = joinPaths('/', URI_PREFIX, blobGlobalId, blobPath);
it('initializes Editor with basic properties', () => {
expect(editor).toBeDefined();
@ -42,93 +49,192 @@ describe('Base editor', () => {
expect(editorEl.dataset.editorLoading).toBeUndefined();
});
describe('instance of the Editor', () => {
describe('instance of the Editor Lite', () => {
let modelSpy;
let instanceSpy;
let setModel;
let dispose;
let modelsStorage;
const setModel = jest.fn();
const dispose = jest.fn();
const mockModelReturn = (res = fakeModel) => {
modelSpy = jest.spyOn(monacoEditor, 'createModel').mockImplementation(() => res);
};
const mockDecorateInstance = (decorations = {}) => {
jest.spyOn(EditorLite, 'convertMonacoToELInstance').mockImplementation((inst) => {
return Object.assign(inst, decorations);
});
};
beforeEach(() => {
setModel = jest.fn();
dispose = jest.fn();
modelsStorage = new Map();
modelSpy = jest.spyOn(monacoEditor, 'createModel').mockImplementation(() => fakeModel);
instanceSpy = jest.spyOn(monacoEditor, 'create').mockImplementation(() => ({
setModel,
dispose,
onDidDispose: jest.fn(),
}));
jest.spyOn(monacoEditor, 'getModel').mockImplementation((uri) => {
return modelsStorage.get(uri.path);
modelSpy = jest.spyOn(monacoEditor, 'createModel');
});
describe('instance of the Code Editor', () => {
beforeEach(() => {
instanceSpy = jest.spyOn(monacoEditor, 'create');
});
it('throws an error if no dom element is supplied', () => {
mockDecorateInstance();
expect(() => {
editor.createInstance();
}).toThrow(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
expect(modelSpy).not.toHaveBeenCalled();
expect(instanceSpy).not.toHaveBeenCalled();
expect(EditorLite.convertMonacoToELInstance).not.toHaveBeenCalled();
});
it('creates model to be supplied to Monaco editor', () => {
mockModelReturn();
mockDecorateInstance({
setModel,
});
editor.createInstance(defaultArguments);
expect(modelSpy).toHaveBeenCalledWith(
blobContent,
undefined,
expect.objectContaining({
path: uriFilePath,
}),
);
expect(setModel).toHaveBeenCalledWith(fakeModel);
});
it('does not create a model automatically if model is passed as `null`', () => {
mockDecorateInstance({
setModel,
});
editor.createInstance({ ...defaultArguments, model: null });
expect(modelSpy).not.toHaveBeenCalled();
expect(setModel).not.toHaveBeenCalled();
});
it('initializes the instance on a supplied DOM node', () => {
editor.createInstance({ el: editorEl });
expect(editor.editorEl).not.toBe(null);
expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.anything());
});
it('with blobGlobalId, creates model with the id in uri', () => {
editor.createInstance(defaultArguments);
expect(modelSpy).toHaveBeenCalledWith(
blobContent,
undefined,
expect.objectContaining({
path: uriFilePath,
}),
);
});
it('initializes instance with passed properties', () => {
const instanceOptions = {
foo: 'bar',
};
editor.createInstance({
el: editorEl,
...instanceOptions,
});
expect(instanceSpy).toHaveBeenCalledWith(
editorEl,
expect.objectContaining(instanceOptions),
);
});
it('disposes instance when the global editor is disposed', () => {
mockDecorateInstance({
dispose,
});
editor.createInstance(defaultArguments);
expect(dispose).not.toHaveBeenCalled();
editor.dispose();
expect(dispose).toHaveBeenCalled();
});
it("removes the disposed instance from the global editor's storage and disposes the associated model", () => {
mockModelReturn();
mockDecorateInstance({
setModel,
});
const instance = editor.createInstance(defaultArguments);
expect(editor.instances).toHaveLength(1);
expect(fakeModel.dispose).not.toHaveBeenCalled();
instance.dispose();
expect(editor.instances).toHaveLength(0);
expect(fakeModel.dispose).toHaveBeenCalled();
});
});
it('throws an error if no dom element is supplied', () => {
expect(() => {
editor.createInstance();
}).toThrow(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
expect(modelSpy).not.toHaveBeenCalled();
expect(instanceSpy).not.toHaveBeenCalled();
expect(setModel).not.toHaveBeenCalled();
});
it('creates model to be supplied to Monaco editor', () => {
editor.createInstance({ el: editorEl, blobPath, blobContent, blobGlobalId: '' });
expect(modelSpy).toHaveBeenCalledWith(blobContent, undefined, createUri(blobPath));
expect(setModel).toHaveBeenCalledWith(fakeModel);
});
it('does not create a new model if a model for the path already exists', () => {
modelSpy = jest
.spyOn(monacoEditor, 'createModel')
.mockImplementation((content, lang, uri) => modelsStorage.set(uri.path, content));
const instanceOptions = { el: editorEl, blobPath, blobContent, blobGlobalId: '' };
const a = editor.createInstance(instanceOptions);
const b = editor.createInstance(instanceOptions);
expect(a === b).toBe(false);
expect(modelSpy).toHaveBeenCalledTimes(1);
});
it('initializes the instance on a supplied DOM node', () => {
editor.createInstance({ el: editorEl });
expect(editor.editorEl).not.toBe(null);
expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.anything());
});
it('with blobGlobalId, creates model with id in uri', () => {
editor.createInstance({ el: editorEl, blobPath, blobContent, blobGlobalId });
expect(modelSpy).toHaveBeenCalledWith(
blobContent,
undefined,
createUri(blobGlobalId, blobPath),
);
});
it('initializes instance with passed properties', () => {
const instanceOptions = {
foo: 'bar',
};
editor.createInstance({
el: editorEl,
...instanceOptions,
describe('instance of the Diff Editor', () => {
beforeEach(() => {
instanceSpy = jest.spyOn(monacoEditor, 'createDiffEditor');
});
expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.objectContaining(instanceOptions));
});
it('disposes instance when the editor is disposed', () => {
editor.createInstance({ el: editorEl, blobPath, blobContent, blobGlobalId });
it('Diff Editor goes through the normal path of Code Editor just with the flag ON', () => {
const spy = jest.spyOn(editor, 'createInstance').mockImplementation(() => {});
editor.createDiffInstance();
expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
isDiff: true,
}),
);
});
expect(dispose).not.toHaveBeenCalled();
it('initializes the instance on a supplied DOM node', () => {
const wrongInstanceSpy = jest.spyOn(monacoEditor, 'create').mockImplementation(() => ({}));
editor.createDiffInstance({ ...defaultArguments, blobOriginalContent });
editor.dispose();
expect(editor.editorEl).not.toBe(null);
expect(wrongInstanceSpy).not.toHaveBeenCalled();
expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.anything());
});
expect(dispose).toHaveBeenCalled();
it('creates correct model for the Diff Editor', () => {
const instance = editor.createDiffInstance({ ...defaultArguments, blobOriginalContent });
const getDiffModelValue = (model) => instance.getModel()[model].getValue();
expect(modelSpy).toHaveBeenCalledTimes(2);
expect(modelSpy.mock.calls[0]).toEqual([
blobContent,
undefined,
expect.objectContaining({
path: uriFilePath,
}),
]);
expect(modelSpy.mock.calls[1]).toEqual([blobOriginalContent, 'markdown']);
expect(getDiffModelValue('original')).toBe(blobOriginalContent);
expect(getDiffModelValue('modified')).toBe(blobContent);
});
it('correctly disposes the diff editor model', () => {
const modifiedModel = fakeModel;
const originalModel = { ...fakeModel };
mockDecorateInstance({
getModel: jest.fn().mockReturnValue({
original: originalModel,
modified: modifiedModel,
}),
});
const instance = editor.createDiffInstance({ ...defaultArguments, blobOriginalContent });
expect(editor.instances).toHaveLength(1);
expect(originalModel.dispose).not.toHaveBeenCalled();
expect(modifiedModel.dispose).not.toHaveBeenCalled();
instance.dispose();
expect(editor.instances).toHaveLength(0);
expect(originalModel.dispose).toHaveBeenCalled();
expect(modifiedModel.dispose).toHaveBeenCalled();
});
});
});
@ -148,16 +254,14 @@ describe('Base editor', () => {
editorEl2 = document.getElementById('editor2');
inst1Args = {
el: editorEl1,
blobGlobalId,
};
inst2Args = {
el: editorEl2,
blobContent,
blobPath,
blobGlobalId,
};
editor = new Editor();
editor = new EditorLite();
instanceSpy = jest.spyOn(monacoEditor, 'create');
});
@ -187,8 +291,20 @@ describe('Base editor', () => {
expect(model1).not.toEqual(model2);
});
it('does not create a new model if a model for the path & globalId combo already exists', () => {
const modelSpy = jest.spyOn(monacoEditor, 'createModel');
inst1 = editor.createInstance({ ...inst2Args, blobGlobalId });
inst2 = editor.createInstance({ ...inst2Args, el: editorEl1, blobGlobalId });
const model1 = inst1.getModel();
const model2 = inst2.getModel();
expect(modelSpy).toHaveBeenCalledTimes(1);
expect(model1).toBe(model2);
});
it('shares global editor options among all instances', () => {
editor = new Editor({
editor = new EditorLite({
readOnly: true,
});
@ -200,7 +316,7 @@ describe('Base editor', () => {
});
it('allows overriding editor options on the instance level', () => {
editor = new Editor({
editor = new EditorLite({
readOnly: true,
});
inst1 = editor.createInstance({
@ -221,6 +337,7 @@ describe('Base editor', () => {
expect(monacoEditor.getModels()).toHaveLength(2);
inst1.dispose();
expect(inst1.getModel()).toBe(null);
expect(inst2.getModel()).not.toBe(null);
expect(editor.instances).toHaveLength(1);
@ -423,19 +540,20 @@ describe('Base editor', () => {
el: editorEl,
blobPath,
blobContent,
blobGlobalId,
extensions,
});
};
beforeEach(() => {
editorExtensionSpy = jest.spyOn(Editor, 'pushToImportsArray').mockImplementation((arr) => {
arr.push(
Promise.resolve({
default: {},
}),
);
});
editorExtensionSpy = jest
.spyOn(EditorLite, 'pushToImportsArray')
.mockImplementation((arr) => {
arr.push(
Promise.resolve({
default: {},
}),
);
});
});
it.each([undefined, [], [''], ''])(
@ -472,9 +590,14 @@ describe('Base editor', () => {
const eventSpy = jest.fn().mockImplementation(() => {
calls.push('event');
});
const useSpy = jest.spyOn(editor, 'use').mockImplementation(() => {
const useSpy = jest.fn().mockImplementation(() => {
calls.push('use');
});
jest.spyOn(EditorLite, 'convertMonacoToELInstance').mockImplementation((inst) => {
const decoratedInstance = inst;
decoratedInstance.use = useSpy;
return decoratedInstance;
});
editorEl.addEventListener(EDITOR_READY_EVENT, eventSpy);
instance = instanceConstructor('foo, bar');
await waitForPromises();
@ -508,12 +631,6 @@ describe('Base editor', () => {
expect(inst1.alpha()).toEqual(alphaRes);
expect(inst2.alpha()).toEqual(alphaRes);
});
it('extends specific instance if it has been passed', () => {
editor.use(AlphaExt, inst2);
expect(inst1.alpha).toBeUndefined();
expect(inst2.alpha()).toEqual(alphaRes);
});
});
});
@ -547,7 +664,7 @@ describe('Base editor', () => {
it('sets default syntax highlighting theme', () => {
const expectedTheme = themes.find((t) => t.name === DEFAULT_THEME);
editor = new Editor();
editor = new EditorLite();
expect(themeDefineSpy).toHaveBeenCalledWith(DEFAULT_THEME, expectedTheme.data);
expect(themeSetSpy).toHaveBeenCalledWith(DEFAULT_THEME);
@ -559,7 +676,7 @@ describe('Base editor', () => {
expect(expectedTheme.name).not.toBe(DEFAULT_THEME);
window.gon.user_color_scheme = expectedTheme.name;
editor = new Editor();
editor = new EditorLite();
expect(themeDefineSpy).toHaveBeenCalledWith(expectedTheme.name, expectedTheme.data);
expect(themeSetSpy).toHaveBeenCalledWith(expectedTheme.name);
@ -570,7 +687,7 @@ describe('Base editor', () => {
const nonExistentTheme = { name };
window.gon.user_color_scheme = nonExistentTheme.name;
editor = new Editor();
editor = new EditorLite();
expect(themeDefineSpy).not.toHaveBeenCalled();
expect(themeSetSpy).toHaveBeenCalledWith(DEFAULT_THEME);

View File

@ -7,6 +7,7 @@ import {
import Editor from '~/ide/lib/editor';
import { createStore } from '~/ide/stores';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
import { EDITOR_TYPE_DIFF } from '~/editor/constants';
import { file } from '../helpers';
describe('Multi-file editor library', () => {
@ -125,7 +126,7 @@ describe('Multi-file editor library', () => {
});
it('sets original & modified when diff editor', () => {
jest.spyOn(instance.instance, 'getEditorType').mockReturnValue('vs.editor.IDiffEditor');
jest.spyOn(instance.instance, 'getEditorType').mockReturnValue(EDITOR_TYPE_DIFF);
jest.spyOn(instance.instance, 'setModel').mockImplementation(() => {});
instance.attachModel(model);

View File

@ -30,7 +30,7 @@ RSpec.describe OperationsHelper do
it 'returns the correct values' do
expect(subject).to eq(
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'),
'alerts_setup_url' => help_page_path('operations/incident_management/integrations.md', anchor: 'configuration'),
'alerts_usage_url' => project_alert_management_index_path(project),
'prometheus_form_path' => project_service_path(project, prometheus_service),
'prometheus_reset_key_path' => reset_alerting_token_project_settings_operations_path(project),

View File

@ -1894,8 +1894,11 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'removes the remote' do
repository_rugged.remotes.create(remote_name, url)
repository.remove_remote(remote_name)
expect(repository.remove_remote(remote_name)).to be true
# Since we deleted the remote via Gitaly, Rugged doesn't know
# this changed underneath it. Let's refresh the Rugged repo.
repository_rugged = Rugged::Repository.new(repository_path)
expect(repository_rugged.remotes[remote_name]).to be_nil
end
end

View File

@ -715,6 +715,10 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
context 'when external issue tracker is enabled' do
let(:project) { create(:project, :repository) }
subject { create(:merge_request, source_project: project) }
before do
subject.project.has_external_issue_tracker = true
subject.project.save!
@ -788,6 +792,10 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
context 'when only external issue tracker enabled' do
let(:project) { create(:project, :repository) }
subject { create(:merge_request, source_project: project) }
before do
subject.project.has_external_issue_tracker = true
subject.project.issues_enabled = false
@ -1274,8 +1282,6 @@ RSpec.describe MergeRequest, factory_default: :keep do
let(:mentioned_issue) { create :issue, project: subject.project }
let(:commit) { double('commit', safe_message: "Fixes #{closing_issue.to_reference}") }
subject { create(:merge_request, source_project: create(:project)) }
it 'detects issues mentioned in description but not closed' do
subject.project.add_developer(subject.author)
subject.description = "Is related to #{mentioned_issue.to_reference} and #{closing_issue.to_reference}"
@ -1478,8 +1484,6 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
describe '#default_merge_commit_message' do
subject { create(:merge_request, source_project: create(:project)) }
it 'includes merge information as the title' do
request = build(:merge_request, source_branch: 'source', target_branch: 'target')
@ -3424,6 +3428,10 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
context 'when resolve_outdated_diff_discussions is set' do
let(:project) { create(:project, :repository) }
subject { create(:merge_request, source_project: project) }
before do
discussion
@ -3444,7 +3452,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
describe '#branch_merge_base_commit' do
let(:project) { create(:project, :repository) }
subject { create(:merge_request, :with_diffs, source_project: project) }
subject { create(:merge_request, source_project: project) }
context 'source and target branch exist' do
it { expect(subject.branch_merge_base_commit.sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }
@ -3467,7 +3475,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
context "with diffs" do
let(:project) { create(:project, :repository) }
subject { create(:merge_request, :with_diffs, source_project: project) }
subject { create(:merge_request, source_project: project) }
let(:expected_diff_refs) do
Gitlab::Diff::DiffRefs.new(
@ -3871,7 +3879,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
describe '#fetch_ref!' do
let(:project) { create(:project, :repository) }
subject { create(:merge_request, :with_diffs, source_project: project) }
subject { create(:merge_request, source_project: project) }
it 'fetches the ref correctly' do
expect { subject.target_project.repository.delete_refs(subject.ref_path) }.not_to raise_error

View File

@ -1136,11 +1136,11 @@ RSpec.describe Repository do
expect(repository.license_key).to be_nil
end
it 'returns nil when the content is not recognizable' do
it 'returns other when the content is not recognizable' do
repository.create_file(user, 'LICENSE', 'Gitlab B.V.',
message: 'Add LICENSE', branch_name: 'master')
expect(repository.license_key).to be_nil
expect(repository.license_key).to eq('other')
end
it 'returns nil when the commit SHA does not exist' do
@ -1180,11 +1180,12 @@ RSpec.describe Repository do
expect(repository.license).to be_nil
end
it 'returns nil when the content is not recognizable' do
it 'returns other when the content is not recognizable' do
license = Licensee::License.new('other')
repository.create_file(user, 'LICENSE', 'Gitlab B.V.',
message: 'Add LICENSE', branch_name: 'master')
expect(repository.license).to be_nil
expect(repository.license).to eq(license)
end
it 'returns the license' do

View File

@ -1542,9 +1542,9 @@ RSpec.describe API::MergeRequests do
end
end
context 'when access_raw_diffs is passed as an option' do
context 'when access_raw_diffs is true' do
it_behaves_like 'accesses diffs via raw_diffs' do
let(:params) { { access_raw_diffs: true } }
let(:params) { { access_raw_diffs: "true" } }
end
end
end

View File

@ -65,7 +65,9 @@ RSpec.describe API::Templates do
expect(json_response['nickname']).to be_nil
expect(json_response['popular']).to be true
expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/')
expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT')
# This was dropped:
# https://github.com/github/choosealicense.com/commit/325806b42aa3d5b78e84120327ec877bc936dbdd#diff-66df8f1997786f7052d29010f2cbb4c66391d60d24ca624c356acc0ab986f139
expect(json_response['source_url']).to be_nil
expect(json_response['description']).to include('A short and simple permissive license with conditions')
expect(json_response['conditions']).to eq(%w[include-copyright])
expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use])
@ -81,7 +83,7 @@ RSpec.describe API::Templates do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(12)
expect(json_response.size).to eq(13)
expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
end