Commit 21b16c5a authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '37158-autodevops-banner' into 'master'

Resolve "Banner to enable Auto DevOps at project level"

Closes #37158

See merge request !13991
parents 2f1a039a 4eb8722d
...@@ -160,6 +160,9 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -160,6 +160,9 @@ import initChangesDropdown from './init_changes_dropdown';
const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
filteredSearchManager.setup(); filteredSearchManager.setup();
} }
if (page === 'projects:merge_requests:index') {
new UserCallout({ setCalloutPerProject: true });
}
const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_';
IssuableIndex.init(pagePrefix); IssuableIndex.init(pagePrefix);
...@@ -342,6 +345,7 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -342,6 +345,7 @@ import initChangesDropdown from './init_changes_dropdown';
case 'projects:show': case 'projects:show':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new NotificationsForm(); new NotificationsForm();
new UserCallout({ setCalloutPerProject: true });
if ($('#tree-slider').length) new TreeView(); if ($('#tree-slider').length) new TreeView();
if ($('.blob-viewer').length) new BlobViewer(); if ($('.blob-viewer').length) new BlobViewer();
...@@ -361,6 +365,9 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -361,6 +365,9 @@ import initChangesDropdown from './init_changes_dropdown';
case 'projects:pipelines:new': case 'projects:pipelines:new':
new NewBranchForm($('.js-new-pipeline-form')); new NewBranchForm($('.js-new-pipeline-form'));
break; break;
case 'projects:pipelines:index':
new UserCallout({ setCalloutPerProject: true });
break;
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
case 'projects:pipelines:failures': case 'projects:pipelines:failures':
case 'projects:pipelines:show': case 'projects:pipelines:show':
...@@ -418,6 +425,7 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -418,6 +425,7 @@ import initChangesDropdown from './init_changes_dropdown';
new TreeView(); new TreeView();
new BlobViewer(); new BlobViewer();
new NewCommitForm($('.js-create-dir-form')); new NewCommitForm($('.js-create-dir-form'));
new UserCallout({ setCalloutPerProject: true });
$('#tree-slider').waitForImages(function() { $('#tree-slider').waitForImages(function() {
gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
}); });
......
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
return { return {
endpoint: pipelinesData.endpoint, endpoint: pipelinesData.endpoint,
cssClass: pipelinesData.cssClass,
helpPagePath: pipelinesData.helpPagePath, helpPagePath: pipelinesData.helpPagePath,
autoDevopsPath: pipelinesData.helpAutoDevopsPath, autoDevopsPath: pipelinesData.helpAutoDevopsPath,
newPipelinePath: pipelinesData.newPipelinePath, newPipelinePath: pipelinesData.newPipelinePath,
...@@ -140,9 +139,7 @@ ...@@ -140,9 +139,7 @@
}; };
</script> </script>
<template> <template>
<div <div class="pipelines-container">
class="pipelines-container"
:class="cssClass">
<div <div
class="top-area scrolling-tabs-container inner-page-scroll-tabs" class="top-area scrolling-tabs-container inner-page-scroll-tabs"
v-if="!isLoading && !shouldRenderEmptyState"> v-if="!isLoading && !shouldRenderEmptyState">
......
...@@ -41,4 +41,8 @@ export default function initSettingsPanels() { ...@@ -41,4 +41,8 @@ export default function initSettingsPanels() {
$section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section)); $section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section));
$section.find('.settings-content:not(.expanded)').on('scroll.expandSection', () => expandSection($section)); $section.find('.settings-content:not(.expanded)').on('scroll.expandSection', () => expandSection($section));
}); });
if (location.hash) {
expandSection($(location.hash));
}
} }
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
export default class UserCallout { export default class UserCallout {
constructor(className = 'user-callout') { constructor(options = {}) {
this.options = options;
const className = this.options.className || 'user-callout';
this.userCalloutBody = $(`.${className}`); this.userCalloutBody = $(`.${className}`);
this.cookieName = this.userCalloutBody.data('uid'); this.cookieName = this.userCalloutBody.data('uid');
this.isCalloutDismissed = Cookies.get(this.cookieName); this.isCalloutDismissed = Cookies.get(this.cookieName);
...@@ -17,7 +21,11 @@ export default class UserCallout { ...@@ -17,7 +21,11 @@ export default class UserCallout {
dismissCallout(e) { dismissCallout(e) {
const $currentTarget = $(e.currentTarget); const $currentTarget = $(e.currentTarget);
Cookies.set(this.cookieName, 'true', { expires: 365 }); if (this.options.setCalloutPerProject) {
Cookies.set(this.cookieName, 'true', { expires: 365, path: this.userCalloutBody.data('project-path') });
} else {
Cookies.set(this.cookieName, 'true', { expires: 365 });
}
if ($currentTarget.hasClass('close')) { if ($currentTarget.hasClass('close')) {
this.userCalloutBody.remove(); this.userCalloutBody.remove();
......
module AutoDevopsHelper
def show_auto_devops_callout?(project)
show_callout?('auto_devops_settings_dismissed') &&
can?(current_user, :admin_pipeline, project) &&
project.has_auto_devops_implicitly_disabled?
end
end
...@@ -477,6 +477,10 @@ class Project < ActiveRecord::Base ...@@ -477,6 +477,10 @@ class Project < ActiveRecord::Base
end end
end end
def has_auto_devops_implicitly_disabled?
auto_devops&.enabled.nil? && !current_application_settings.auto_devops_enabled?
end
def repository_storage_path def repository_storage_path
Gitlab.config.repositories.storages[repository_storage].try(:[], 'path') Gitlab.config.repositories.storages[repository_storage].try(:[], 'path')
end end
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
- if @project.merge_requests.exists? - if @project.merge_requests.exists?
%div{ class: container_class } %div{ class: container_class }
- if show_auto_devops_callout?(@project)
= render 'shared/auto_devops_callout'
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls .nav-controls
......
...@@ -2,20 +2,23 @@ ...@@ -2,20 +2,23 @@
- page_title "Pipelines" - page_title "Pipelines"
= render "projects/pipelines/head" = render "projects/pipelines/head"
#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), %div{ 'class' => container_class }
"css-class" => container_class, - if show_auto_devops_callout?(@project)
"help-page-path" => help_page_path('ci/quick_start/README'), = render 'shared/auto_devops_callout'
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
"new-pipeline-path" => new_project_pipeline_path(@project),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"all-path" => project_pipelines_path(@project),
"pending-path" => project_pipelines_path(@project, scope: :pending),
"running-path" => project_pipelines_path(@project, scope: :running),
"finished-path" => project_pipelines_path(@project, scope: :finished),
"branches-path" => project_pipelines_path(@project, scope: :branches),
"tags-path" => project_pipelines_path(@project, scope: :tags),
"has-ci" => @project.has_ci?,
"ci-lint-path" => ci_lint_path } }
= page_specific_javascript_bundle_tag('common_vue') #pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
= page_specific_javascript_bundle_tag('pipelines') "help-page-path" => help_page_path('ci/quick_start/README'),
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
"new-pipeline-path" => new_project_pipeline_path(@project),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"all-path" => project_pipelines_path(@project),
"pending-path" => project_pipelines_path(@project, scope: :pending),
"running-path" => project_pipelines_path(@project, scope: :running),
"finished-path" => project_pipelines_path(@project, scope: :finished),
"branches-path" => project_pipelines_path(@project, scope: :branches),
"tags-path" => project_pipelines_path(@project, scope: :tags),
"has-ci" => @project.has_ci?,
"ci-lint-path" => ci_lint_path } }
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('pipelines')
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- expanded = Rails.env.test? - expanded = Rails.env.test?
%section.settings %section.settings#js-general-pipeline-settings
.settings-header .settings-header
%h4 %h4
General pipelines settings General pipelines settings
......
...@@ -82,5 +82,8 @@ ...@@ -82,5 +82,8 @@
- view_path = default_project_view - view_path = default_project_view
- if show_auto_devops_callout?(@project)
= render 'shared/auto_devops_callout'
%div{ class: project_child_container_class(view_path) } %div{ class: project_child_container_class(view_path) }
= render view_path = render view_path
...@@ -14,5 +14,7 @@ ...@@ -14,5 +14,7 @@
= render "projects/commits/head" = render "projects/commits/head"
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if show_auto_devops_callout?(@project)
= render 'shared/auto_devops_callout'
= render 'projects/last_push' = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
.user-callout{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
.bordered-box.landing.content-block
%button.btn.btn-default.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss Auto DevOps box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
.svg-container
= custom_icon('icon_autodevops')
.user-callout-copy
%h4= _('Auto DevOps (Beta)')
%p= _('Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p
#{s_('AutoDevOps|Learn more in the')}
= link_to _('Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
= link_to _('Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn btn-primary js-close-callout'
This diff is collapsed.
---
title: Created callout for auto devops
merge_request:
author:
type: added
require 'spec_helper'
describe AutoDevopsHelper do
set(:project) { create(:project) }
set(:user) { create(:user) }
describe '.show_auto_devops_callout?' do
let(:allowed) { true }
before do
allow(helper).to receive(:can?).with(user, :admin_pipeline, project) { allowed }
allow(helper).to receive(:current_user) { user }
end
subject { helper.show_auto_devops_callout?(project) }
context 'when all conditions are met' do
it { is_expected.to eq(true) }
end
context 'when dismissed' do
before do
helper.request.cookies[:auto_devops_settings_dismissed] = 'true'
end
it { is_expected.to eq(false) }
end
context 'when user cannot admin project' do
let(:allowed) { false }
it { is_expected.to eq(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(false) }
end
context 'when auto devops is explicitly enabled for project' do
before do
project.create_auto_devops!(enabled: true)
end
it { is_expected.to eq(false) }
end
context 'when auto devops is explicitly disabled for project' do
before do
project.create_auto_devops!(enabled: false)
end
it { is_expected.to eq(false) }
end
end
end
...@@ -33,4 +33,17 @@ describe('UserCallout', function () { ...@@ -33,4 +33,17 @@ describe('UserCallout', function () {
this.userCalloutBtn.click(); this.userCalloutBtn.click();
expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true'); expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true');
}); });
describe('Sets cookie with setCalloutPerProject', () => {
beforeEach(() => {
spyOn(Cookies, 'set').and.callFake(() => {});
document.querySelector('.user-callout').setAttribute('data-project-path', 'foo/bar');
this.userCallout = new UserCallout({ setCalloutPerProject: true });
});
it('sets a cookie when the user clicks the close button', () => {
this.userCalloutBtn.click();
expect(Cookies.set).toHaveBeenCalledWith('user_callout_dismissed', 'true', Object({ expires: 365, path: 'foo/bar' }));
});
});
}); });
...@@ -2607,6 +2607,50 @@ describe Project do ...@@ -2607,6 +2607,50 @@ describe Project do
end end
end end
describe '#has_auto_devops_implicitly_disabled?' do
set(:project) { create(:project) }
context 'when enabled in settings' do
before do
stub_application_setting(auto_devops_enabled: true)
end
it 'does not have auto devops implicitly disabled' do
expect(project).not_to have_auto_devops_implicitly_disabled
end
end
context 'when disabled in settings' do
before do
stub_application_setting(auto_devops_enabled: false)
end
it 'auto devops is implicitly disabled' do
expect(project).to have_auto_devops_implicitly_disabled
end
context 'when explicitly disabled' do
before do
create(:project_auto_devops, project: project, enabled: false)
end
it 'does not have auto devops implicitly disabled' do
expect(project).not_to have_auto_devops_implicitly_disabled
end
end
context 'when explicitly enabled' do
before do
create(:project_auto_devops, project: project)
end
it 'does not have auto devops implicitly disabled' do
expect(project).not_to have_auto_devops_implicitly_disabled
end
end
end
end
context '#auto_devops_variables' do context '#auto_devops_variables' do
set(:project) { create(:project) } set(:project) { create(:project) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment