diff options
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) |