Commit 678093c8 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'web-ide-preferred-ci' into 'master'

Make web IDE the preferred editor for .gitlab-ci.yml

See merge request gitlab-org/gitlab!39508
parents a7a15788 52ad6ade
<script>
import { GlAlert, GlButton } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
GlAlert,
GlButton,
},
props: {
dismissEndpoint: {
type: String,
required: true,
},
featureId: {
type: String,
required: true,
},
editPath: {
type: String,
required: true,
},
},
data() {
return {
showAlert: true,
};
},
methods: {
dismissAlert() {
this.showAlert = false;
return axios.post(this.dismissEndpoint, {
feature_name: this.featureId,
});
},
},
};
</script>
<template>
<gl-alert v-if="showAlert" class="gl-mt-5" @dismiss="dismissAlert">
{{ __('The Web IDE offers advanced syntax highlighting capabilities and more.') }}
<div class="gl-mt-5">
<gl-button :href="editPath" category="primary" variant="info">{{
__('Open Web IDE')
}}</gl-button>
</div>
</gl-alert>
</template>
import Vue from 'vue';
import WebIdeAlert from './components/web_ide_alert.vue';
export default el => {
const { dismissEndpoint, featureId, editPath } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
render(createElement) {
return createElement(WebIdeAlert, {
props: {
dismissEndpoint,
featureId,
editPath,
},
});
},
});
};
......@@ -7,12 +7,14 @@ import BlobFileDropzone from '../blob/blob_file_dropzone';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
import { disableButtonIfEmptyField, setCookie } from '~/lib/utils/common_utils';
import Tracking from '~/tracking';
import initWebIdeAlert from '~/blob/suggest_web_ide_ci';
export default () => {
const editBlobForm = $('.js-edit-blob-form');
const uploadBlobForm = $('.js-upload-blob-form');
const deleteBlobForm = $('.js-delete-blob-form');
const suggestEl = document.querySelector('.js-suggest-gitlab-ci-yml');
const alertEl = document.getElementById('js-suggest-web-ide-ci');
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relativeUrlRoot');
......@@ -80,4 +82,8 @@ export default () => {
});
}
}
if (alertEl) {
initWebIdeAlert(alertEl);
}
};
......@@ -41,7 +41,7 @@ module BlobHelper
end
def encode_ide_path(path)
url_encode(path).gsub('%2F', '/')
ERB::Util.url_encode(path).gsub('%2F', '/')
end
def edit_blob_button(project = @project, ref = @ref, path = @path, options = {})
......@@ -375,4 +375,9 @@ module BlobHelper
def human_access
@project.team.human_max_access(current_user&.id).try(:downcase)
end
def editing_ci_config?
@path.to_s.end_with?(Ci::Pipeline::CONFIG_EXTENSION) ||
@path.to_s == @project.ci_config_path_or_default
end
end
......@@ -9,6 +9,7 @@ module UserCalloutsHelper
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
WEBHOOKS_MOVED = 'webhooks_moved'
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
WEB_IDE_ALERT_DISMISSED = 'web_ide_alert_dismissed'
def show_admin_integrations_moved?
!user_dismissed?(ADMIN_INTEGRATIONS_MOVED)
......@@ -50,6 +51,10 @@ module UserCalloutsHelper
customize_homepage && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
end
def show_web_ide_alert?
!user_dismissed?(WEB_IDE_ALERT_DISMISSED)
end
private
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)
......
......@@ -19,6 +19,8 @@ module Ci
PROJECT_ROUTE_AND_NAMESPACE_ROUTE = {
project: [:project_feature, :route, { namespace: :route }]
}.freeze
CONFIG_EXTENSION = '.gitlab-ci.yml'
DEFAULT_CONFIG_PATH = CONFIG_EXTENSION
BridgeStatusError = Class.new(StandardError)
......@@ -647,7 +649,7 @@ module Ci
def config_path
return unless repository_source? || unknown_source?
project.ci_config_path.presence || '.gitlab-ci.yml'
project.ci_config_path_or_default
end
def has_yaml_errors?
......
......@@ -20,6 +20,7 @@ module Enums
webhooks_moved: 13,
service_templates_deprecated: 14,
admin_integrations_moved: 15,
web_ide_alert_dismissed: 16,
personal_access_token_expiry: 21, # EE-only
suggest_pipeline: 22,
customize_homepage: 23
......
......@@ -2518,6 +2518,14 @@ class Project < ApplicationRecord
.exists?
end
def default_branch_or_master
default_branch || 'master'
end
def ci_config_path_or_default
ci_config_path.presence || Ci::Pipeline::DEFAULT_CONFIG_PATH
end
private
def find_service(services, name)
......
......@@ -7,6 +7,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
include StorageHelper
include TreeHelper
include IconsHelper
include BlobHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
......@@ -114,7 +115,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def add_ci_yml_path
add_special_file_path(file_name: '.gitlab-ci.yml')
add_special_file_path(file_name: ci_config_path_or_default)
end
def add_ci_yml_ide_path
ide_edit_path(project, default_branch_or_master, ci_config_path_or_default)
end
def add_readme_path
......@@ -219,7 +224,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
if current_user && can_current_user_push_to_default_branch?
AnchorData.new(false,
statistic_icon + _('New file'),
project_new_blob_path(project, default_branch || 'master'),
project_new_blob_path(project, default_branch_or_master),
'missing')
end
end
......@@ -325,7 +330,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
if cicd_missing?
AnchorData.new(false,
statistic_icon + _('Set up CI/CD'),
add_ci_yml_path)
add_ci_yml_ide_path)
elsif repository.gitlab_ci_yml.present?
AnchorData.new(false,
statistic_icon('doc-text') + _('CI/CD configuration'),
......@@ -397,7 +402,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name }
project_new_blob_path(
project,
project.default_branch || 'master',
default_branch_or_master,
file_name: file_name,
commit_message: commit_message,
branch_name: branch_name
......
......@@ -6,6 +6,10 @@
Someone edited the file the same time you did. Please check out
= link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
and make sure your changes will not unintentionally remove theirs.
- if editing_ci_config? && show_web_ide_alert?
#js-suggest-web-ide-ci{ data: { dismiss_endpoint: user_callouts_path, feature_id: UserCalloutsHelper::WEB_IDE_ALERT_DISMISSED, edit_path: ide_edit_path } }
.editor-title-row
%h3.page-title.blob-edit-page-title
Edit file
......
---
title: Add alert when editing .gitlab-ci.yml
merge_request: 39508
author:
type: added
......@@ -17131,6 +17131,9 @@ msgstr ""
msgid "Open Selection"
msgstr ""
msgid "Open Web IDE"
msgstr ""
msgid "Open comment type dropdown"
msgstr ""
......@@ -24336,6 +24339,9 @@ msgstr ""
msgid "The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., \"http://localhost:9200, http://localhost:9201\")."
msgstr ""
msgid "The Web IDE offers advanced syntax highlighting capabilities and more."
msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
......
......@@ -226,7 +226,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
expect(project.repository.gitlab_ci_yml).to be_nil
page.within('.project-buttons') do
expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path)
expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_ide_path)
end
end
......
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMount } from '@vue/test-utils';
import { GlButton, GlAlert } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import WebIdeAlert from '~/blob/suggest_web_ide_ci/components/web_ide_alert.vue';
const dismissEndpoint = '/-/user_callouts';
const featureId = 'web_ide_alert_dismissed';
const editPath = 'edit/master/-/.gitlab-ci.yml';
describe('WebIdeAlert', () => {
let wrapper;
let mock;
const findButton = () => wrapper.find(GlButton);
const findAlert = () => wrapper.find(GlAlert);
const dismissAlert = alertWrapper => alertWrapper.vm.$emit('dismiss');
const getPostPayload = () => JSON.parse(mock.history.post[0].data);
const createComponent = () => {
wrapper = shallowMount(WebIdeAlert, {
propsData: {
dismissEndpoint,
featureId,
editPath,
},
});
};
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onPost(dismissEndpoint).reply(200);
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
mock.restore();
});
describe('with defaults', () => {
it('displays alert correctly', () => {
expect(findAlert().exists()).toBe(true);
});
it('web ide button link has correct path', () => {
expect(findButton().attributes('href')).toBe(editPath);
});
it('dismisses alert correctly', async () => {
const alertWrapper = findAlert();
dismissAlert(alertWrapper);
await waitForPromises();
expect(alertWrapper.exists()).toBe(false);
expect(mock.history.post).toHaveLength(1);
expect(getPostPayload()).toEqual({ feature_name: featureId });
});
});
});
......@@ -481,4 +481,59 @@ RSpec.describe BlobHelper do
end
end
end
describe '#editing_ci_config?' do
let(:project) { build(:project) }
subject { helper.editing_ci_config? }
before do
assign(:project, project)
assign(:path, path)
end
context 'when path is nil' do
let(:path) { nil }
it { is_expected.to be_falsey }
end
context 'when path is not a ci file' do
let(:path) { 'some-file.txt' }
it { is_expected.to be_falsey }
end
context 'when path ends is gitlab-ci.yml' do
let(:path) { '.gitlab-ci.yml' }
it { is_expected.to be_truthy }
end
context 'when path ends with gitlab-ci.yml' do
let(:path) { 'template.gitlab-ci.yml' }
it { is_expected.to be_truthy }
end
context 'with custom ci paths' do
let(:path) { 'path/to/ci.yaml' }
before do
project.ci_config_path = 'path/to/ci.yaml'
end
it { is_expected.to be_truthy }
end
context 'with custom ci config and path' do
let(:path) { 'path/to/template.gitlab-ci.yml' }
before do
project.ci_config_path = 'ci/path/.gitlab-ci.yml@another-group/another-project'
end
it { is_expected.to be_truthy }
end
end
end
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