Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-06-23 18:09:28 +00:00
parent 5eeb391043
commit 901af2a0dd
49 changed files with 638 additions and 278 deletions

View File

@ -41,7 +41,6 @@ After your merge request has been approved according to our [approval guidelines
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details)
- [ ] Once your `master` MR is merged, comment on the original security issue with a link to that MR indicating the issue is fixed.
## Summary

View File

@ -27,17 +27,22 @@ const setupAxiosStartupCalls = axios => {
if (existing) {
// eslint-disable-next-line no-param-reassign
req.adapter = () =>
existing.fetchCall.then(res =>
existing.fetchCall.then(res => {
const fetchHeaders = {};
res.headers.forEach((val, key) => {
fetchHeaders[key] = val;
});
// eslint-disable-next-line promise/no-nesting
res.json().then(data => ({
return res.json().then(data => ({
data,
status: res.status,
statusText: res.statusText,
headers: res.headers,
headers: fetchHeaders,
config: req,
request: req,
})),
);
}));
});
}
return req;

View File

@ -19,6 +19,7 @@ import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// everything else
import loadAwardsHandler from './awards_handler';
import applyGitLabUIConfig from '@gitlab/ui/dist/config';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown';
@ -42,6 +43,8 @@ import { __ } from './locale';
import 'ee_else_ce/main_ee';
applyGitLabUIConfig();
// expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;

View File

@ -353,7 +353,10 @@ export default {
</gl-deprecated-button>
</div>
<div v-if="externalDashboardUrl.length" class="mb-2 mr-2 d-flex d-sm-block">
<div
v-if="externalDashboardUrl && externalDashboardUrl.length"
class="mb-2 mr-2 d-flex d-sm-block"
>
<gl-deprecated-button
class="flex-grow-1 js-external-dashboard-link"
variant="primary"

View File

@ -30,7 +30,7 @@ const defaultClient = createDefaultClient(
},
readme(_, { url }) {
return axios
.get(url, { params: { viewer: 'rich', format: 'json' } })
.get(url, { params: { format: 'json', viewer: 'rich' } })
.then(({ data }) => ({ ...data, __typename: 'ReadmeFile' }));
},
},

View File

@ -10,6 +10,7 @@ module AlertManagement
include Sortable
include Noteable
include Gitlab::SQL::Pattern
include Presentable
STATUSES = {
triggered: 0,
@ -25,6 +26,8 @@ module AlertManagement
ignored: :ignore
}.freeze
DETAILS_IGNORED_PARAMS = %w(start_time).freeze
belongs_to :project
belongs_to :issue, optional: true
@ -136,7 +139,7 @@ module AlertManagement
end
def details
details_payload = payload.except(*attributes.keys)
details_payload = payload.except(*attributes.keys, *DETAILS_IGNORED_PARAMS)
Gitlab::Utils::InlineHash.merge_keys(details_payload)
end
@ -161,6 +164,12 @@ module AlertManagement
project.execute_services(hook_data, :alert_hooks)
end
def present
return super(presenter_class: AlertManagement::PrometheusAlertPresenter) if prometheus?
super
end
private
def hook_data

View File

@ -553,10 +553,28 @@ module Ci
end
end
def lazy_ref_commit
return unless ::Gitlab::Ci::Features.pipeline_latest?
BatchLoader.for(ref).batch do |refs, loader|
next unless project.repository_exists?
project.repository.list_commits_by_ref_name(refs).then do |commits|
loader.call(ref, commits[ref])
end
end
end
def latest?
return false unless git_ref && commit.present?
project.commit(git_ref) == commit
unless ::Gitlab::Ci::Features.pipeline_latest?
return project.commit(git_ref) == commit
end
return false if lazy_ref_commit.nil?
lazy_ref_commit.id == commit.id
end
def retried

View File

@ -15,5 +15,19 @@ module Integration
Project.where(id: custom_integration_project_ids)
end
def ids_without_integration(integration, limit)
services = Service
.select('1')
.where('services.project_id = projects.id')
.where(type: integration.type)
Project
.where('NOT EXISTS (?)', services)
.where(pending_delete: false)
.where(archived: false)
.limit(limit)
.pluck(:id)
end
end
end

View File

@ -0,0 +1,98 @@
# frozen_string_literal: true
module AlertManagement
class AlertPresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize
include IncidentManagement::Settings
MARKDOWN_LINE_BREAK = " \n".freeze
def initialize(alert, _attributes = {})
super
@alert = alert
@project = alert.project
end
def issue_description
horizontal_line = "\n\n---\n\n"
[
issue_summary_markdown,
alert_markdown,
incident_management_setting.issue_template_content
].compact.join(horizontal_line)
end
def start_time
started_at&.strftime('%d %B %Y, %-l:%M%p (%Z)')
end
def issue_summary_markdown
<<~MARKDOWN.chomp
#### Summary
#{metadata_list}
#{alert_details}#{metric_embed_for_alert}
MARKDOWN
end
private
attr_reader :alert, :project
def alerting_alert
strong_memoize(:alerting_alert) do
Gitlab::Alerting::Alert.new(project: project, payload: alert.payload).present
end
end
def alert_markdown; end
def metadata_list
metadata = []
metadata << list_item('Start time', start_time)
metadata << list_item('Severity', severity)
metadata << list_item('full_query', backtick(full_query)) if full_query
metadata << list_item('Service', service) if service
metadata << list_item('Monitoring tool', monitoring_tool) if monitoring_tool
metadata << list_item('Hosts', host_links) if hosts.any?
metadata.join(MARKDOWN_LINE_BREAK)
end
def alert_details
if details.present?
<<~MARKDOWN.chomp
#### Alert Details
#{details_list}
MARKDOWN
end
end
def details_list
alert.details
.map { |label, value| list_item(label, value) }
.join(MARKDOWN_LINE_BREAK)
end
def metric_embed_for_alert; end
def full_query; end
def list_item(key, value)
"**#{key}:** #{value}".strip
end
def backtick(value)
"`#{value}`"
end
def host_links
hosts.join(' ')
end
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module AlertManagement
class PrometheusAlertPresenter < AlertManagement::AlertPresenter
private
def alert_markdown
alerting_alert.alert_markdown
end
def details_list
alerting_alert.annotation_list
end
def metric_embed_for_alert
alerting_alert.metric_embed_for_alert
end
def full_query
alerting_alert.full_query
end
end
end

View File

@ -58,6 +58,21 @@ module Projects
MARKDOWN
end
def annotation_list
strong_memoize(:annotation_list) do
annotations
.reject { |annotation| annotation.label.in?(RESERVED_ANNOTATIONS | GENERIC_ALERT_SUMMARY_ANNOTATIONS) }
.map { |annotation| list_item(annotation.label, annotation.value) }
.join(MARKDOWN_LINE_BREAK)
end
end
def metric_embed_for_alert
url = embed_url_for_gitlab_alert || embed_url_for_self_managed_alert
"\n[](#{url})" if url
end
private
def alert_title
@ -93,15 +108,6 @@ module Projects
end
end
def annotation_list
strong_memoize(:annotation_list) do
annotations
.reject { |annotation| annotation.label.in?(RESERVED_ANNOTATIONS | GENERIC_ALERT_SUMMARY_ANNOTATIONS) }
.map { |annotation| list_item(annotation.label, annotation.value) }
.join(MARKDOWN_LINE_BREAK)
end
end
def list_item(key, value)
"**#{key}:** #{value}".strip
end
@ -120,12 +126,6 @@ module Projects
Array(hosts.value).join(' ')
end
def metric_embed_for_alert
url = embed_url_for_gitlab_alert || embed_url_for_self_managed_alert
"\n[](#{url})" if url
end
def embed_url_for_gitlab_alert
return unless gitlab_alert

View File

@ -64,7 +64,7 @@ module Admin
def create_integration_for_projects_without_integration
loop do
batch = Project.uncached { project_ids_without_integration }
batch = Project.uncached { Project.ids_without_integration(integration, BATCH_SIZE) }
bulk_create_from_integration(batch) unless batch.empty?
@ -114,22 +114,6 @@ module Admin
integration.type == 'ExternalWikiService'
end
# rubocop: disable CodeReuse/ActiveRecord
def project_ids_without_integration
services = Service
.select('1')
.where('services.project_id = projects.id')
.where(type: integration.type)
Project
.where('NOT EXISTS (?)', services)
.where(pending_delete: false)
.where(archived: false)
.limit(BATCH_SIZE)
.pluck(:id)
end
# rubocop: enable CodeReuse/ActiveRecord
def service_hash
@service_hash ||= integration.to_service_hash
.tap { |json| json['inherit_from_id'] = integration.id }

View File

@ -17,7 +17,7 @@ module AlertManagement
return error_no_permissions unless allowed?
return error_issue_already_exists if alert.issue
result = create_issue(user, alert_payload)
result = create_issue
@issue = result.payload[:issue]
return error(result.message) if result.error?
@ -36,7 +36,7 @@ module AlertManagement
user.can?(:create_issue, project)
end
def create_issue(user, alert_payload)
def create_issue
issue = do_create_issue(label_ids: issue_label_ids)
# Create an unlabelled issue if we couldn't create the issue
@ -53,14 +53,6 @@ module AlertManagement
success
end
def alert_payload
if alert.prometheus?
alert.payload
else
Gitlab::Alerting::NotificationPayloadParser.call(alert.payload.to_h)
end
end
def update_alert_issue_id
alert.update(issue_id: issue.id)
end
@ -85,24 +77,16 @@ module AlertManagement
Issues::CreateService.new(
project,
user,
title: issue_title,
description: issue_description,
title: alert_presenter.title,
description: alert_presenter.issue_description,
**params
).execute
end
def issue_title
alert_presenter.full_title
end
def issue_description
horizontal_line = "\n\n---\n\n"
[
alert_summary,
alert_markdown,
issue_template_content
].compact.join(horizontal_line)
def alert_presenter
strong_memoize(:alert_presenter) do
alert.present
end
end
def issue_label_ids
@ -117,31 +101,6 @@ module AlertManagement
.execute
end
def alert_summary
alert_presenter.issue_summary_markdown
end
def alert_markdown
alert_presenter.alert_markdown
end
def alert_presenter
strong_memoize(:alert_presenter) do
Gitlab::Alerting::Alert.new(project: project, payload: alert_payload).present
end
end
def issue_template_content
incident_management_setting.issue_template_content
end
def incident_management_setting
strong_memoize(:incident_management_setting) do
project.incident_management_setting ||
project.build_incident_management_setting
end
end
def issue_errors(issue)
issue.errors.full_messages.to_sentence
end

View File

@ -26,7 +26,7 @@ module Projects
def propagate_projects_with_template
loop do
batch = Project.uncached { project_ids_without_integration }
batch = Project.uncached { Project.ids_without_integration(template, BATCH_SIZE) }
bulk_create_from_template(batch) unless batch.empty?
@ -50,22 +50,6 @@ module Projects
end
end
# rubocop: disable CodeReuse/ActiveRecord
def project_ids_without_integration
services = Service
.select('1')
.where('services.project_id = projects.id')
.where(type: template.type)
Project
.where('NOT EXISTS (?)', services)
.where(pending_delete: false)
.where(archived: false)
.limit(BATCH_SIZE)
.pluck(:id)
end
# rubocop: enable CodeReuse/ActiveRecord
def bulk_insert(klass, columns, values_array)
items_to_insert = values_array.map { |array| Hash[columns.zip(array)] }

View File

@ -0,0 +1,5 @@
---
title: Improve pipeline index controller performance by resolving Gitaly N+1 calls
merge_request: 34160
author:
type: performance

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class AddAuthorIdIndexToAuditEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
INDEX_NAME = 'index_audit_events_on_entity_id_entity_type_id_desc_author_id'
OLD_INDEX_NAME = 'index_audit_events_on_entity_id_and_entity_type_and_id_desc'
def up
add_concurrent_index(:audit_events, [:entity_id, :entity_type, :id, :author_id], order: { id: :desc }, name: INDEX_NAME)
remove_concurrent_index_by_name(:audit_events, OLD_INDEX_NAME)
end
def down
add_concurrent_index(:audit_events, [:entity_id, :entity_type, :id], order: { id: :desc }, name: OLD_INDEX_NAME)
remove_concurrent_index_by_name(:audit_events, INDEX_NAME)
end
end

View File

@ -9419,7 +9419,7 @@ CREATE INDEX index_approvers_on_target_id_and_target_type ON public.approvers US
CREATE INDEX index_approvers_on_user_id ON public.approvers USING btree (user_id);
CREATE INDEX index_audit_events_on_entity_id_and_entity_type_and_id_desc ON public.audit_events USING btree (entity_id, entity_type, id DESC);
CREATE INDEX index_audit_events_on_entity_id_entity_type_id_desc_author_id ON public.audit_events USING btree (entity_id, entity_type, id DESC, author_id);
CREATE INDEX index_audit_events_on_ruby_object_in_details ON public.audit_events USING btree (id) WHERE (details ~~ '%ruby/object%'::text);
@ -14108,6 +14108,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200615123055
20200615193524
20200615232735
20200616145031
20200617000757
20200617001001
20200617001118

View File

@ -133,34 +133,34 @@ replicating data from those features will cause the data to be **lost**.
If you wish to use those features on a **secondary** node, or to execute a failover
successfully, you must replicate their data using some other means.
| Feature | Replicated | Verified | Notes |
| Feature | Replicated (added in GitLab version) | Verified (added in GitLab version) | Notes |
|:---------------------------------------------------------------------|:---------------------------------------------------------|:--------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------|
| Application data in PostgreSQL | **Yes** | **Yes** | |
| Project repository | **Yes** | **Yes** | |
| Project wiki repository | **Yes** | **Yes** | |
| Project designs repository | **Yes** | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/32467) | |
| Uploads | **Yes** | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Verified only on transfer, or manually (*1*) |
| LFS objects | **Yes** | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8922) | Verified only on transfer, or manually (*1*). Unavailable for new LFS objects in 11.11.x and 12.0.x (*2*). |
| CI job artifacts (other than traces) | **Yes** | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only manually (*1*) |
| Archived traces | **Yes** | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only on transfer, or manually (*1*) |
| Personal snippets | **Yes** | **Yes** | |
| Application data in PostgreSQL | **Yes** (10.2) | **Yes** (10.2) | |
| Project repository | **Yes** (10.2) | **Yes** (10.7) | |
| Project wiki repository | **Yes** (10.2) | **Yes** (10.7) | |
| Project designs repository | **Yes** (12.7) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/32467) | |
| Uploads | **Yes** (10.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | Verified only on transfer, or manually (*1*) |
| LFS objects | **Yes** (10.2) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8922) | Verified only on transfer, or manually (*1*). Unavailable for new LFS objects in 11.11.x and 12.0.x (*2*). |
| CI job artifacts (other than traces) | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only manually (*1*) |
| Archived traces | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Verified only on transfer, or manually (*1*) |
| Personal snippets | **Yes** (10.2) | **Yes** (10.2) | |
| [Versioned snippets](../../../user/snippets.md#versioned-snippets) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2810) | |
| Project snippets | **Yes** | **Yes** | |
| Object pools for forked project deduplication | **Yes** | No | |
| Project snippets | **Yes** (10.2) | **Yes** (10.2) | |
| Object pools for forked project deduplication | **Yes** | No | |
| [Server-side Git Hooks](../../custom_hooks.md) | No | No | |
| [Elasticsearch integration](../../../integration/elasticsearch.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/1186) | No | |
| [GitLab Pages](../../pages/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/589) | No | |
| [Container Registry](../../packages/container_registry.md) | **Yes** | No | |
| [NPM Registry](../../../user/packages/npm_registry/index.md) | **Yes** | No | |
| [Maven Repository](../../../user/packages/maven_repository/index.md) | **Yes** | No | |
| [Conan Repository](../../../user/packages/conan_repository/index.md) | **Yes** | No | |
| [NuGet Repository](../../../user/packages/nuget_repository/index.md) | **Yes** | No | |
| [PyPi Repository](../../../user/packages/pypi_repository/index.md) | **Yes** | No | |
| [Composer Repository](../../../user/packages/composer_repository/index.md) | **Yes** | No | |
| [Container Registry](../../packages/container_registry.md) | **Yes** (12.3) | No | |
| [NPM Registry](../../../user/packages/npm_registry/index.md) | **Yes** (13.2) | No | |
| [Maven Repository](../../../user/packages/maven_repository/index.md) | **Yes** (13.2) | No | |
| [Conan Repository](../../../user/packages/conan_repository/index.md) | **Yes** (13.2) | No | |
| [NuGet Repository](../../../user/packages/nuget_repository/index.md) | **Yes** (13.2) | No | |
| [PyPi Repository](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | No | |
| [Composer Repository](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | No | |
| [External merge request diffs](../../merge_request_diffs.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/33817) | No | |
| [Terraform State](../../terraform_state.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/3112)(*3*) | No | |
| [Vulnerability Export](../../../user/application_security/security_dashboard/#export-vulnerabilities-1) | [No](https://gitlab.com/groups/gitlab-org/-/epics/3111)(*3*) | No | | |
| Content in object storage | **Yes** | No | |
| Content in object storage | **Yes** (12.4) | No | |
- (*1*): The integrity can be verified manually using
[Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing

View File

@ -387,31 +387,9 @@ Gitaly makes the following assumptions:
clients, and that Gitaly server can read and write to `/mnt/gitlab/storage2`.
- Your `gitaly1.internal` and `gitaly2.internal` Gitaly servers can reach each other.
Note you can't a use mixed setup, with at least one of your Gitaly servers configured as a local
server with the `path` setting provided. This is because other Gitaly instances can't communicate
with it. The following setup is _incorrect_, because:
- You must replace `path` with `gitaly_address` containing a proper value.
- The address must be reachable from the other two addresses provided.
```ruby
git_data_dirs({
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage1' => { 'path' => '/var/opt/gitlab/git-data' },
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
})
```
You should use configuration similar to the following:
```ruby
git_data_dirs({
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
# this should be the IP address of the GitLab server that has Gitaly running on it
'storage1' => { 'gitaly_address' => 'tcp://gitlab.internal:8075' },
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
})
```
You can't define Gitaly servers with some as a local directory (with `path`) and some as remote
server (with `gitaly_address`). However, local and remote Gitaly services can be used together. See
[mixed configuration](#mixed-configuration) for more information.
**For Omnibus GitLab**
@ -475,6 +453,34 @@ If you have [server hooks](../server_hooks.md) configured, either per repository
must move these to the Gitaly servers. If you have multiple Gitaly servers, copy your server hooks
to all Gitaly servers.
#### Mixed configuration
GitLab can reside on the same server as one of many Gitaly servers, but doesn't support
configuration that mixes local and remote configuration. The following setup is incorrect, because:
- `path` is invalid for some of the Gitaly servers.
- All addresses must be reachable from the other Gitaly servers.
```ruby
git_data_dirs({
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage1' => { 'path' => '/var/opt/gitlab/git-data' },
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
})
```
To combine local and remote Gitaly servers, use an external address for the local Gitaly server. For
example:
```ruby
git_data_dirs({
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
# Address of the GitLab server that has Gitaly running on it
'storage1' => { 'gitaly_address' => 'tcp://gitlab.internal:8075' },
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
})
```
### Disable Gitaly where not required (optional)
If you are running Gitaly [as a remote service](#run-gitaly-on-its-own-server) you may want to

View File

@ -30,9 +30,6 @@ or higher, are required for your CPU or node counts. For more information, see
our [Sysbench](https://github.com/akopytov/sysbench)-based
[CPU benchmark](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
AWS-equivalent and Azure-equivalent configurations are rough suggestions that
may change in the future, and haven't been tested or validated.
Due to better performance and availability, for data objects (such as LFS,
uploads, or artifacts), using an [object storage service](#configure-the-object-storage)
is recommended instead of using NFS. Using an object storage service also

View File

@ -146,3 +146,20 @@ export const fetchFoos = ({ state }) => {
return axios.get(state.settings.fooPath);
};
```
### 7. How can I test the production build locally?
Sometimes it's necessary to test locally what the frontend production build would produce, to do so the steps are:
1. Stop webpack: `gdk stop webpack`.
1. Open `gitlab.yaml` located in your `gitlab` installation folder, scroll down to the `webpack` section and change `dev_server` to `enabled: false`.
1. Run `yarn webpack-prod && gdk restart rails-web`.
The production build takes a few minutes to be completed; any code change at this point will be
displayed only after executing the item 3 above again.
To return to the normal development mode:
1. Open `gitlab.yaml` located in your `gitlab` installation folder, scroll down to the `webpack` section and change back `dev_server` to `enabled: true`.
1. Run `yarn clean` to remove the production assets and free some space (optional).
1. Start webpack again: `gdk start webpack`.
1. Restart GDK: `gdk-restart rails-web`.

View File

@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab Composer Repository **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15886) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.1.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15886) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
With the GitLab Composer Repository, every project can have its own space to store [Composer](https://getcomposer.org/) packages.

View File

@ -22,7 +22,7 @@ The Packages feature allows GitLab to act as a repository for the following:
| [NuGet Repository](nuget_repository/index.md) **(PREMIUM)** | The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ |
| [PyPi Repository](pypi_repository/index.md) **(PREMIUM)** | The GitLab PyPi Repository will enable every project in GitLab to have its own space to store [PyPi](https://pypi.org/) packages. | 12.10+ |
| [Go Proxy](go_proxy/index.md) **(PREMIUM)** | The Go proxy for GitLab enables every project in GitLab to be fetched with the [Go proxy protocol](https://proxy.golang.org/). | 13.1+ |
| [Composer Repository](composer_repository/index.md) **(PREMIUM)** | The GitLab Composer Repository will enable every project in GitLab to have its own space to store [Composer](https://getcomposer.org/) packages. | 13.1+ |
| [Composer Repository](composer_repository/index.md) **(PREMIUM)** | The GitLab Composer Repository will enable every project in GitLab to have its own space to store [Composer](https://getcomposer.org/) packages. | 13.2+ |
## Enable the Package Registry for your project

View File

@ -35,18 +35,16 @@ To create and enable a feature flag:
1. Navigate to your project's **Operations > Feature Flags**.
1. Click the **New feature flag** button.
1. Fill in the details:
- Enter a name that starts with a letter and contains only lowercase letters, digits, underscores (`_`),
or dashes (`-`), and does not end with a dash (`-`) or underscore (`_`).
- Enter a description (optional, 255 characters max).
1. Enter a name that starts with a letter and contains only lowercase letters, digits, underscores (`_`),
or dashes (`-`), and does not end with a dash (`-`) or underscore (`_`).
1. Enter a description (optional, 255 characters max).
1. Enter details about how the flag should be applied:
- In GitLab 13.0 and earlier, add **Environment specs**. For each environment,
include the:
- Status (default enabled)
- [Rollout strategy](#rollout-strategy-legacy) (defaults to all users)
- In GitLab 13.1 and later, add Feature Flag [**Strategies**](#feature-flag-strategies). For
each strategy, include the:
- Type (defaults to [All users](#all-users))
- Environment (defaults to all environments)
include the **Status** (default enabled) and [**Rollout strategy**](#rollout-strategy-legacy)
(defaults to **All users**).
- In GitLab 13.1 and later, add Feature Flag [**Strategies**](#feature-flag-strategies).
For each strategy, include the **Type** (defaults to [**All users**](#all-users))
and **Environments** (defaults to all environments).
1. Click **Create feature flag**.
You can change these settings by clicking the **{pencil}** (edit) button

View File

@ -1,49 +1,5 @@
---
type: reference, howto
stage: Release
group: Release Management
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
redirect_to: 'pages_ci_cd_template.md'
---
# Create a Pages website by using a CI/CD template
GitLab provides `.gitlab-ci.yml` templates for the most popular Static Site Generators (SSGs).
You can create your own `.gitlab-ci.yml` file from one of these templates, and run
the CI/CD pipeline to generate a Pages website.
Use a `.gitlab-ci.yml` template when you have an existing project that you want to add a Pages site to.
Your GitLab repository should contain files specific to an SSG, or plain HTML.
After you complete these steps, you may need to do additional
configuration for the Pages site to generate properly.
1. In the left sidebar, click **Project overview**.
1. Click **Set up CI/CD**.
![setup GitLab CI/CD](../img/setup_ci_v13_1.png)
If this button is not available, CI/CD is already configured for
your project. You may want to browse the `.gitlab-ci.yml` files
[in these projects instead](https://gitlab.com/pages).
1. From the **Apply a template** list, choose a template for the SSG you're using.
You can also choose plain HTML.
![gitlab-ci templates](../img/choose_ci_template_v13_1.png)
If you don't find a corresponding template, you can view the
[GitLab Pages group of sample projects](https://gitlab.com/pages).
These projects contain `.gitlab-ci.yml` files that you can modify for your needs.
You can also [learn how to write your own `.gitlab-ci.yml`
file for GitLab Pages](../getting_started_part_four.md).
1. Save and commit the `.gitlab-ci.yml` file.
If everything is configured correctly, the site can take approximately 30 minutes to deploy.
You can watch the pipeline run by going to **CI / CD > Pipelines**.
When the pipeline is finished, go to **Settings > Pages** to find the link to
your Pages website.
For every change pushed to your repository, GitLab CI/CD runs a new pipeline
that immediately publishes your changes to the Pages site.
This document was moved to [pages_ci_cd_template.md](pages_ci_cd_template.md).

View File

@ -0,0 +1,49 @@
---
type: reference, howto
stage: Release
group: Release Management
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
---
# Create a Pages website by using a CI/CD template
GitLab provides `.gitlab-ci.yml` templates for the most popular Static Site Generators (SSGs).
You can create your own `.gitlab-ci.yml` file from one of these templates, and run
the CI/CD pipeline to generate a Pages website.
Use a `.gitlab-ci.yml` template when you have an existing project that you want to add a Pages site to.
Your GitLab repository should contain files specific to an SSG, or plain HTML.
After you complete these steps, you may need to do additional
configuration for the Pages site to generate properly.
1. In the left sidebar, click **Project overview**.
1. Click **Set up CI/CD**.
![setup GitLab CI/CD](../img/setup_ci_v13_1.png)
If this button is not available, CI/CD is already configured for
your project. You may want to browse the `.gitlab-ci.yml` files
[in these projects instead](https://gitlab.com/pages).
1. From the **Apply a template** list, choose a template for the SSG you're using.
You can also choose plain HTML.
![gitlab-ci templates](../img/choose_ci_template_v13_1.png)
If you don't find a corresponding template, you can view the
[GitLab Pages group of sample projects](https://gitlab.com/pages).
These projects contain `.gitlab-ci.yml` files that you can modify for your needs.
You can also [learn how to write your own `.gitlab-ci.yml`
file for GitLab Pages](../getting_started_part_four.md).
1. Save and commit the `.gitlab-ci.yml` file.
If everything is configured correctly, the site can take approximately 30 minutes to deploy.
You can watch the pipeline run by going to **CI / CD > Pipelines**.
When the pipeline is finished, go to **Settings > Pages** to find the link to
your Pages website.
For every change pushed to your repository, GitLab CI/CD runs a new pipeline
that immediately publishes your changes to the Pages site.

View File

@ -48,7 +48,7 @@ To create a GitLab Pages website:
| -------- | ----------- |
| [Fork a sample project](getting_started/fork_sample_project.md) | Create a new project with Pages already configured by forking a sample project. |
| [Use a new project template](getting_started/pages_new_project_template.md) | Create a new project with Pages already configured by using a new project template. |
| [Use a `.gitlab-ci.yml` template](getting_started/new_or_existing_website.md) | Add a Pages site to an existing project. Use a pre-populated CI template file. |
| [Use a `.gitlab-ci.yml` template](getting_started/pages_ci_cd_template.md) | Add a Pages site to an existing project. Use a pre-populated CI template file. |
| [Create a `gitlab-ci.yml` file from scratch](getting_started_part_four.md) | Add a Pages site to an existing project. Learn how to create and configure your own CI file. |
To update a GitLab Pages website:

View File

@ -38,6 +38,10 @@ module Gitlab
::Feature.enabled?(:ci_atomic_processing, project, default_enabled: true)
end
def self.pipeline_latest?
::Feature.enabled?(:ci_pipeline_latest, default_enabled: true)
end
def self.release_generation_enabled?
::Feature.enabled?(:ci_release_generation)
end

View File

@ -17,6 +17,7 @@ module Gitlab
pipelines.each do |pipeline|
self.new(pipeline).tap do |preloader|
preloader.preload_commit_authors
preloader.preload_ref_commits
preloader.preload_pipeline_warnings
preloader.preload_stages_warnings
end
@ -27,12 +28,19 @@ module Gitlab
@pipeline = pipeline
end
# This also preloads the author of every commit. We're using "lazy_author"
# here since "author" immediately loads the data on the first call.
def preload_commit_authors
# This also preloads the author of every commit. We're using "lazy_author"
# here since "author" immediately loads the data on the first call.
@pipeline.commit.try(:lazy_author)
end
# This preloads latest commits for given refs and therefore makes it
# much less expensive to check if a pipeline is a latest one for
# given branch.
def preload_ref_commits
@pipeline.lazy_ref_commit
end
def preload_pipeline_warnings
# This preloads the number of warnings for every pipeline, ensuring
# that Ci::Pipeline#has_warnings? doesn't execute any additional
@ -40,10 +48,10 @@ module Gitlab
@pipeline.number_of_warnings
end
# This preloads the number of warnings for every stage, ensuring
# that Ci::Stage#has_warnings? doesn't execute any additional
# queries.
def preload_stages_warnings
# This preloads the number of warnings for every stage, ensuring
# that Ci::Stage#has_warnings? doesn't execute any additional
# queries.
@pipeline.stages.each { |stage| stage.number_of_warnings }
end
end

View File

@ -1008,6 +1008,12 @@ module Gitlab
end
end
def list_commits_by_ref_name(refs)
wrapped_gitaly_errors do
gitaly_commit_client.list_commits_by_ref_name(refs)
end
end
def last_commit_for_path(sha, path, literal_pathspec: false)
wrapped_gitaly_errors do
gitaly_commit_client.last_commit_for_path(sha, path, literal_pathspec: literal_pathspec)

View File

@ -391,6 +391,21 @@ module Gitlab
messages
end
def list_commits_by_ref_name(refs)
request = Gitaly::ListCommitsByRefNameRequest
.new(repository: @gitaly_repo, ref_names: refs)
response = GitalyClient.call(@repository.storage, :commit_service, :list_commits_by_ref_name, request, timeout: GitalyClient.medium_timeout)
commit_refs = response.flat_map do |message|
message.commit_refs.map do |commit_ref|
[commit_ref.ref_name, Gitlab::Git::Commit.new(@repository, commit_ref.commit)]
end
end
Hash[commit_refs]
end
private
def call_commit_diff(request_params, options = {})

View File

@ -77,9 +77,9 @@ RSpec.describe Projects::PipelinesController do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
# ListCommitsByOid, RepositoryExists, HasLocalBranches
# ListCommitsByOid, RepositoryExists, HasLocalBranches, ListCommitsByRefNames
expect { get_pipelines_index_json }
.to change { Gitlab::GitalyClient.get_request_count }.by(3)
.to change { Gitlab::GitalyClient.get_request_count }.by(4)
end
end
@ -111,13 +111,15 @@ RSpec.describe Projects::PipelinesController do
context 'scope is branches or tags' do
before do
create(:ci_pipeline, :failed, project: project, ref: 'v1.0.0', tag: true)
create(:ci_pipeline, :failed, project: project, ref: 'master', tag: false)
create(:ci_pipeline, :failed, project: project, ref: 'feature', tag: false)
end
context 'when scope is branches' do
it 'returns matched pipelines' do
get_pipelines_index_json(scope: 'branches')
check_pipeline_response(returned: 1, all: 7, running: 2, pending: 1, finished: 4)
check_pipeline_response(returned: 2, all: 9, running: 2, pending: 1, finished: 6)
end
end
@ -125,7 +127,7 @@ RSpec.describe Projects::PipelinesController do
it 'returns matched pipelines' do
get_pipelines_index_json(scope: 'tags')
check_pipeline_response(returned: 1, all: 7, running: 2, pending: 1, finished: 4)
check_pipeline_response(returned: 1, all: 9, running: 2, pending: 1, finished: 6)
end
end
end
@ -234,7 +236,8 @@ RSpec.describe Projects::PipelinesController do
user = create(:user)
pipeline = create(:ci_empty_pipeline, status: status,
project: project,
sha: sha,
sha: sha.id,
ref: sha.id.first(8),
user: user,
merge_request: merge_request)

View File

@ -6,10 +6,11 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
include MetricsDashboardHelpers
let(:user) { create(:user) }
let(:project) { project_with_dashboard('.gitlab/dashboards/test.yml') }
let(:environment) { create(:environment, project: project) }
let(:params) { { environment: environment } }
let_it_be(:user) { create(:user) }
let_it_be(:namespace) { create(:namespace, name: 'monitoring' )}
let_it_be(:project) { project_with_dashboard_namespace('.gitlab/dashboards/test.yml', namespace: namespace) }
let_it_be(:environment) { create(:environment, id: 1, project: project) }
let_it_be(:params) { { environment: environment } }
before(:all) do
clean_frontend_fixtures('metrics_dashboard/')
@ -24,6 +25,7 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
project.add_maintainer(user)
allow(controller).to receive(:project).and_return(project)
allow(controller).to receive(:environment).and_return(environment)
allow(controller)
.to receive(:metrics_dashboard_params)
.and_return(params)
@ -35,7 +37,9 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
it 'metrics_dashboard/environment_metrics_dashboard.json' do
routes.draw { get "metrics_dashboard" => "anonymous#metrics_dashboard" }
response = get :metrics_dashboard, format: :json
expect(response).to be_successful
end
end

View File

@ -3,7 +3,19 @@
exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="prometheus-graphs"
currentenvironmentname="environment1"
customdashboardbasepath=".gitlab/dashboards"
dashboardendpoint="/monitoring/monitor-project/-/environments/1/metrics_dashboard.json"
dashboardsendpoint="/monitoring/monitor-project/-/performance_monitoring/dashboards.json"
dashboardtimezone="LOCAL"
data-qa-selector="prometheus_graphs"
deploymentsendpoint="/monitoring/monitor-project/-/environments/1/deployments.json"
environmentstate="available"
logspath="/monitoring/monitor-project/-/logs?environment_name=environment1"
metricsdashboardbasepath="/monitoring/monitor-project/-/environments/1/metrics"
metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json"
projectpath="/monitoring/monitor-project"
prometheusstatus=""
>
<div
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
@ -135,15 +147,15 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<!---->
<empty-state-stub
clusterspath="/path/to/clusters"
documentationpath="/path/to/docs"
emptygettingstartedsvgpath="/path/to/getting-started.svg"
emptyloadingsvgpath="/path/to/loading.svg"
emptynodatasmallsvgpath="/path/to/no-data-small.svg"
emptynodatasvgpath="/path/to/no-data.svg"
emptyunabletoconnectsvgpath="/path/to/unable-to-connect.svg"
clusterspath="/monitoring/monitor-project/-/clusters"
documentationpath="/help/administration/monitoring/prometheus/index.md"
emptygettingstartedsvgpath="/images/illustrations/monitoring/getting_started.svg"
emptyloadingsvgpath="/images/illustrations/monitoring/loading.svg"
emptynodatasmallsvgpath="/images/illustrations/chart-empty-state-small.svg"
emptynodatasvgpath="/images/illustrations/monitoring/no_data.svg"
emptyunabletoconnectsvgpath="/images/illustrations/monitoring/unable_to_connect.svg"
selectedstate="gettingStarted"
settingspath="/path/to/settings"
settingspath="/monitoring/monitor-project/-/services/prometheus/edit"
/>
</div>
`;

View File

@ -18,8 +18,8 @@ import {
singleStatMetricsResult,
graphDataPrometheusQueryRangeMultiTrack,
barMockData,
propsData,
} from '../mock_data';
import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
import { panelTypes } from '~/monitoring/constants';
@ -32,7 +32,6 @@ import MonitorColumnChart from '~/monitoring/components/charts/column.vue';
import MonitorBarChart from '~/monitoring/components/charts/bar.vue';
import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
import { graphData, graphDataEmpty } from '../fixture_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
@ -63,7 +62,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
settingsPath: propsData.settingsPath,
settingsPath: dashboardProps.settingsPath,
...props,
},
store,
@ -316,7 +315,7 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath);
expect(findEditCustomMetricLink().attributes('href')).toBe(dashboardProps.settingsPath);
});
});
});
@ -433,7 +432,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
settingsPath: propsData.settingsPath,
settingsPath: dashboardProps.settingsPath,
graphData: {
y_label: 'metric',
...graphData,
@ -483,7 +482,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
settingsPath: propsData.settingsPath,
settingsPath: dashboardProps.settingsPath,
namespace: mockNamespace,
},
store,

View File

@ -27,8 +27,12 @@ import {
setupStoreWithVariable,
setupStoreWithLinks,
} from '../store_utils';
import { environmentData, dashboardGitResponse, propsData } from '../mock_data';
import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data';
import { environmentData, dashboardGitResponse } from '../mock_data';
import {
metricsDashboardViewModel,
metricsDashboardPanelCount,
dashboardProps,
} from '../fixture_data';
import createFlash from '~/flash';
jest.mock('~/flash');
@ -48,7 +52,7 @@ describe('Dashboard', () => {
const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(Dashboard, {
propsData: { ...propsData, ...props },
propsData: { ...dashboardProps, ...props },
store,
stubs: {
DashboardHeader,
@ -59,7 +63,7 @@ describe('Dashboard', () => {
const createMountedWrapper = (props = {}, options = {}) => {
wrapper = mount(Dashboard, {
propsData: { ...propsData, ...props },
propsData: { ...dashboardProps, ...props },
store,
stubs: {
'graph-group': true,

View File

@ -5,7 +5,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
import { createStore } from '~/monitoring/stores';
import { setupAllDashboards } from '../store_utils';
import { propsData } from '../mock_data';
import { dashboardProps } from '../fixture_data';
jest.mock('~/lib/utils/url_utility');
@ -29,7 +29,7 @@ describe('Dashboard template', () => {
it('matches the default snapshot', () => {
wrapper = shallowMount(Dashboard, {
propsData: { ...propsData },
propsData: { ...dashboardProps },
store,
stubs: {
DashboardHeader,

View File

@ -9,7 +9,8 @@ import {
updateHistory,
} from '~/lib/utils/url_utility';
import axios from '~/lib/utils/axios_utils';
import { mockProjectDir, propsData } from '../mock_data';
import { mockProjectDir } from '../mock_data';
import { dashboardProps } from '../fixture_data';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
@ -26,7 +27,7 @@ describe('dashboard invalid url parameters', () => {
const createMountedWrapper = (props = { hasMetrics: true }, options = {}) => {
wrapper = mount(Dashboard, {
propsData: { ...propsData, ...props },
propsData: { ...dashboardProps, ...props },
store,
stubs: { 'graph-group': true, 'dashboard-panel': true, 'dashboard-header': DashboardHeader },
...options,

View File

@ -1,5 +1,7 @@
import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
import { metricStates } from '~/monitoring/constants';
import { parseBoolean, convertObjectProps } from '~/lib/utils/common_utils';
import { convertToCamelCase } from '~/lib/utils/text_utility';
import { metricsResult } from './mock_data';
@ -8,6 +10,20 @@ export const metricsDashboardResponse = getJSONFixture(
'metrics_dashboard/environment_metrics_dashboard.json',
);
export const metricsDashboardPayload = metricsDashboardResponse.dashboard;
const metricsData = convertObjectProps(
// Some props use kebab-case, convert to snake_case first
key => convertToCamelCase(key.replace(/-/g, '_')),
metricsDashboardResponse.metrics_data,
);
export const dashboardProps = {
...metricsData,
hasMetrics: parseBoolean(metricsData.hasMetrics),
customMetricsAvailable: parseBoolean(metricsData.customMetricsAvailable),
prometheusAlertsAvailable: parseBoolean(metricsData.prometheusAlertsAvailable),
};
export const metricsDashboardViewModel = mapToDashboardViewModel(metricsDashboardPayload);
export const metricsDashboardPanelCount = 22;

View File

@ -11,8 +11,8 @@ import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
import { mockApiEndpoint, propsData } from '../mock_data';
import { metricsDashboardPayload } from '../fixture_data';
import { mockApiEndpoint } from '../mock_data';
import { metricsDashboardPayload, dashboardProps } from '../fixture_data';
import { setupStoreWithData } from '../store_utils';
const localVue = createLocalVue();
@ -56,7 +56,7 @@ describe('Dashboard', () => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
...dashboardProps,
hasMetrics: true,
showPanels: true,
},

View File

@ -28,8 +28,9 @@ describe Gitlab::Ci::Pipeline::Preloader do
end
end
it 'preloads commit authors and number of warnings' do
it 'preloads commit authors, number of warnings and ref commits' do
expect(commit).to receive(:lazy_author)
expect(pipeline).to receive(:lazy_ref_commit)
expect(pipeline).to receive(:number_of_warnings)
expect(stage).to receive(:number_of_warnings)
@ -38,6 +39,7 @@ describe Gitlab::Ci::Pipeline::Preloader do
it 'returns original collection' do
allow(commit).to receive(:lazy_author)
allow(pipeline).to receive(:lazy_ref_commit)
allow(pipeline).to receive(:number_of_warnings)
allow(stage).to receive(:number_of_warnings)

View File

@ -381,4 +381,15 @@ describe Gitlab::GitalyClient::CommitService do
commits.map { |commit| Gitlab::Git::Commit.new(repository, commit) }
end
end
describe '#list_commits_by_ref_name' do
it 'lists latest commits grouped by a ref name' do
response = client.list_commits_by_ref_name(%w[master feature v1.0.0 nonexistent])
expect(response.fetch('master').id).to eq 'b83d6e391c22777fca1ed3012fce84f633d7fed0'
expect(response.fetch('feature').id).to eq '0b4bc9a49b562e85de7cc9e834518ea6828729b9'
expect(response.fetch('v1.0.0').id).to eq '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
expect(response).not_to have_key 'nonexistent'
end
end
end

View File

@ -337,4 +337,22 @@ describe AlertManagement::Alert do
expect { subject }.to change { alert.events }.by(1)
end
end
describe '#present' do
context 'when alert is generic' do
let(:alert) { build(:alert_management_alert) }
it 'uses generic alert presenter' do
expect(alert.present).to be_kind_of(AlertManagement::AlertPresenter)
end
end
context 'when alert is Prometheus specific' do
let(:alert) { build(:alert_management_alert, :prometheus) }
it 'uses Prometheus Alert presenter' do
expect(alert.present).to be_kind_of(AlertManagement::PrometheusAlertPresenter)
end
end
end
end

View File

@ -1488,6 +1488,12 @@ describe Ci::Pipeline, :mailer do
sha: project.commit.sha)
end
describe '#lazy_ref_commit' do
it 'returns the latest commit for a ref lazily' do
expect(pipeline.lazy_ref_commit.id).to eq project.commit(pipeline.ref).id
end
end
describe '#latest?' do
context 'with latest sha' do
it 'returns true' do
@ -1496,17 +1502,26 @@ describe Ci::Pipeline, :mailer do
end
context 'with a branch name as the ref' do
it 'looks up commit with the full ref name' do
expect(pipeline.project).to receive(:commit).with('refs/heads/master').and_call_original
it 'looks up a commit for a branch' do
expect(pipeline.ref).to eq 'master'
expect(pipeline).to be_latest
end
end
context 'with a tag name as a ref' do
it 'looks up a commit for a tag' do
expect(project.repository.branch_names).not_to include 'v1.0.0'
pipeline.update(sha: project.commit('v1.0.0').sha, ref: 'v1.0.0', tag: true)
expect(pipeline).to be_tag
expect(pipeline).to be_latest
end
end
context 'with not latest sha' do
before do
pipeline.update(
sha: project.commit("#{project.default_branch}~1").sha)
pipeline.update(sha: project.commit("#{project.default_branch}~1").sha)
end
it 'returns false' do

View File

@ -3,8 +3,9 @@
require 'spec_helper'
RSpec.describe Integration do
let(:project_1) { create(:project) }
let(:project_2) { create(:project) }
let!(:project_1) { create(:project) }
let!(:project_2) { create(:project) }
let!(:project_3) { create(:project) }
let(:instance_integration) { create(:jira_service, :instance) }
before do
@ -18,4 +19,10 @@ RSpec.describe Integration do
expect(Project.with_custom_integration_for(instance_integration)).to contain_exactly(project_2)
end
end
describe '#ids_without_integration' do
it 'returns projects ids without an integration' do
expect(Project.ids_without_integration(instance_integration, 100)).to contain_exactly(project_3.id)
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AlertManagement::AlertPresenter do
let_it_be(:project) { create(:project) }
let_it_be(:generic_payload) do
{
'title' => 'Alert title',
'start_time' => '2020-04-27T10:10:22.265949279Z',
'custom' => { 'param' => 73 }
}
end
let_it_be(:alert) do
create(:alert_management_alert, :with_host, :with_service, :with_monitoring_tool, project: project, payload: generic_payload)
end
subject(:presenter) { described_class.new(alert) }
describe '#issue_description' do
let(:markdown_line_break) { ' ' }
it 'returns an alert issue description' do
expect(presenter.issue_description).to eq(
<<~MARKDOWN.chomp
#### Summary
**Start time:** #{presenter.start_time}#{markdown_line_break}
**Severity:** #{presenter.severity}#{markdown_line_break}
**Service:** #{alert.service}#{markdown_line_break}
**Monitoring tool:** #{alert.monitoring_tool}#{markdown_line_break}
**Hosts:** #{alert.hosts.join(' ')}
#### Alert Details
**custom.param:** 73
MARKDOWN
)
end
end
end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AlertManagement::PrometheusAlertPresenter do
let_it_be(:project) { create(:project) }
let_it_be(:prometheus_payload) do
{
'annotations' => {
'title' => 'Alert title',
'gitlab_incident_markdown' => '**`markdown example`**',
'custom annotation' => 'custom annotation value'
},
'startsAt' => '2020-04-27T10:10:22.265949279Z',
'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
}
end
let_it_be(:alert) do
create(:alert_management_alert, :prometheus, project: project, payload: prometheus_payload)
end
subject(:presenter) { described_class.new(alert) }
describe '#issue_description' do
let(:markdown_line_break) { ' ' }
it 'returns an alert issue description' do
expect(presenter.issue_description).to eq(
<<~MARKDOWN.chomp
#### Summary
**Start time:** #{presenter.start_time}#{markdown_line_break}
**Severity:** #{presenter.severity}#{markdown_line_break}
**full_query:** `vector(1)`#{markdown_line_break}
**Monitoring tool:** Prometheus
#### Alert Details
**custom annotation:** custom annotation value
---
**`markdown example`**
MARKDOWN
)
end
end
end

View File

@ -8,10 +8,6 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
let_it_be(:project) { create(:project, group: group) }
let_it_be(:payload) do
{
'title' => 'Alert title',
'annotations' => {
'title' => 'Alert title'
},
'startsAt' => '2020-04-27T10:10:22.265949279Z',
'generatorURL' => 'http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1'
}
@ -19,6 +15,7 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
let_it_be(:generic_alert, reload: true) { create(:alert_management_alert, :triggered, project: project, payload: payload) }
let_it_be(:prometheus_alert, reload: true) { create(:alert_management_alert, :triggered, :prometheus, project: project, payload: payload) }
let(:alert) { generic_alert }
let(:alert_presenter) { alert.present }
let(:created_issue) { Issue.last! }
describe '#execute' do
@ -61,7 +58,7 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
end
it 'sets the issue title' do
expect(created_issue.title).to eq(alert_presenter.title)
expect(created_issue.title).to eq(alert.title)
end
it 'sets the issue description' do
@ -165,9 +162,6 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
context 'when the alert is prometheus alert' do
let(:alert) { prometheus_alert }
let(:alert_presenter) do
Gitlab::Alerting::Alert.new(project: project, payload: alert.payload).present
end
it_behaves_like 'creating an alert issue'
it_behaves_like 'setting an issue attributes'
@ -176,10 +170,6 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
context 'when the alert is generic' do
let(:alert) { generic_alert }
let(:alert_presenter) do
alert_payload = Gitlab::Alerting::NotificationPayloadParser.call(alert.payload.to_h)
Gitlab::Alerting::Alert.new(project: project, payload: alert_payload).present
end
it_behaves_like 'creating an alert issue'
it_behaves_like 'setting an issue attributes'
@ -187,11 +177,11 @@ RSpec.describe AlertManagement::CreateAlertIssueService do
end
context 'when issue cannot be created' do
let(:alert) { prometheus_alert }
let(:alert) { generic_alert }
before do
# set invalid payload for Prometheus alert
alert.update!(payload: {})
# Invalid alert
alert.update_columns(title: '')
end
it 'has an unsuccessful status' do

View File

@ -7,6 +7,12 @@ module MetricsDashboardHelpers
create(:project, :custom_repo, files: { dashboard_path => dashboard_yml })
end
def project_with_dashboard_namespace(dashboard_path, dashboard_yml = nil)
dashboard_yml ||= fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')
create(:project, :custom_repo, namespace: namespace, path: 'monitor-project', files: { dashboard_path => dashboard_yml })
end
def delete_project_dashboard(project, user, dashboard_path)
project.repository.delete_file(
user,