summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/dev-fixtures.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml33
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml77
-rw-r--r--app/controllers/admin/services_controller.rb2
-rw-r--r--app/controllers/concerns/import_url_params.rb8
-rw-r--r--app/mailers/emails/projects.rb13
-rw-r--r--app/services/notification_service.rb18
-rw-r--r--app/views/notify/prometheus_alert_fired_email.html.haml28
-rw-r--r--app/views/notify/prometheus_alert_fired_email.text.erb21
-rw-r--r--changelogs/unreleased/sh-fix-import-by-url-retries.yml5
-rw-r--r--db/migrate/20200318152134_adds_sha256_to_package_files.rb9
-rw-r--r--db/schema.rb3
-rw-r--r--doc/development/pipelines.md4
-rw-r--r--doc/user/admin_area/index.md15
-rw-r--r--doc/user/project/clusters/serverless/index.md4
-rw-r--r--scripts/utils.sh51
-rw-r--r--spec/controllers/concerns/import_url_params_spec.rb6
-rw-r--r--spec/mailers/emails/projects_spec.rb136
-rw-r--r--spec/services/notification_service_spec.rb35
20 files changed, 429 insertions, 47 deletions
diff --git a/.gitlab/ci/dev-fixtures.gitlab-ci.yml b/.gitlab/ci/dev-fixtures.gitlab-ci.yml
index e77a75d2822..0045691873f 100644
--- a/.gitlab/ci/dev-fixtures.gitlab-ci.yml
+++ b/.gitlab/ci/dev-fixtures.gitlab-ci.yml
@@ -11,7 +11,7 @@
SEED_CYCLE_ANALYTICS: "true"
SEED_PRODUCTIVITY_ANALYTICS: "true"
CYCLE_ANALYTICS_ISSUE_COUNT: 1
- SIZE: 0 # number of external projects to fork, requires network connection
+ SIZE: 0 # number of external projects to fork, requires network connection
# SEED_NESTED_GROUPS: "false" # requires network connection
run-dev-fixtures:
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index f381c423f5d..77ad938a0ef 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -157,9 +157,9 @@ dast:
extends:
- .default-retry
- .reports:rules:dast
- needs:
- - job: review-deploy
- artifacts: true
+ # This is needed so that manual jobs with needs don't block the pipeline.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
+ dependencies: ["review-deploy"]
stage: qa # GitLab-specific
image:
name: "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION"
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 14b1561ec1a..17a8bbbb21d 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -15,7 +15,7 @@
build-qa-image:
extends:
- .review-docker
- - .review:rules:mr-and-schedule
+ - .review:rules:mr-and-schedule-auto
stage: prepare
script:
- '[[ ! -d "ee/" ]] || export GITLAB_EDITION="ee"'
@@ -45,7 +45,7 @@ review-cleanup:
review-build-cng:
extends:
- .default-retry
- - .review:rules:mr-and-schedule
+ - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
image: ruby:2.6-alpine
stage: review-prepare
before_script:
@@ -57,6 +57,9 @@ review-build-cng:
artifacts: false
script:
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
+ # When the job is manual, review-deploy is also manual and we don't want people
+ # to have to manually start the jobs in sequence, so we do it for them.
+ - '[ -z $CI_JOB_MANUAL ] || play_job "review-deploy"'
.review-workflow-base:
extends:
@@ -76,11 +79,9 @@ review-build-cng:
review-deploy:
extends:
- .review-workflow-base
- - .review:rules:mr-and-schedule
+ - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
stage: review
- needs:
- - job: review-build-cng
- artifacts: false
+ dependencies: []
resource_group: "review/${CI_COMMIT_REF_NAME}"
allow_failure: true
before_script:
@@ -100,6 +101,10 @@ review-deploy:
- download_chart
- date
- deploy || (display_deployment_debug && exit 1)
+ # When the job is manual, review-qa-smoke is also manual and we don't want people
+ # to have to manually start the jobs in sequence, so we do it for them.
+ - '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
+ - '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
artifacts:
paths: [environment_url.txt]
expire_in: 2 days
@@ -140,9 +145,9 @@ review-stop:
.review-qa-base:
extends: .review-docker
stage: qa
- needs:
- - job: review-deploy
- artifacts: true
+ # This is needed so that manual jobs with needs don't block the pipeline.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
+ dependencies: ["review-deploy"]
allow_failure: true
variables:
QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
@@ -172,7 +177,7 @@ review-stop:
review-qa-smoke:
extends:
- .review-qa-base
- - .review:rules:mr-only-auto
+ - .review:rules:mr-only-auto-if-frontend-manual-otherwise
script:
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
@@ -189,11 +194,11 @@ review-qa-all:
review-performance:
extends:
- .review-docker
- - .review:rules:mr-and-schedule
+ - .review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise
stage: qa
- needs:
- - job: review-deploy
- artifacts: true
+ # This is needed so that manual jobs with needs don't block the pipeline.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
+ dependencies: ["review-deploy"]
allow_failure: true
before_script:
- export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 26138532f46..aae6f5a1e8e 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -57,6 +57,17 @@
- "doc/**/*"
- ".markdownlint.json"
+.frontend-dependency-patterns: &frontend-dependency-patterns
+ - "{package.json,yarn.lock}"
+
+.frontend-patterns: &frontend-patterns
+ - "{package.json,yarn.lock}"
+ - "{babel.config,jest.config}.js"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "vendor/assets/**/*"
+ - "{,ee/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*"
+
.backstage-patterns: &backstage-patterns
- "Dangerfile"
- "danger/**/*"
@@ -66,39 +77,38 @@
- "doc/README.md" # Some RSpec test rely on this file
.code-patterns: &code-patterns
+ - "{package.json,yarn.lock}"
+ - "{babel.config,jest.config}.js"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- - ".csscomb.json"
- - "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- - "{babel.config,jest.config}.js"
- "config.ru"
- - "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
-.frontend-dependency-patterns: &frontend-dependency-patterns
- - "{package.json,yarn.lock}"
-
.qa-patterns: &qa-patterns
- ".dockerignore"
- "qa/**/*"
.code-backstage-patterns: &code-backstage-patterns
+ - "{package.json,yarn.lock}"
+ - "{babel.config,jest.config}.js"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- - ".csscomb.json"
- - "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- - "{babel.config,jest.config}.js"
- "config.ru"
- - "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
# Backstage changes
@@ -110,17 +120,18 @@
- "doc/README.md" # Some RSpec test rely on this file
.code-qa-patterns: &code-qa-patterns
+ - "{package.json,yarn.lock}"
+ - "{babel.config,jest.config}.js"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- - ".csscomb.json"
- - "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- - "{babel.config,jest.config}.js"
- "config.ru"
- - "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
# QA changes
@@ -128,17 +139,18 @@
- "qa/**/*"
.code-backstage-qa-patterns: &code-backstage-qa-patterns
+ - "{package.json,yarn.lock}"
+ - "{babel.config,jest.config}.js"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- - ".csscomb.json"
- - "Dockerfile.assets"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
- - "{babel.config,jest.config}.js"
- "config.ru"
- - "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
# Backstage changes
@@ -417,7 +429,11 @@
- if: '$DAST_DISABLED || $GITLAB_FEATURES !~ /\bdast\b/'
when: never
- <<: *if-dot-com-gitlab-org-merge-request
+ changes: *frontend-patterns
+ when: on_success
+ - <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns
+ when: manual
.reports:schedule-dast:
rules:
@@ -428,7 +444,7 @@
################
# Review rules #
################
-.review:rules:mr-and-schedule:
+.review:rules:mr-and-schedule-auto:
rules:
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns
@@ -436,12 +452,33 @@
- <<: *if-dot-com-gitlab-org-schedule
when: on_success
+.review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise:
+ rules:
+ - <<: *if-dot-com-gitlab-org-merge-request
+ changes: *frontend-patterns
+ when: on_success
+ - <<: *if-dot-com-gitlab-org-merge-request
+ changes: *code-qa-patterns
+ when: manual
+ allow_failure: true
+ - <<: *if-dot-com-gitlab-org-schedule
+ when: on_success
+
.review:rules:mr-only-auto:
rules:
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns
when: on_success
+.review:rules:mr-only-auto-if-frontend-manual-otherwise:
+ rules:
+ - <<: *if-dot-com-gitlab-org-merge-request
+ changes: *frontend-patterns
+ when: on_success
+ - <<: *if-dot-com-gitlab-org-merge-request
+ changes: *code-qa-patterns
+ when: manual
+
.review:rules:mr-only-manual:
rules:
- <<: *if-dot-com-gitlab-org-merge-request
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 50b79cde5c5..2f554519632 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -41,7 +41,7 @@ class Admin::ServicesController < Admin::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def service
- @service ||= Service.where(id: params[:id], template: true).first
+ @service ||= Service.find_by(id: params[:id], template: true)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/concerns/import_url_params.rb b/app/controllers/concerns/import_url_params.rb
index e51e4157f50..28a3876810a 100644
--- a/app/controllers/concerns/import_url_params.rb
+++ b/app/controllers/concerns/import_url_params.rb
@@ -4,7 +4,13 @@ module ImportUrlParams
def import_url_params
return {} unless params.dig(:project, :import_url).present?
- { import_url: import_params_to_full_url(params[:project]) }
+ {
+ import_url: import_params_to_full_url(params[:project]),
+ # We need to set import_type because attempting to retry an import by URL
+ # could leave a stale value around. This would erroneously cause an importer
+ # (e.g. import/export) to run.
+ import_type: 'git'
+ }
end
def import_params_to_full_url(params)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 5c957437039..fdf40a77ca4 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -55,6 +55,19 @@ module Emails
reply_to: @message.reply_to,
subject: @message.subject)
end
+
+ def prometheus_alert_fired_email(project_id, user_id, alert_payload)
+ @project = ::Project.find(project_id)
+ user = ::User.find(user_id)
+
+ @alert = ::Gitlab::Alerting::Alert
+ .new(project: @project, payload: alert_payload)
+ .present
+ return unless @alert.valid?
+
+ subject_text = "Alert: #{@alert.full_title}"
+ mail(to: user.notification_email_for(@project.group), subject: subject(subject_text))
+ end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 6b92e5a5625..62827f20929 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -523,6 +523,14 @@ class NotificationService
end
end
+ def prometheus_alerts_fired(project, alerts)
+ return if project.emails_disabled?
+
+ owners_and_maintainers_without_invites(project).to_a.product(alerts).each do |recipient, alert|
+ mailer.prometheus_alert_fired_email(project.id, recipient.user.id, alert).deliver_later
+ end
+ end
+
protected
def new_resource_email(target, method)
@@ -618,6 +626,16 @@ class NotificationService
private
+ def owners_and_maintainers_without_invites(project)
+ recipients = project.members.active_without_invites_and_requests.owners_and_maintainers
+
+ if recipients.empty? && project.group
+ recipients = project.group.members.active_without_invites_and_requests.owners_and_maintainers
+ end
+
+ recipients
+ end
+
def project_maintainers_recipients(target, action:)
NotificationRecipients::BuildService.build_project_maintainers_recipients(target, action: action)
end
diff --git a/app/views/notify/prometheus_alert_fired_email.html.haml b/app/views/notify/prometheus_alert_fired_email.html.haml
new file mode 100644
index 00000000000..17f9481d353
--- /dev/null
+++ b/app/views/notify/prometheus_alert_fired_email.html.haml
@@ -0,0 +1,28 @@
+%p
+ = _('An alert has been triggered in %{project_path}.') % { project_path: @alert.project_full_path }
+
+- if description = @alert.description
+ %p
+ = _('Description:')
+ = description
+
+- if env_name = @alert.environment_name
+ %p
+ = _('Environment:')
+ = env_name
+
+- if metric_query = @alert.metric_query
+ %p
+ = _('Metric:')
+
+ %pre
+ = metric_query
+
+- if @alert.show_incident_issues_link?
+ %p
+ = link_to(_('View incident issues.'), @alert.incident_issues_link)
+
+- if @alert.show_performance_dashboard_link?
+ %p
+ = link_to(_('View performance dashboard.'), @alert.performance_dashboard_link)
+
diff --git a/app/views/notify/prometheus_alert_fired_email.text.erb b/app/views/notify/prometheus_alert_fired_email.text.erb
new file mode 100644
index 00000000000..c3f005cfb7e
--- /dev/null
+++ b/app/views/notify/prometheus_alert_fired_email.text.erb
@@ -0,0 +1,21 @@
+<%= _('An alert has been triggered in %{project_path}.') % { project_path: @alert.project_full_path } %>.
+
+<% if description = @alert.description %>
+<%= _('Description:') %> <%= description %>
+<% end %>
+
+<% if env_name = @alert.environment_name %>
+<%= _('Environment:') %> <%= env_name %>
+<% end %>
+
+<% if metric_query = @alert.metric_query %>
+<%= _('Metric:') %> <%= metric_query %>
+<% end %>
+
+<% if @alert.show_incident_issues_link? %>
+<%= _('View incident issues.') %> <%= @alert.incident_issues_link %>
+<% end %>
+
+<% if @alert.show_performance_dashboard_link? %>
+<%= _('View the performance dashboard at') %> <%= @alert.performance_dashboard_link %>
+<% end %>
diff --git a/changelogs/unreleased/sh-fix-import-by-url-retries.yml b/changelogs/unreleased/sh-fix-import-by-url-retries.yml
new file mode 100644
index 00000000000..b66026f4891
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-import-by-url-retries.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure import by URL works after a failed import
+merge_request: 27546
+author:
+type: fixed
diff --git a/db/migrate/20200318152134_adds_sha256_to_package_files.rb b/db/migrate/20200318152134_adds_sha256_to_package_files.rb
new file mode 100644
index 00000000000..26d2c1b97d4
--- /dev/null
+++ b/db/migrate/20200318152134_adds_sha256_to_package_files.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddsSha256ToPackageFiles < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :packages_package_files, :file_sha256, :binary
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 69ef887bfe5..ba3c1985593 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_03_16_111759) do
+ActiveRecord::Schema.define(version: 2020_03_18_152134) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -3057,6 +3057,7 @@ ActiveRecord::Schema.define(version: 2020_03_16_111759) do
t.binary "file_sha1"
t.string "file_name", null: false
t.text "file", null: false
+ t.binary "file_sha256"
t.index ["package_id", "file_name"], name: "index_packages_package_files_on_package_id_and_file_name"
end
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index 1454f91ac20..5e2b5b3c230 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -133,7 +133,9 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
|------------------------------|--------------------------------------------------------------------------|
| `yaml-patterns` | Only create job for YAML-related changes. |
| `docs-patterns` | Only create job for docs-related changes. |
-| `backstage-patterns` | Only create job for backstage-related changes. |
+| `frontend-dependency-patterns` | Only create job when frontend dependencies are updated (i.e. `package.json`, and `yarn.lock`). changes. |
+| `frontend-patterns` | Only create job for frontend-related changes. |
+| `backstage-patterns` | Only create job for backstage-related changes (i.e. Danger, fixtures, RuboCop, specs). |
| `code-patterns` | Only create job for code-related changes. |
| `qa-patterns` | Only create job for QA-related changes. |
| `code-backstage-patterns` | Combination of `code-patterns` and `backstage-patterns`. |
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index 6ec09c071ca..cbc033fdedc 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -51,7 +51,7 @@ The Dashboard is the default view of the Admin Area, and is made up of the follo
| Section | Description |
|:-----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------|
| Projects | The total number of projects, up to 10 of the latest projects, and the option of creating a new project. |
-| Users | The total number of users, up to 10 of the latest users, and the option of creating a new user. |
+| Users | The total number of users, up to 10 of the latest users, the option of creating a new user, and a link to [**Users statistics**](#users-statistics). |
| Groups | The total number of groups, up to 10 of the latest groups, and the option of creating a new group. |
| Statistics | Totals of all elements of the GitLab instance. |
| Features | All features available on the GitLab instance. Enabled features are marked with a green circle icon, and disabled features are marked with a power icon. |
@@ -134,6 +134,19 @@ To search for users, enter your criteria in the search field. The user search is
insensitive, and applies partial matching to name and username. To search for an email address,
you must provide the complete email address.
+#### Users statistics
+
+The **Users statistics** page provides an overview of user accounts by role. Use this information
+when validating seat usage of your subscription.
+
+The page displays subtotals of all users matching criteria such as _Users with highest role
+Maintainer_ and _Blocked users_.
+
+The **Total users** is calculated as: **Active users** + **Blocked users**.
+
+GitLab billing is based on the number of active users. For details of active users, see
+[Choosing the number of users](../../subscriptions/index.md#choosing-the-number-of-users).
+
### Administering Groups
You can administer all groups in the GitLab instance from the Admin Area's Groups page.
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 0b5ebf3c74c..efffdaab8e5 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -245,7 +245,7 @@ project):
```yaml
include:
- template: Serverless.gitlab-ci.yml
+ - template: Serverless.gitlab-ci.yml
functions:build:
extends: .serverless:build:functions
@@ -462,7 +462,7 @@ Add the following `.gitlab-ci.yml` to the root of your repository
```yaml
include:
- template: Serverless.gitlab-ci.yml
+ - template: Serverless.gitlab-ci.yml
build:
extends: .serverless:build:image
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 5d52ca0b40a..c32be9c9438 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -57,3 +57,54 @@ function echoinfo() {
printf "\033[0;33m%s\n\033[0m" "${1}" >&2;
fi
}
+
+function get_job_id() {
+ local job_name="${1}"
+ local query_string="${2:+&${2}}"
+ local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
+ if [ -z "${api_token}" ]; then
+ echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
+ return
+ fi
+
+ local max_page=3
+ local page=1
+
+ while true; do
+ local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
+ echoinfo "GET ${url}"
+
+ local job_id
+ job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
+ [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
+
+ let "page++"
+ done
+
+ if [[ "${job_id}" == "" ]]; then
+ echoerr "The '${job_name}' job ID couldn't be retrieved!"
+ else
+ echoinfo "The '${job_name}' job ID is ${job_id}"
+ echo "${job_id}"
+ fi
+}
+
+function play_job() {
+ local job_name="${1}"
+ local job_id
+ job_id=$(get_job_id "${job_name}" "scope=manual");
+ if [ -z "${job_id}" ]; then return; fi
+
+ local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
+ if [ -z "${api_token}" ]; then
+ echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
+ return
+ fi
+
+ local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
+ echoinfo "POST ${url}"
+
+ local job_url
+ job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".web_url")
+ echoinfo "Manual job '${job_name}' started at: ${job_url}"
+}
diff --git a/spec/controllers/concerns/import_url_params_spec.rb b/spec/controllers/concerns/import_url_params_spec.rb
index adbe6e5d3bf..41e29d71386 100644
--- a/spec/controllers/concerns/import_url_params_spec.rb
+++ b/spec/controllers/concerns/import_url_params_spec.rb
@@ -31,7 +31,8 @@ describe ImportUrlParams do
describe '#import_url_params' do
it 'returns hash with import_url' do
expect(import_url_params).to eq(
- import_url: "https://user:password@url.com"
+ import_url: "https://user:password@url.com",
+ import_type: 'git'
)
end
end
@@ -48,7 +49,8 @@ describe ImportUrlParams do
describe '#import_url_params' do
it 'does not change the url' do
expect(import_url_params).to eq(
- import_url: "https://user:password@url.com"
+ import_url: "https://user:password@url.com",
+ import_type: 'git'
)
end
end
diff --git a/spec/mailers/emails/projects_spec.rb b/spec/mailers/emails/projects_spec.rb
new file mode 100644
index 00000000000..6c94ed0aa4d
--- /dev/null
+++ b/spec/mailers/emails/projects_spec.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'email_spec'
+
+describe Emails::Projects do
+ include EmailSpec::Matchers
+ include_context 'gitlab email notification'
+
+ shared_examples 'no email' do
+ it 'does not send mail' do
+ expect(subject.message).to be_a_kind_of(ActionMailer::Base::NullMail)
+ end
+ end
+
+ shared_examples 'shows the incident issues url' do
+ context 'create issue setting enabled' do
+ before do
+ create(:project_incident_management_setting, project: project, create_issue: true)
+ end
+
+ let(:incident_issues_url) do
+ project_issues_url(project, label_name: 'incident')
+ end
+
+ it { is_expected.to have_body_text(incident_issues_url) }
+ end
+ end
+
+ let_it_be(:user) { create(:user) }
+
+ describe '#prometheus_alert_fired_email' do
+ subject do
+ Notify.prometheus_alert_fired_email(project.id, user.id, alert_params)
+ end
+
+ let(:alert_params) do
+ { 'startsAt' => Time.now.rfc3339 }
+ end
+
+ context 'with a gitlab alert' do
+ before do
+ alert_params['labels'] = { 'gitlab_alert_id' => alert.prometheus_metric_id.to_s }
+ end
+
+ let(:title) do
+ "#{alert.title} #{alert.computed_operator} #{alert.threshold}"
+ end
+
+ let(:metrics_url) do
+ metrics_project_environment_url(project, environment)
+ end
+
+ let(:environment) { alert.environment }
+
+ let!(:alert) { create(:prometheus_alert, project: project) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'has expected subject' do
+ is_expected.to have_subject("#{project.name} | Alert: #{environment.name}: #{title} for 5 minutes")
+ end
+
+ it 'has expected content' do
+ is_expected.to have_body_text('An alert has been triggered')
+ is_expected.to have_body_text(project.full_path)
+ is_expected.to have_body_text('Environment:')
+ is_expected.to have_body_text(environment.name)
+ is_expected.to have_body_text('Metric:')
+ is_expected.to have_body_text(alert.full_query)
+ is_expected.to have_body_text(metrics_url)
+ end
+
+ it_behaves_like 'shows the incident issues url'
+ end
+
+ context 'with no payload' do
+ let(:alert_params) { {} }
+
+ it_behaves_like 'no email'
+ end
+
+ context 'with an unknown alert' do
+ before do
+ alert_params['labels'] = { 'gitlab_alert_id' => 'unknown' }
+ end
+
+ it_behaves_like 'no email'
+ end
+
+ context 'with an external alert' do
+ let(:title) { 'alert title' }
+
+ let(:metrics_url) do
+ metrics_project_environments_url(project)
+ end
+
+ before do
+ alert_params['annotations'] = { 'title' => title }
+ alert_params['generatorURL'] = 'http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1'
+ end
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'has expected subject' do
+ is_expected.to have_subject("#{project.name} | Alert: #{title}")
+ end
+
+ it 'has expected content' do
+ is_expected.to have_body_text('An alert has been triggered')
+ is_expected.to have_body_text(project.full_path)
+ is_expected.not_to have_body_text('Description:')
+ is_expected.not_to have_body_text('Environment:')
+ end
+
+ context 'with annotated description' do
+ let(:description) { 'description' }
+
+ before do
+ alert_params['annotations']['description'] = description
+ end
+
+ it 'shows the description' do
+ is_expected.to have_body_text('Description:')
+ is_expected.to have_body_text(description)
+ end
+ end
+
+ it_behaves_like 'shows the incident issues url'
+ end
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 96906b4ca3c..8b43844eb96 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -2783,6 +2783,41 @@ describe NotificationService, :mailer do
end
end
+ describe '#prometheus_alerts_fired' do
+ let!(:project) { create(:project) }
+ let!(:prometheus_alert) { create(:prometheus_alert, project: project) }
+ let!(:master) { create(:user) }
+ let!(:developer) { create(:user) }
+
+ before do
+ project.add_master(master)
+ end
+
+ it 'sends the email to owners and masters' do
+ expect(Notify).to receive(:prometheus_alert_fired_email).with(project.id, master.id, prometheus_alert).and_call_original
+ expect(Notify).to receive(:prometheus_alert_fired_email).with(project.id, project.owner.id, prometheus_alert).and_call_original
+ expect(Notify).not_to receive(:prometheus_alert_fired_email).with(project.id, developer.id, prometheus_alert)
+
+ subject.prometheus_alerts_fired(prometheus_alert.project, [prometheus_alert])
+ end
+
+ it_behaves_like 'project emails are disabled' do
+ before do
+ allow_next_instance_of(::Gitlab::Alerting::Alert) do |instance|
+ allow(instance).to receive(:valid?).and_return(true)
+ end
+ end
+
+ let(:alert_params) { { 'labels' => { 'gitlab_alert_id' => 'unknown' } } }
+ let(:notification_target) { prometheus_alert.project }
+ let(:notification_trigger) { subject.prometheus_alerts_fired(prometheus_alert.project, [alert_params]) }
+
+ around do |example|
+ perform_enqueued_jobs { example.run }
+ end
+ end
+ end
+
def build_team(project)
@u_watcher = create_global_setting_for(create(:user), :watch)
@u_participating = create_global_setting_for(create(:user), :participating)