diff options
23 files changed, 565 insertions, 164 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 34708977d20..9e84e441ae8 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -521,6 +521,13 @@ import ProjectVariables from './project_variables'; case 'projects:settings:ci_cd:show': // Initialize expandable settings panels initSettingsPanels(); + + import(/* webpackChunkName: "ci-cd-settings" */ './projects/ci_cd_settings_bundle') + .then(ciCdSettings => ciCdSettings.default()) + .catch((err) => { + Flash(s__('ProjectSettings|Problem setting up the CI/CD settings JavaScript')); + throw err; + }); case 'groups:settings:ci_cd:show': new ProjectVariables(); break; diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index 67261c1c9b4..44deab9288e 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -41,7 +41,7 @@ const createFlashEl = (message, type, isInContentWrapper = false) => ` `; const removeFlashClickListener = (flashEl, fadeTransition) => { - flashEl.parentNode.addEventListener('click', () => hideFlash(flashEl, fadeTransition)); + flashEl.addEventListener('click', () => hideFlash(flashEl, fadeTransition)); }; /* diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 6fa1e84c170..33cc807912c 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -190,7 +190,7 @@ export const insertText = (target, text) => { target.selectionStart = target.selectionEnd = selectionStart + insertedText.length; // Trigger autosave - $(target).trigger('input'); + target.dispatchEvent(new Event('input')); // Trigger autosize const event = document.createEvent('Event'); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 08e326cba9c..5e0edd823be 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -301,6 +301,8 @@ $(function () { const flashContainer = document.querySelector('.flash-container'); if (flashContainer && flashContainer.children.length) { - removeFlashClickListener(flashContainer.children[0]); + flashContainer.querySelectorAll('.flash-alert, .flash-notice, .flash-success').forEach((flashEl) => { + removeFlashClickListener(flashEl); + }); } }); diff --git a/app/assets/javascripts/projects/ci_cd_settings_bundle.js b/app/assets/javascripts/projects/ci_cd_settings_bundle.js new file mode 100644 index 00000000000..90e418f6771 --- /dev/null +++ b/app/assets/javascripts/projects/ci_cd_settings_bundle.js @@ -0,0 +1,19 @@ +function updateAutoDevopsRadios(radioWrappers) { + radioWrappers.forEach((radioWrapper) => { + const radio = radioWrapper.querySelector('.js-auto-devops-enable-radio'); + const runPipelineCheckboxWrapper = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox-wrapper'); + const runPipelineCheckbox = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox'); + + if (runPipelineCheckbox) { + runPipelineCheckbox.checked = radio.checked; + runPipelineCheckboxWrapper.classList.toggle('hide', !radio.checked); + } + }); +} + +export default function initCiCdSettings() { + const radioWrappers = document.querySelectorAll('.js-auto-devops-enable-radio-wrapper'); + radioWrappers.forEach(radioWrapper => + radioWrapper.addEventListener('change', () => updateAutoDevopsRadios(radioWrappers)), + ); +} diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index e1b086ebb2b..88ce119ee3a 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -34,8 +34,15 @@ } } + .flash-success { + @extend .alert; + @extend .alert-success; + margin: 0; + } + .flash-notice, - .flash-alert { + .flash-alert, + .flash-success { border-radius: $border-radius-default; .container-fluid, @@ -48,7 +55,8 @@ margin-bottom: 0; .flash-notice, - .flash-alert { + .flash-alert, + .flash-success { border-radius: 0; } } diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb index abab2e2f0c9..b890818c475 100644 --- a/app/controllers/projects/pipelines_settings_controller.rb +++ b/app/controllers/projects/pipelines_settings_controller.rb @@ -6,11 +6,19 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController end def update - if @project.update(update_params) - flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." - redirect_to project_settings_ci_cd_path(@project) - else - render 'show' + Projects::UpdateService.new(project, current_user, update_params).tap do |service| + if service.execute + flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." + + if service.run_auto_devops_pipeline? + CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false) + flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe + end + + redirect_to project_settings_ci_cd_path(@project) + else + render 'show' + end end end @@ -21,6 +29,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :public_builds, :auto_cancel_pending_pipelines, :ci_config_path, + :run_auto_devops_pipeline_implicit, :run_auto_devops_pipeline_explicit, auto_devops_attributes: [:id, :domain, :enabled] ) end diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb index 483b957decb..069c29feb80 100644 --- a/app/helpers/auto_devops_helper.rb +++ b/app/helpers/auto_devops_helper.rb @@ -8,6 +8,22 @@ module AutoDevopsHelper !project.ci_service end + def show_run_auto_devops_pipeline_checkbox_for_instance_setting?(project) + return false if project.repository.gitlab_ci_yml + + if project&.auto_devops&.enabled.present? + !project.auto_devops.enabled && current_application_settings.auto_devops_enabled? + else + current_application_settings.auto_devops_enabled? + end + end + + def show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(project) + return false if project.repository.gitlab_ci_yml + + !project.auto_devops_enabled? + end + def auto_devops_warning_message(project) missing_domain = !project.auto_devops&.has_domain? missing_service = !project.kubernetes_service&.active? diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 13e292a18bf..72eecc61c96 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -15,7 +15,7 @@ module Projects return error("Could not set the default branch") unless project.change_head(params[:default_branch]) end - if project.update_attributes(params.except(:default_branch)) + if project.update_attributes(update_params) if project.previous_changes.include?('path') project.rename_repo else @@ -31,8 +31,16 @@ module Projects end end + def run_auto_devops_pipeline? + params.dig(:run_auto_devops_pipeline_explicit) == 'true' || params.dig(:run_auto_devops_pipeline_implicit) == 'true' + end + private + def update_params + params.except(:default_branch, :run_auto_devops_pipeline_explicit, :run_auto_devops_pipeline_implicit) + end + def renaming_project_with_container_registry_tags? new_path = params[:path] diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index baa8036de10..1db32379df3 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -1,10 +1,6 @@ .flash-container.flash-container-page - - if alert - .flash-alert + -# We currently only support `alert`, `notice`, `success` + - flash.each do |key, value| + %div{ class: "flash-#{key}" } %div{ class: (container_class) } - %span= alert - - - elsif notice - .flash-notice - %div{ class: (container_class) } - %span= notice + %span= value diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml index 77211099830..ee4fa663b9f 100644 --- a/app/views/projects/pipelines_settings/_show.html.haml +++ b/app/views/projects/pipelines_settings/_show.html.haml @@ -13,29 +13,39 @@ %p.settings-message.text-center = message.html_safe = f.fields_for :auto_devops_attributes, @auto_devops do |form| - .radio + .radio.js-auto-devops-enable-radio-wrapper = form.label :enabled_true do - = form.radio_button :enabled, 'true' + = form.radio_button :enabled, 'true', class: 'js-auto-devops-enable-radio' %strong Enable Auto DevOps %br %span.descr The Auto DevOps pipeline configuration will be used when there is no <code>.gitlab-ci.yml</code> in the project. - .radio + - if show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(@project) + .checkbox.hide.js-run-auto-devops-pipeline-checkbox-wrapper + = label_tag 'project[run_auto_devops_pipeline_explicit]' do + = check_box_tag 'project[run_auto_devops_pipeline_explicit]', true, false, class: 'js-run-auto-devops-pipeline-checkbox' + = s_('ProjectSettings|Immediately run a pipeline on the default branch') + + .radio.js-auto-devops-enable-radio-wrapper = form.label :enabled_false do - = form.radio_button :enabled, 'false' + = form.radio_button :enabled, 'false', class: 'js-auto-devops-enable-radio' %strong Disable Auto DevOps %br %span.descr An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery. - .radio - = form.label :enabled_nil do - = form.radio_button :enabled, '' + .radio.js-auto-devops-enable-radio-wrapper + = form.label :enabled_ do + = form.radio_button :enabled, '', class: 'js-auto-devops-enable-radio' %strong Instance default (#{current_application_settings.auto_devops_enabled? ? 'enabled' : 'disabled'}) %br %span.descr Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>. - %br + - if show_run_auto_devops_pipeline_checkbox_for_instance_setting?(@project) + .checkbox.hide.js-run-auto-devops-pipeline-checkbox-wrapper + = label_tag 'project[run_auto_devops_pipeline_implicit]' do + = check_box_tag 'project[run_auto_devops_pipeline_implicit]', true, false, class: 'js-run-auto-devops-pipeline-checkbox' + = s_('ProjectSettings|Immediately run a pipeline on the default branch') %p You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages. = form.text_field :domain, class: 'form-control', placeholder: 'domain.com' diff --git a/app/workers/create_pipeline_worker.rb b/app/workers/create_pipeline_worker.rb new file mode 100644 index 00000000000..865ad1ba420 --- /dev/null +++ b/app/workers/create_pipeline_worker.rb @@ -0,0 +1,16 @@ +class CreatePipelineWorker + include Sidekiq::Worker + include PipelineQueue + + enqueue_in group: :creation + + def perform(project_id, user_id, ref, source, params = {}) + project = Project.find(project_id) + user = User.find(user_id) + params = params.deep_symbolize_keys + + Ci::CreatePipelineService + .new(project, user, ref: ref) + .execute(source, **params) + end +end diff --git a/changelogs/unreleased/38962-automatically-run-a-pipeline-when-auto-devops-is-turned-on-in-project-settings.yml b/changelogs/unreleased/38962-automatically-run-a-pipeline-when-auto-devops-is-turned-on-in-project-settings.yml new file mode 100644 index 00000000000..a4d703bc69f --- /dev/null +++ b/changelogs/unreleased/38962-automatically-run-a-pipeline-when-auto-devops-is-turned-on-in-project-settings.yml @@ -0,0 +1,5 @@ +--- +title: Add the option to automatically run a pipeline after updating AutoDevOps settings +merge_request: 15380 +author: +type: changed diff --git a/changelogs/unreleased/40373-fix-issue-note-submit-disabled-on-paste.yml b/changelogs/unreleased/40373-fix-issue-note-submit-disabled-on-paste.yml new file mode 100644 index 00000000000..e683e60397e --- /dev/null +++ b/changelogs/unreleased/40373-fix-issue-note-submit-disabled-on-paste.yml @@ -0,0 +1,6 @@ +--- +title: Fix Issue comment submit button being disabled when pasting content from another + GFM note +merge_request: 15530 +author: +type: fixed diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index a8b918177de..bc7c431731a 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -28,6 +28,7 @@ - [build, 2] - [pipeline, 2] - [pipeline_processing, 5] + - [pipeline_creation, 4] - [pipeline_default, 3] - [pipeline_cache, 3] - [pipeline_hooks, 2] diff --git a/doc/topics/autodevops/img/auto_devops_settings.png b/doc/topics/autodevops/img/auto_devops_settings.png Binary files differnew file mode 100644 index 00000000000..b572cc5b855 --- /dev/null +++ b/doc/topics/autodevops/img/auto_devops_settings.png diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 28308fc905c..914217772b8 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -121,7 +121,7 @@ Google Cloud. ## Enabling Auto DevOps -NOTE: **Note:** +**Note:** If you haven't done already, read the [prerequisites](#prerequisites) to make full use of Auto DevOps. If this is your fist time, we recommend you follow the [quick start guide](#quick-start). @@ -129,10 +129,14 @@ full use of Auto DevOps. If this is your fist time, we recommend you follow the 1. Go to your project's **Settings > CI/CD > General pipelines settings** and find the Auto DevOps section 1. Select "Enable Auto DevOps" +1. After selecting an option to enable Auto DevOps, a checkbox will appear below + so you can immediately run a pipeline on the default branch 1. Optionally, but recommended, add in the [base domain](#auto-devops-base-domain) that will be used by Kubernetes to deploy your application 1. Hit **Save changes** for the changes to take effect +![Project AutoDevops settings section](img/auto_devops_settings.png) + Now that it's enabled, there are a few more steps depending on whether your project has a `.gitlab-ci.yml` or not: diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb index 21b6a6d45f5..b2d83a02290 100644 --- a/spec/controllers/projects/pipelines_settings_controller_spec.rb +++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb @@ -12,19 +12,22 @@ describe Projects::PipelinesSettingsController do end describe 'PATCH update' do - before do + subject do patch :update, namespace_id: project.namespace.to_param, project_id: project, - project: { - auto_devops_attributes: params - } + project: { auto_devops_attributes: params, + run_auto_devops_pipeline_implicit: 'false', + run_auto_devops_pipeline_explicit: auto_devops_pipeline } end context 'when updating the auto_devops settings' do let(:params) { { enabled: '', domain: 'mepmep.md' } } + let(:auto_devops_pipeline) { 'false' } it 'redirects to the settings page' do + subject + expect(response).to have_gitlab_http_status(302) expect(flash[:notice]).to eq("Pipelines settings for '#{project.name}' were successfully updated.") end @@ -33,11 +36,32 @@ describe Projects::PipelinesSettingsController do let(:params) { { enabled: '' } } it 'allows enabled to be set to nil' do + subject project_auto_devops.reload expect(project_auto_devops.enabled).to be_nil end end + + context 'when run_auto_devops_pipeline is true' do + let(:auto_devops_pipeline) { 'true' } + + it 'queues a CreatePipelineWorker' do + expect(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args) + + subject + end + end + + context 'when run_auto_devops_pipeline is not true' do + let(:auto_devops_pipeline) { 'false' } + + it 'does not queue a CreatePipelineWorker' do + expect(CreatePipelineWorker).not_to receive(:perform_async).with(project.id, user.id, :web, any_args) + + subject + end + end end end end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index ea8f997409d..eb8e7265dd3 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -8,13 +8,14 @@ feature "Pipelines settings" do background do sign_in(user) project.team << [user, role] - visit project_pipelines_settings_path(project) end context 'for developer' do given(:role) { :developer } scenario 'to be disallowed to view' do + visit project_settings_ci_cd_path(project) + expect(page.status_code).to eq(404) end end @@ -23,6 +24,8 @@ feature "Pipelines settings" do given(:role) { :master } scenario 'be allowed to change' do + visit project_settings_ci_cd_path(project) + fill_in('Test coverage parsing', with: 'coverage_regex') click_on 'Save changes' @@ -32,6 +35,8 @@ feature "Pipelines settings" do end scenario 'updates auto_cancel_pending_pipelines' do + visit project_settings_ci_cd_path(project) + page.check('Auto-cancel redundant, pending pipelines') click_on 'Save changes' @@ -42,14 +47,119 @@ feature "Pipelines settings" do expect(checkbox).to be_checked end - scenario 'update auto devops settings' do - fill_in('project_auto_devops_attributes_domain', with: 'test.com') - page.choose('project_auto_devops_attributes_enabled_false') - click_on 'Save changes' + describe 'Auto DevOps' do + it 'update auto devops settings' do + visit project_settings_ci_cd_path(project) - expect(page.status_code).to eq(200) - expect(project.auto_devops).to be_present - expect(project.auto_devops).not_to be_enabled + fill_in('project_auto_devops_attributes_domain', with: 'test.com') + page.choose('project_auto_devops_attributes_enabled_false') + click_on 'Save changes' + + expect(page.status_code).to eq(200) + expect(project.auto_devops).to be_present + expect(project.auto_devops).not_to be_enabled + end + + describe 'Immediately run pipeline checkbox option', :js do + context 'when auto devops is set to instance default (enabled)' do + before do + stub_application_setting(auto_devops_enabled: true) + project.create_auto_devops!(enabled: nil) + visit project_settings_ci_cd_path(project) + end + + it 'does not show checkboxes on page-load' do + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false) + end + + it 'selecting explicit disabled hides all checkboxes' do + page.choose('project_auto_devops_attributes_enabled_false') + + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false) + end + + it 'selecting explicit enabled hides all checkboxes because we are already enabled' do + page.choose('project_auto_devops_attributes_enabled_true') + + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false) + end + end + + context 'when auto devops is set to instance default (disabled)' do + before do + stub_application_setting(auto_devops_enabled: false) + project.create_auto_devops!(enabled: nil) + visit project_settings_ci_cd_path(project) + end + + it 'does not show checkboxes on page-load' do + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false) + end + + it 'selecting explicit disabled hides all checkboxes' do + page.choose('project_auto_devops_attributes_enabled_false') + + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 1, visible: false) + end + + it 'selecting explicit enabled shows a checkbox' do + page.choose('project_auto_devops_attributes_enabled_true') + + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper:not(.hide)', count: 1) + end + end + + context 'when auto devops is set to explicit disabled' do + before do + stub_application_setting(auto_devops_enabled: true) + project.create_auto_devops!(enabled: false) + visit project_settings_ci_cd_path(project) + end + + it 'does not show checkboxes on page-load' do + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper.hide', count: 2, visible: false) + end + + it 'selecting explicit enabled shows a checkbox' do + page.choose('project_auto_devops_attributes_enabled_true') + + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper:not(.hide)', count: 1) + end + + it 'selecting instance default (enabled) shows a checkbox' do + page.choose('project_auto_devops_attributes_enabled_') + + expect(page).to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper:not(.hide)', count: 1) + end + end + + context 'when auto devops is set to explicit enabled' do + before do + stub_application_setting(auto_devops_enabled: false) + project.create_auto_devops!(enabled: true) + visit project_settings_ci_cd_path(project) + end + + it 'does not have any checkboxes' do + expect(page).not_to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper', visible: false) + end + end + + context 'when master contains a .gitlab-ci.yml file' do + let(:project) { create(:project, :repository) } + + before do + project.repository.create_file(user, '.gitlab-ci.yml', "script: ['test']", message: 'test', branch_name: project.default_branch) + stub_application_setting(auto_devops_enabled: true) + project.create_auto_devops!(enabled: false) + visit project_settings_ci_cd_path(project) + end + + it 'does not have any checkboxes' do + expect(page).not_to have_selector('.js-run-auto-devops-pipeline-checkbox-wrapper', visible: false) + end + end + end end end end diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb index 5e272af6073..7266e1b84d1 100644 --- a/spec/helpers/auto_devops_helper_spec.rb +++ b/spec/helpers/auto_devops_helper_spec.rb @@ -82,4 +82,104 @@ describe AutoDevopsHelper do it { is_expected.to eq(false) } end end + + describe '.show_run_auto_devops_pipeline_checkbox_for_instance_setting?' do + subject { helper.show_run_auto_devops_pipeline_checkbox_for_instance_setting?(project) } + + context 'when master contains a .gitlab-ci.yml file' do + before do + allow(project.repository).to receive(:gitlab_ci_yml).and_return("script: ['test']") + end + + it { is_expected.to eq(false) } + end + + context 'when auto devops is explicitly enabled' do + before do + project.create_auto_devops!(enabled: true) + end + + it { is_expected.to eq(false) } + end + + context 'when auto devops is explicitly disabled' do + before do + project.create_auto_devops!(enabled: false) + end + + context 'when auto devops is enabled system-wide' do + before do + stub_application_setting(auto_devops_enabled: true) + end + + it { is_expected.to eq(true) } + end + + context 'when auto devops is disabled system-wide' do + before do + stub_application_setting(auto_devops_enabled: false) + end + + it { is_expected.to eq(false) } + end + end + + context 'when auto devops is set to instance setting' do + before do + project.create_auto_devops!(enabled: nil) + end + + it { is_expected.to eq(false) } + end + end + + describe '.show_run_auto_devops_pipeline_checkbox_for_explicit_setting?' do + subject { helper.show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(project) } + + context 'when master contains a .gitlab-ci.yml file' do + before do + allow(project.repository).to receive(:gitlab_ci_yml).and_return("script: ['test']") + end + + it { is_expected.to eq(false) } + end + + context 'when auto devops is explicitly enabled' do + before do + project.create_auto_devops!(enabled: true) + end + + it { is_expected.to eq(false) } + end + + context 'when auto devops is explicitly disabled' do + before do + project.create_auto_devops!(enabled: false) + end + + it { is_expected.to eq(true) } + end + + context 'when auto devops is set to instance setting' do + before do + project.create_auto_devops!(enabled: nil) + end + + context 'when auto devops is enabled system-wide' do + before do + stub_application_setting(auto_devops_enabled: true) + end + + it { is_expected.to eq(false) } + end + + context 'when auto devops is disabled system-wide' do + before do + stub_application_setting(auto_devops_enabled: false) + end + + it { is_expected.to eq(true) } + end + end + end end diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js index b669aabcee4..97e3ab682c5 100644 --- a/spec/javascripts/flash_spec.js +++ b/spec/javascripts/flash_spec.js @@ -278,7 +278,7 @@ describe('Flash', () => { removeFlashClickListener(flashEl, false); - flashEl.parentNode.click(); + flashEl.click(); setTimeout(() => { expect(document.querySelector('.flash')).toBeNull(); diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 3da222e2ed8..fcd71857af3 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,198 +1,222 @@ require 'spec_helper' -describe Projects::UpdateService, '#execute' do +describe Projects::UpdateService do include ProjectForksHelper - let(:gitlab_shell) { Gitlab::Shell.new } let(:user) { create(:user) } - let(:admin) { create(:admin) } - let(:project) do create(:project, creator: user, namespace: user.namespace) end - context 'when changing visibility level' do - context 'when visibility_level is INTERNAL' do - it 'updates the project to internal' do - result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) - - expect(result).to eq({ status: :success }) - expect(project).to be_internal - end - end - - context 'when visibility_level is PUBLIC' do - it 'updates the project to public' do - result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC) - expect(result).to eq({ status: :success }) - expect(project).to be_public - end - end - - context 'when visibility levels are restricted to PUBLIC only' do - before do - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - end + describe '#execute' do + let(:gitlab_shell) { Gitlab::Shell.new } + let(:admin) { create(:admin) } + context 'when changing visibility level' do context 'when visibility_level is INTERNAL' do it 'updates the project to internal' do result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) + expect(result).to eq({ status: :success }) expect(project).to be_internal end end context 'when visibility_level is PUBLIC' do - it 'does not update the project to public' do + it 'updates the project to public' do result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + expect(result).to eq({ status: :success }) + expect(project).to be_public + end + end - expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) - expect(project).to be_private + context 'when visibility levels are restricted to PUBLIC only' do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) end - context 'when updated by an admin' do - it 'updates the project to public' do - result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + context 'when visibility_level is INTERNAL' do + it 'updates the project to internal' do + result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) expect(result).to eq({ status: :success }) - expect(project).to be_public + expect(project).to be_internal end end - end - end - context 'When project visibility is higher than parent group' do - let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + context 'when visibility_level is PUBLIC' do + it 'does not update the project to public' do + result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC) - before do - project.update(namespace: group, visibility_level: group.visibility_level) + expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) + expect(project).to be_private + end + + context 'when updated by an admin' do + it 'updates the project to public' do + result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + expect(result).to eq({ status: :success }) + expect(project).to be_public + end + end + end end - it 'does not update project visibility level' do - result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + context 'When project visibility is higher than parent group' do + let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + + before do + project.update(namespace: group, visibility_level: group.visibility_level) + end + + it 'does not update project visibility level' do + result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC) - expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' }) - expect(project.reload).to be_internal + expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' }) + expect(project.reload).to be_internal + end end end - end - describe 'when updating project that has forks' do - let(:project) { create(:project, :internal) } - let(:forked_project) { fork_project(project) } + describe 'when updating project that has forks' do + let(:project) { create(:project, :internal) } + let(:forked_project) { fork_project(project) } - it 'updates forks visibility level when parent set to more restrictive' do - opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } + it 'updates forks visibility level when parent set to more restrictive' do + opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } - expect(project).to be_internal - expect(forked_project).to be_internal + expect(project).to be_internal + expect(forked_project).to be_internal - expect(update_project(project, admin, opts)).to eq({ status: :success }) + expect(update_project(project, admin, opts)).to eq({ status: :success }) - expect(project).to be_private - expect(forked_project.reload).to be_private - end + expect(project).to be_private + expect(forked_project.reload).to be_private + end - it 'does not update forks visibility level when parent set to less restrictive' do - opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC } + it 'does not update forks visibility level when parent set to less restrictive' do + opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC } - expect(project).to be_internal - expect(forked_project).to be_internal + expect(project).to be_internal + expect(forked_project).to be_internal - expect(update_project(project, admin, opts)).to eq({ status: :success }) + expect(update_project(project, admin, opts)).to eq({ status: :success }) - expect(project).to be_public - expect(forked_project.reload).to be_internal + expect(project).to be_public + expect(forked_project.reload).to be_internal + end end - end - context 'when updating a default branch' do - let(:project) { create(:project, :repository) } + context 'when updating a default branch' do + let(:project) { create(:project, :repository) } - it 'changes a default branch' do - update_project(project, admin, default_branch: 'feature') + it 'changes a default branch' do + update_project(project, admin, default_branch: 'feature') - expect(Project.find(project.id).default_branch).to eq 'feature' - end + expect(Project.find(project.id).default_branch).to eq 'feature' + end - it 'does not change a default branch' do - # The branch 'unexisted-branch' does not exist. - update_project(project, admin, default_branch: 'unexisted-branch') + it 'does not change a default branch' do + # The branch 'unexisted-branch' does not exist. + update_project(project, admin, default_branch: 'unexisted-branch') - expect(Project.find(project.id).default_branch).to eq 'master' + expect(Project.find(project.id).default_branch).to eq 'master' + end end - end - context 'when updating a project that contains container images' do - before do - stub_container_registry_config(enabled: true) - stub_container_registry_tags(repository: /image/, tags: %w[rc1]) - create(:container_repository, project: project, name: :image) - end + context 'when updating a project that contains container images' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w[rc1]) + create(:container_repository, project: project, name: :image) + end - it 'does not allow to rename the project' do - result = update_project(project, admin, path: 'renamed') + it 'does not allow to rename the project' do + result = update_project(project, admin, path: 'renamed') - expect(result).to include(status: :error) - expect(result[:message]).to match(/contains container registry tags/) - end + expect(result).to include(status: :error) + expect(result[:message]).to match(/contains container registry tags/) + end - it 'allows to update other settings' do - result = update_project(project, admin, public_builds: true) + it 'allows to update other settings' do + result = update_project(project, admin, public_builds: true) - expect(result[:status]).to eq :success - expect(project.reload.public_builds).to be true + expect(result[:status]).to eq :success + expect(project.reload.public_builds).to be true + end end - end - context 'when renaming a project' do - let(:repository_storage) { 'default' } - let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + context 'when renaming a project' do + let(:repository_storage) { 'default' } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } - context 'with legacy storage' do - before do - gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing") - end + context 'with legacy storage' do + before do + gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing") + end + + after do + gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") + end + + it 'does not allow renaming when new path matches existing repository on disk' do + result = update_project(project, admin, path: 'existing') - after do - gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") + expect(result).to include(status: :error) + expect(result[:message]).to match('There is already a repository with that name on disk') + expect(project).not_to be_valid + expect(project.errors.messages).to have_key(:base) + expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk') + end end - it 'does not allow renaming when new path matches existing repository on disk' do - result = update_project(project, admin, path: 'existing') + context 'with hashed storage' do + let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } - expect(result).to include(status: :error) - expect(result[:message]).to match('There is already a repository with that name on disk') - expect(project).not_to be_valid - expect(project.errors.messages).to have_key(:base) - expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk') + before do + stub_application_setting(hashed_storage_enabled: true) + end + + it 'does not check if new path matches existing repository on disk' do + expect(project).not_to receive(:repository_with_same_path_already_exists?) + + result = update_project(project, admin, path: 'existing') + + expect(result).to include(status: :success) + end end end - context 'with hashed storage' do - let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } + context 'when passing invalid parameters' do + it 'returns an error result when record cannot be updated' do + result = update_project(project, admin, { name: 'foo&bar' }) - before do - stub_application_setting(hashed_storage_enabled: true) + expect(result).to eq({ + status: :error, + message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'." + }) end + end + end - it 'does not check if new path matches existing repository on disk' do - expect(project).not_to receive(:repository_with_same_path_already_exists?) + describe '#run_auto_devops_pipeline?' do + subject { described_class.new(project, user, params).run_auto_devops_pipeline? } - result = update_project(project, admin, path: 'existing') + context 'when neither pipeline setting is true' do + let(:params) { {} } - expect(result).to include(status: :success) - end + it { is_expected.to eq(false) } + end + + context 'when run_auto_devops_pipeline_explicit is true' do + let(:params) { { run_auto_devops_pipeline_explicit: 'true' } } + + it { is_expected.to eq(true) } end - end - context 'when passing invalid parameters' do - it 'returns an error result when record cannot be updated' do - result = update_project(project, admin, { name: 'foo&bar' }) + context 'when run_auto_devops_pipeline_implicit is true' do + let(:params) { { run_auto_devops_pipeline_implicit: 'true' } } - expect(result).to eq({ - status: :error, - message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'." - }) + it { is_expected.to eq(true) } end end diff --git a/spec/workers/create_pipeline_worker_spec.rb b/spec/workers/create_pipeline_worker_spec.rb new file mode 100644 index 00000000000..02cb0f46cb4 --- /dev/null +++ b/spec/workers/create_pipeline_worker_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe CreatePipelineWorker do + describe '#perform' do + let(:worker) { described_class.new } + + context 'when a project not found' do + it 'does not call the Service' do + expect(Ci::CreatePipelineService).not_to receive(:new) + expect { worker.perform(99, create(:user).id, 'master', :web) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'when a user not found' do + let(:project) { create(:project) } + + it 'does not call the Service' do + expect(Ci::CreatePipelineService).not_to receive(:new) + expect { worker.perform(project.id, 99, project.default_branch, :web) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'when everything is ok' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:create_pipeline_service) { instance_double(Ci::CreatePipelineService) } + + it 'calls the Service' do + expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: project.default_branch).and_return(create_pipeline_service) + expect(create_pipeline_service).to receive(:execute).with(:web, any_args) + + worker.perform(project.id, user.id, project.default_branch, :web) + end + end + end +end |