diff options
99 files changed, 1574 insertions, 317 deletions
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js index 84e5bb3c46e..aee67899ca2 100644 --- a/app/assets/javascripts/pages/projects/blob/show/index.js +++ b/app/assets/javascripts/pages/projects/blob/show/index.js @@ -3,6 +3,7 @@ import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_sta import BlobViewer from '~/blob/viewer/index'; import initBlob from '~/pages/projects/init_blob'; import GpgBadges from '~/gpg_badges'; +import '~/sourcegraph/load'; document.addEventListener('DOMContentLoaded', () => { new BlobViewer(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js index 5aa4734244e..0eb6f231839 100644 --- a/app/assets/javascripts/pages/projects/commit/show/index.js +++ b/app/assets/javascripts/pages/projects/commit/show/index.js @@ -9,6 +9,7 @@ import initNotes from '~/init_notes'; import initChangesDropdown from '~/init_changes_dropdown'; import initDiffNotes from '~/diff_notes/diff_notes_bundle'; import { fetchCommitMergeRequests } from '~/commit_merge_requests'; +import '~/sourcegraph/load'; document.addEventListener('DOMContentLoaded', () => { const hasPerfBar = document.querySelector('.with-performance-bar'); diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js index fa1de1f13cb..16034313af2 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js @@ -5,6 +5,7 @@ import { handleLocationHash } from '~/lib/utils/common_utils'; import howToMerge from '~/how_to_merge'; import initPipelines from '~/commit/pipelines/pipelines_bundle'; import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle'; +import initSourcegraph from '~/sourcegraph'; import initWidget from '../../../vue_merge_request_widget'; export default function() { @@ -19,4 +20,5 @@ export default function() { handleLocationHash(); howToMerge(); initWidget(); + initSourcegraph(); } diff --git a/app/assets/javascripts/pages/projects/pages_domains/form.js b/app/assets/javascripts/pages/projects/pages_domains/form.js index cef8e92610c..ae5368179b1 100644 --- a/app/assets/javascripts/pages/projects/pages_domains/form.js +++ b/app/assets/javascripts/pages/projects/pages_domains/form.js @@ -1,17 +1,23 @@ import setupToggleButtons from '~/toggle_buttons'; +function updateVisibility(selector, isVisible) { + Array.from(document.querySelectorAll(selector)).forEach(el => { + if (isVisible) { + el.classList.remove('d-none'); + } else { + el.classList.add('d-none'); + } + }); +} + export default () => { const toggleContainer = document.querySelector('.js-auto-ssl-toggle-container'); if (toggleContainer) { const onToggleButtonClicked = isAutoSslEnabled => { - Array.from(document.querySelectorAll('.js-shown-unless-auto-ssl')).forEach(el => { - if (isAutoSslEnabled) { - el.classList.add('d-none'); - } else { - el.classList.remove('d-none'); - } - }); + updateVisibility('.js-shown-unless-auto-ssl', !isAutoSslEnabled); + + updateVisibility('.js-shown-if-auto-ssl', isAutoSslEnabled); Array.from(document.querySelectorAll('.js-enabled-unless-auto-ssl')).forEach(el => { if (isAutoSslEnabled) { diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue index 688baa93b6d..96177512e35 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue @@ -64,14 +64,14 @@ export default { v-for="(testSuite, index) in getTestSuites" :key="index" role="row" - class="gl-responsive-table-row test-reports-summary-row rounded cursor-pointer js-suite-row" + class="gl-responsive-table-row gl-responsive-table-row-clickable test-reports-summary-row rounded cursor-pointer js-suite-row" @click="tableRowClick(testSuite)" > <div class="table-section section-25"> <div role="rowheader" class="table-mobile-header font-weight-bold"> {{ __('Suite') }} </div> - <div class="table-mobile-content test-reports-summary-suite cgray pl-3"> + <div class="table-mobile-content underline cgray pl-3"> {{ testSuite.name }} </div> </div> diff --git a/app/assets/javascripts/releases/detail/index.js b/app/assets/javascripts/releases/detail/index.js index 3da971e6d90..0dab90a1ede 100644 --- a/app/assets/javascripts/releases/detail/index.js +++ b/app/assets/javascripts/releases/detail/index.js @@ -5,7 +5,7 @@ import createStore from './store'; export default () => { const el = document.getElementById('js-edit-release-page'); - const store = createStore(el.dataset); + const store = createStore(); store.dispatch('setInitialState', el.dataset); return new Vue({ diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue index 5a89efa4538..26c1f5813f5 100644 --- a/app/assets/javascripts/repository/components/last_commit.vue +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -109,7 +109,7 @@ export default { class="text-expander" @click="toggleShowDescription" > - <icon name="ellipsis_h" /> + <icon name="ellipsis_h" :size="10" /> </gl-button> <div class="committer"> <gl-link @@ -124,7 +124,7 @@ export default { </div> <pre v-if="commit.description" - v-show="showDescription" + :class="{ 'd-block': showDescription }" class="commit-row-description append-bottom-8" > {{ commit.description }} diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js index 112f7d1a30e..87310278b9e 100644 --- a/app/assets/javascripts/repository/log_tree.js +++ b/app/assets/javascripts/repository/log_tree.js @@ -1,4 +1,5 @@ import axios from '~/lib/utils/axios_utils'; +import { normalizeData } from 'ee_else_ce/repository/utils/commit'; import getCommits from './queries/getCommits.query.graphql'; import getProjectPath from './queries/getProjectPath.query.graphql'; import getRef from './queries/getRef.query.graphql'; @@ -6,19 +7,6 @@ import getRef from './queries/getRef.query.graphql'; let fetchpromise; let resolvers = []; -export function normalizeData(data) { - return data.map(d => ({ - sha: d.commit.id, - message: d.commit.message, - committedDate: d.commit.committed_date, - commitPath: d.commit_path, - fileName: d.file_name, - type: d.type, - lockLabel: d.lock_label, - __typename: 'LogTreeCommit', - })); -} - export function resolveCommit(commits, { resolve, entry }) { const commit = commits.find(c => c.fileName === entry.name && c.type === entry.type); diff --git a/app/assets/javascripts/repository/queries/commit.fragment.graphql b/app/assets/javascripts/repository/queries/commit.fragment.graphql new file mode 100644 index 00000000000..9bb13c475c7 --- /dev/null +++ b/app/assets/javascripts/repository/queries/commit.fragment.graphql @@ -0,0 +1,8 @@ +fragment TreeEntryCommit on LogTreeCommit { + sha + message + committedDate + commitPath + fileName + type +} diff --git a/app/assets/javascripts/repository/queries/getCommit.query.graphql b/app/assets/javascripts/repository/queries/getCommit.query.graphql index 65d750884a8..e4aeaaff8fe 100644 --- a/app/assets/javascripts/repository/queries/getCommit.query.graphql +++ b/app/assets/javascripts/repository/queries/getCommit.query.graphql @@ -1,11 +1,7 @@ +#import "ee_else_ce/repository/queries/commit.fragment.graphql" + query getCommit($fileName: String!, $type: String!, $path: String!) { commit(path: $path, fileName: $fileName, type: $type) @client { - sha - message - committedDate - commitPath - fileName - type - lockLabel + ...TreeEntryCommit } } diff --git a/app/assets/javascripts/repository/queries/getCommits.query.graphql b/app/assets/javascripts/repository/queries/getCommits.query.graphql index 780a24d2663..0976b8f32d7 100644 --- a/app/assets/javascripts/repository/queries/getCommits.query.graphql +++ b/app/assets/javascripts/repository/queries/getCommits.query.graphql @@ -1,11 +1,7 @@ +#import "ee_else_ce/repository/queries/commit.fragment.graphql" + query getCommits { commits @client { - sha - message - committedDate - commitPath - fileName - type - lockLabel + ...TreeEntryCommit } } diff --git a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql index 74ccdd79dd0..4bb959a8001 100644 --- a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql +++ b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql @@ -5,7 +5,7 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) { lastCommit { sha title - message + description webUrl authoredDate author { diff --git a/app/assets/javascripts/repository/utils/commit.js b/app/assets/javascripts/repository/utils/commit.js new file mode 100644 index 00000000000..6c204b57b37 --- /dev/null +++ b/app/assets/javascripts/repository/utils/commit.js @@ -0,0 +1,13 @@ +// eslint-disable-next-line import/prefer-default-export +export function normalizeData(data, extra = () => {}) { + return data.map(d => ({ + sha: d.commit.id, + message: d.commit.message, + committedDate: d.commit.committed_date, + commitPath: d.commit_path, + fileName: d.file_name, + type: d.type, + __typename: 'LogTreeCommit', + ...extra(d), + })); +} diff --git a/app/assets/javascripts/sourcegraph/index.js b/app/assets/javascripts/sourcegraph/index.js new file mode 100644 index 00000000000..796e90bf08e --- /dev/null +++ b/app/assets/javascripts/sourcegraph/index.js @@ -0,0 +1,28 @@ +function loadScript(path) { + const script = document.createElement('script'); + script.type = 'application/javascript'; + script.src = path; + script.defer = true; + document.head.appendChild(script); +} + +/** + * Loads the Sourcegraph integration for support for Sourcegraph extensions and + * code intelligence. + */ +export default function initSourcegraph() { + const { url } = gon.sourcegraph || {}; + + if (!url) { + return; + } + + const assetsUrl = new URL('/assets/webpack/sourcegraph/', window.location.href); + const scriptPath = new URL('scripts/integration.bundle.js', assetsUrl).href; + + window.SOURCEGRAPH_ASSETS_URL = assetsUrl.href; + window.SOURCEGRAPH_URL = url; + window.SOURCEGRAPH_INTEGRATION = 'gitlab-integration'; + + loadScript(scriptPath); +} diff --git a/app/assets/javascripts/sourcegraph/load.js b/app/assets/javascripts/sourcegraph/load.js new file mode 100644 index 00000000000..f9491505d42 --- /dev/null +++ b/app/assets/javascripts/sourcegraph/load.js @@ -0,0 +1,6 @@ +import initSourcegraph from './index'; + +/** + * Load sourcegraph in it's own listener so that it's isolated from failures. + */ +document.addEventListener('DOMContentLoaded', initSourcegraph); diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 7596dbf1229..af4ac024e4f 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -168,7 +168,7 @@ export default { :prepend="true" tag="* [ ] " :button-title="__('Add a task list')" - icon="task-done" + icon="list-task" /> <toolbar-button :tag="mdTable" diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index f4e7e4e456b..31ea59df4c5 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -572,3 +572,10 @@ img.emoji { .gl-font-size-20 { font-size: $gl-font-size-20; } .gl-font-size-28 { font-size: $gl-font-size-28; } .gl-font-size-42 { font-size: $gl-font-size-42; } + +.border-section { + @include gl-py-6; + @include gl-m-0; + + border-top: 1px solid $border-color; +} diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss index fd6f80e26cb..1878fac1c60 100644 --- a/app/assets/stylesheets/framework/responsive_tables.scss +++ b/app/assets/stylesheets/framework/responsive_tables.scss @@ -20,6 +20,17 @@ @extend .gl-responsive-table-row-layout; margin-top: 10px; border: 1px solid $border-color; + color: $gray-700; + + &.gl-responsive-table-row-clickable { + &:hover { + background-color: $gray-light; + + .underline { + text-decoration: underline; + } + } + } @include media-breakpoint-up(md) { margin: 0; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index faf669829e1..364fe3da71e 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -1084,18 +1084,6 @@ button.mini-pipeline-graph-dropdown-toggle { } .test-reports-table { - color: $gray-700; - - .test-reports-summary-row { - &:hover { - background-color: $gray-light; - - .test-reports-summary-suite { - text-decoration: underline; - } - } - } - .build-trace { @include build-trace(); } diff --git a/app/controllers/concerns/sourcegraph_gon.rb b/app/controllers/concerns/sourcegraph_gon.rb new file mode 100644 index 00000000000..01925cf9d4d --- /dev/null +++ b/app/controllers/concerns/sourcegraph_gon.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module SourcegraphGon + extend ActiveSupport::Concern + + included do + before_action :push_sourcegraph_gon, if: :html_request? + end + + private + + def push_sourcegraph_gon + return unless sourcegraph_enabled? + + gon.push({ + sourcegraph: { url: Gitlab::CurrentSettings.sourcegraph_url } + }) + end + + def sourcegraph_enabled? + Gitlab::CurrentSettings.sourcegraph_enabled && sourcegraph_enabled_for_project? && current_user&.sourcegraph_enabled + end + + def sourcegraph_enabled_for_project? + return false unless project && Gitlab::Sourcegraph.feature_enabled?(project) + return project.public? if Gitlab::CurrentSettings.sourcegraph_public_only + + true + end +end diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 42d4d785174..214640a5295 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -47,7 +47,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController :preferred_language, :time_display_relative, :time_format_in_24h, - :show_whitespace_in_diffs + :show_whitespace_in_diffs, + :sourcegraph_enabled ] end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 205ec288ce9..7c97f771a70 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -8,6 +8,8 @@ class Projects::BlobController < Projects::ApplicationController include NotesHelper include ActionView::Helpers::SanitizeHelper include RedirectsForMissingPathOnTree + include SourcegraphGon + prepend_before_action :authenticate_user!, only: [:edit] around_action :allow_gitaly_ref_name_caching, only: [:show] diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 939a09d4fd2..afb670b687b 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -8,6 +8,7 @@ class Projects::CommitController < Projects::ApplicationController include CreatesCommit include DiffForPath include DiffHelper + include SourcegraphGon # Authorize before_action :require_non_empty_project diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 59987da21ec..766ec1e33f3 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -9,6 +9,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo include ToggleAwardEmoji include IssuableCollections include RecordUserLastActivity + include SourcegraphGon skip_before_action :merge_request, only: [:index, :bulk_update] before_action :whitelist_query_limiting, only: [:assign_related_issues, :update] diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb index 50a47e98893..8f40eac5186 100644 --- a/app/controllers/projects/pages_domains_controller.rb +++ b/app/controllers/projects/pages_domains_controller.rb @@ -8,6 +8,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController before_action :domain, except: [:new, :create] def show + redirect_to edit_project_pages_domain_path(@project, @domain) end def new @@ -23,7 +24,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController flash[:alert] = 'Failed to verify domain ownership' end - redirect_to project_pages_domain_path(@project, @domain) + redirect_to edit_project_pages_domain_path(@project, @domain) end def edit @@ -33,7 +34,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController @domain = @project.pages_domains.create(create_params) if @domain.valid? - redirect_to project_pages_domain_path(@project, @domain) + redirect_to edit_project_pages_domain_path(@project, @domain) else render 'new' end @@ -77,7 +78,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController end def update_params - params.require(:pages_domain).permit(:user_provided_key, :user_provided_certificate, :auto_ssl_enabled) + params.fetch(:pages_domain, {}).permit(:user_provided_key, :user_provided_certificate, :auto_ssl_enabled) end def domain diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index f0868a8d377..a011209375e 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -259,6 +259,9 @@ module ApplicationSettingsHelper :shared_runners_text, :sign_in_text, :signup_enabled, + :sourcegraph_enabled, + :sourcegraph_url, + :sourcegraph_public_only, :terminal_max_session_time, :terms, :throttle_authenticated_api_enabled, diff --git a/app/helpers/sourcegraph_helper.rb b/app/helpers/sourcegraph_helper.rb new file mode 100644 index 00000000000..cc5a5c77e9a --- /dev/null +++ b/app/helpers/sourcegraph_helper.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module SourcegraphHelper + def sourcegraph_url_message + link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: Gitlab::CurrentSettings.sourcegraph_url } + link_end = "#{sprite_icon('external-link', size: 12, css_class: 'ml-1 vertical-align-center')}</a>".html_safe + + message = + if Gitlab::CurrentSettings.sourcegraph_url_is_com? + s_('SourcegraphPreferences|Uses %{link_start}Sourcegraph.com%{link_end}.').html_safe + else + s_('SourcegraphPreferences|Uses a custom %{link_start}Sourcegraph instance%{link_end}.').html_safe + end + + message % { link_start: link_start, link_end: link_end } + end + + def sourcegraph_experimental_message + if Gitlab::Sourcegraph.feature_conditional? + s_("SourcegraphPreferences|This feature is experimental and currently limited to certain projects.") + elsif Gitlab::CurrentSettings.sourcegraph_public_only + s_("SourcegraphPreferences|This feature is experimental and limited to public projects.") + else + s_("SourcegraphPreferences|This feature is experimental.") + end + end +end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index b47e1142cca..4028d711fd1 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -99,6 +99,10 @@ class ApplicationSetting < ApplicationRecord presence: true, if: :plantuml_enabled + validates :sourcegraph_url, + presence: true, + if: :sourcegraph_enabled + validates :snowplow_collector_hostname, presence: true, hostname: true, @@ -343,6 +347,10 @@ class ApplicationSetting < ApplicationRecord end after_commit :expire_performance_bar_allowed_user_ids_cache, if: -> { previous_changes.key?('performance_bar_allowed_group_id') } + def sourcegraph_url_is_com? + !!(sourcegraph_url =~ /\Ahttps:\/\/(www\.)?sourcegraph\.com/) + end + def self.create_from_defaults transaction(requires_new: true) do super diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 80715fae68d..7bb89f0d1e2 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -102,6 +102,9 @@ module ApplicationSettingImplementation shared_runners_text: nil, sign_in_text: nil, signup_enabled: Settings.gitlab['signup_enabled'], + sourcegraph_enabled: false, + sourcegraph_url: nil, + sourcegraph_public_only: true, terminal_max_session_time: 0, throttle_authenticated_api_enabled: false, throttle_authenticated_api_period_in_seconds: 3600, diff --git a/app/models/user.rb b/app/models/user.rb index f704589ad80..d0e758b0055 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -240,6 +240,7 @@ class User < ApplicationRecord delegate :time_display_relative, :time_display_relative=, to: :user_preference delegate :time_format_in_24h, :time_format_in_24h=, to: :user_preference delegate :show_whitespace_in_diffs, :show_whitespace_in_diffs=, to: :user_preference + delegate :sourcegraph_enabled, :sourcegraph_enabled=, to: :user_preference delegate :setup_for_company, :setup_for_company=, to: :user_preference accepts_nested_attributes_for :user_preference, update_only: true diff --git a/app/services/concerns/git/logger.rb b/app/services/concerns/git/logger.rb new file mode 100644 index 00000000000..7c036212e66 --- /dev/null +++ b/app/services/concerns/git/logger.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Git + module Logger + def log_error(message, save_message_on_model: false) + Gitlab::GitLogger.error("#{self.class.name} error (#{merge_request.to_reference(full: true)}): #{message}") + merge_request.update(merge_error: message) if save_message_on_model + end + end +end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index aacc3d6831e..00bf69739ad 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -29,6 +29,19 @@ module MergeRequests .execute_for_merge_request(merge_request) end + def source_project + @source_project ||= merge_request.source_project + end + + def target_project + @target_project ||= merge_request.target_project + end + + # Don't try to print expensive instance variables. + def inspect + "#<#{self.class} #{merge_request.to_reference(full: true)}>" + end + private def create(merge_request) diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb index 4d36dd4feae..7e9442c0c7c 100644 --- a/app/services/merge_requests/rebase_service.rb +++ b/app/services/merge_requests/rebase_service.rb @@ -1,9 +1,13 @@ # frozen_string_literal: true module MergeRequests - class RebaseService < MergeRequests::WorkingCopyBaseService + class RebaseService < MergeRequests::BaseService + include Git::Logger + REBASE_ERROR = 'Rebase failed. Please rebase locally' + attr_reader :merge_request + def execute(merge_request) @merge_request = merge_request diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb index 88ca3b4f5a8..d25997c925e 100644 --- a/app/services/merge_requests/squash_service.rb +++ b/app/services/merge_requests/squash_service.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true module MergeRequests - class SquashService < MergeRequests::WorkingCopyBaseService + class SquashService < MergeRequests::BaseService + include Git::Logger + def execute # If performing a squash would result in no change, then # immediately return a success message without performing a squash diff --git a/app/services/merge_requests/working_copy_base_service.rb b/app/services/merge_requests/working_copy_base_service.rb deleted file mode 100644 index 2d2be1f4c25..00000000000 --- a/app/services/merge_requests/working_copy_base_service.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module MergeRequests - class WorkingCopyBaseService < MergeRequests::BaseService - attr_reader :merge_request - - def source_project - @source_project ||= merge_request.source_project - end - - def target_project - @target_project ||= merge_request.target_project - end - - def log_error(message, save_message_on_model: false) - Gitlab::GitLogger.error("#{self.class.name} error (#{merge_request.to_reference(full: true)}): #{message}") - - merge_request.update(merge_error: message) if save_message_on_model - end - - # Don't try to print expensive instance variables. - def inspect - "#<#{self.class} #{merge_request.to_reference(full: true)}>" - end - end -end diff --git a/app/views/admin/application_settings/_sourcegraph.html.haml b/app/views/admin/application_settings/_sourcegraph.html.haml new file mode 100644 index 00000000000..23cda0334a2 --- /dev/null +++ b/app/views/admin/application_settings/_sourcegraph.html.haml @@ -0,0 +1,38 @@ +- return unless Gitlab::Sourcegraph.feature_available? +- expanded = integration_expanded?('sourcegraph_') + +%section.settings.as-sourcegraph.no-animate#js-sourcegraph-settings{ class: ('expanded' if expanded) } + .settings-header + %h4 + = _('Sourcegraph') + %button.btn.btn-default.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p + - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: 'https://sourcegraph.com/' } + - link_end = "#{sprite_icon('external-link', size: 12, css_class: 'ml-1 vertical-align-center')}</a>".html_safe + = s_('SourcegraphAdmin|Enable code intelligence powered by %{link_start}Sourcegraph%{link_end} on your GitLab instance\'s code views and merge requests.').html_safe % { link_start: link_start, link_end: link_end } + %span + = link_to s_('SourcegraphAdmin|More information'), help_page_path('integration/sourcegraph.md'), target: '_blank' + + + .settings-content + = form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-sourcegraph-settings'), html: { class: 'fieldset-form' } do |f| + = form_errors(@application_setting) + + %fieldset + .form-group + .form-check + = f.check_box :sourcegraph_enabled, class: 'form-check-input' + = f.label :sourcegraph_enabled, s_('SourcegraphAdmin|Enable Sourcegraph'), class: 'form-check-label' + .form-group + .form-check + = f.check_box :sourcegraph_public_only, class: 'form-check-input' + = f.label :sourcegraph_public_only, s_('SourcegraphAdmin|Block on private and internal projects'), class: 'form-check-label' + .form-text.text-muted + = s_('SourcegraphAdmin|If checked, only public projects will have code intelligence and communicate with Sourcegraph.') + .form-group + = f.label :sourcegraph_url, s_('SourcegraphAdmin|Sourcegraph URL'), class: 'label-bold' + = f.text_field :sourcegraph_url, class: 'form-control', placeholder: s_('SourcegraphAdmin|e.g. https://sourcegraph.example.com') + .form-text.text-muted + = s_('SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects.') + = f.submit s_('SourcegraphAdmin|Save changes'), class: 'btn btn-success' diff --git a/app/views/admin/application_settings/integrations.html.haml b/app/views/admin/application_settings/integrations.html.haml index 3f459e0f491..0aa833e49a8 100644 --- a/app/views/admin/application_settings/integrations.html.haml +++ b/app/views/admin/application_settings/integrations.html.haml @@ -4,6 +4,7 @@ = render_if_exists 'admin/application_settings/elasticsearch_form' = render 'admin/application_settings/plantuml' += render 'admin/application_settings/sourcegraph' = render_if_exists 'admin/application_settings/slack' = render 'admin/application_settings/third_party_offers' = render 'admin/application_settings/snowplow' diff --git a/app/views/profiles/preferences/_sourcegraph.html.haml b/app/views/profiles/preferences/_sourcegraph.html.haml new file mode 100644 index 00000000000..20a904694ca --- /dev/null +++ b/app/views/profiles/preferences/_sourcegraph.html.haml @@ -0,0 +1,26 @@ +- return unless Gitlab::Sourcegraph::feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled +- sourcegraph_url = Gitlab::CurrentSettings.sourcegraph_url + +.col-sm-12 + %hr + +.col-lg-4.profile-settings-sidebar + %h4.prepend-top-0 + = s_('Preferences|Integrations') + %p + = s_('Preferences|Customize integrations with third party services.') + = succeed '.' do + = link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'integrations'), target: '_blank' +.col-lg-8 + %label.label-bold + = s_('Preferences|Sourcegraph') + = link_to icon('question-circle'), help_page_path('user/profile/preferences.md', anchor: 'sourcegraph'), target: '_blank', class: 'has-tooltip', title: _('More information') + .form-group.form-check + = f.check_box :sourcegraph_enabled, class: 'form-check-input' + = f.label :sourcegraph_enabled, class: 'form-check-label' do + - link_start = '<a href="%{url}">'.html_safe % { url: sourcegraph_url } + - link_end = '</a>'.html_safe + = s_('Preferences|Enable integrated code intelligence on code views').html_safe % { link_start: link_start, link_end: link_end } + .form-text.text-muted + = sourcegraph_url_message + = sourcegraph_experimental_message diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 84657592cd8..bf76b7379dd 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -111,6 +111,9 @@ = time_display_label .form-text.text-muted = s_('Preferences|For example: 30 mins ago.') + + = render 'sourcegraph', f: f + .col-lg-4.profile-settings-sidebar .col-lg-8 .form-group diff --git a/app/views/projects/blob/_markdown_buttons.html.haml b/app/views/projects/blob/_markdown_buttons.html.haml index 28d1ff97825..44ec2fa69cb 100644 --- a/app/views/projects/blob/_markdown_buttons.html.haml +++ b/app/views/projects/blob/_markdown_buttons.html.haml @@ -6,7 +6,7 @@ = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: _("Add a link") }) = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: _("Add a bullet list") }) = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: _("Add a numbered list") }) - = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: _("Add a task list") }) + = markdown_toolbar_button({ icon: "list-task", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: _("Add a task list") }) = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: _("Add a table") }) - if show_fullscreen_button %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: _("Go full screen"), data: { container: "body" } } diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index b05491f2c6e..4676c7399f1 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -21,11 +21,11 @@ %span.badge.badge-danger = s_('GitLabPages|Expired') %div - = link_to s_('GitLabPages|Details'), project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped" + = link_to s_('GitLabPages|Edit'), edit_project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped btn-success btn-inverted" = link_to s_('GitLabPages|Remove'), project_pages_domain_path(@project, domain), data: { confirm: s_('GitLabPages|Are you sure?')}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" - if verification_enabled && domain.unverified? %li.list-group-item.bs-callout-warning - - details_link_start = "<a href='#{project_pages_domain_path(@project, domain)}'>".html_safe + - details_link_start = "<a href='#{edit_project_pages_domain_path(@project, domain)}'>".html_safe - details_link_end = '</a>'.html_safe = s_('GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}.').html_safe % { domain: domain.domain, link_start: details_link_start, diff --git a/app/views/projects/pages_domains/_certificate.html.haml b/app/views/projects/pages_domains/_certificate.html.haml index 42631fca5e8..92d30e0b056 100644 --- a/app/views/projects/pages_domains/_certificate.html.haml +++ b/app/views/projects/pages_domains/_certificate.html.haml @@ -1,18 +1,63 @@ -- if @domain.auto_ssl_enabled? - - if @domain.enabled? - - if @domain.certificate_text - %pre - = @domain.certificate_text - - else - .bs-callout.bs-callout-info - = _("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.") +- auto_ssl_available = ::Gitlab::LetsEncrypt.enabled? +- auto_ssl_enabled = @domain.auto_ssl_enabled? +- auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled +- has_user_defined_certificate = @domain.certificate && @domain.certificate_user_provided? + +- if auto_ssl_available + .form-group.border-section + .row + .col-sm-2 + = _('Certificate') + .col-sm-10.js-auto-ssl-toggle-container + %label{ for: "pages_domain_auto_ssl_enabled_button" } + - lets_encrypt_link_url = "https://letsencrypt.org/" + - lets_encrypt_link_start = "<a href=\"%{lets_encrypt_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { lets_encrypt_link_url: lets_encrypt_link_url } + - lets_encrypt_link_end = "</a>".html_safe + = _("Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: lets_encrypt_link_end } + %button{ type: "button", id: "pages_domain_auto_ssl_enabled_button", + class: "js-project-feature-toggle project-feature-toggle mt-2 #{"is-checked" if auto_ssl_available_and_enabled}", + "aria-label": _("Automatic certificate management using Let's Encrypt") } + = f.hidden_field :auto_ssl_enabled?, class: "js-project-feature-toggle-input" + %span.toggle-icon + = sprite_icon("status_success_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-checked") + = sprite_icon("status_failed_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-unchecked") + %p.text-secondary.mt-3 + - docs_link_url = help_page_path("user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md") + - docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url } + - docs_link_end = "</a>".html_safe + = _("Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end } + +.form-group.border-section.js-shown-unless-auto-ssl{ class: ("d-none" if auto_ssl_available_and_enabled) } + - if has_user_defined_certificate + .row + .col-sm-10.offset-sm-2 + .card + .card-header + = _('Certificate') + .d-flex.justify-content-between.align-items-center.p-3 + %span + = @domain.subject || _('missing') + = link_to _('Remove'), + clean_certificate_project_pages_domain_path(@project, @domain), + data: { confirm: _('Are you sure?') }, + class: 'btn btn-remove btn-sm', + method: :delete - else - .bs-callout.bs-callout-warning - = _("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.") -- else - - if @domain.certificate_text - %pre - = @domain.certificate_text - - else - .light - = _("missing") + .row + .col-sm-10.offset-sm-2 + = f.label :user_provided_certificate, _("Certificate (PEM)") + = f.text_area :user_provided_certificate, + rows: 5, + class: "form-control js-enabled-unless-auto-ssl", + disabled: auto_ssl_available_and_enabled + %span.help-inline.text-muted= _("Upload a certificate for your domain with all intermediates") + .row + .col-sm-10.offset-sm-2 + = f.label :user_provided_key, _("Key (PEM)") + = f.text_area :user_provided_key, + rows: 5, + class: "form-control js-enabled-unless-auto-ssl", + disabled: auto_ssl_available_and_enabled + %span.help-inline.text-muted= _("Upload a private key for your certificate") + += render 'lets_encrypt_callout', auto_ssl_available_and_enabled: auto_ssl_available_and_enabled diff --git a/app/views/projects/pages_domains/_dns.html.haml b/app/views/projects/pages_domains/_dns.html.haml new file mode 100644 index 00000000000..e4e590f0a98 --- /dev/null +++ b/app/views/projects/pages_domains/_dns.html.haml @@ -0,0 +1,33 @@ +- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? +- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}." + +.form-group.border-section + .row + .col-sm-2 + = _("DNS") + .col-sm-10 + .input-group + = text_field_tag :domain_dns, dns_record , class: "monospace js-select-on-focus form-control", readonly: true + .input-group-append + = clipboard_button(target: '#domain_dns', class: 'btn-default input-group-text d-none d-sm-block') + %p.form-text.text-muted + = _("To access this domain create a new DNS record") +- if verification_enabled + - verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}" + .form-group.border-section + .row + .col-sm-2 + = _("Verification status") + .col-sm-10 + .status-badge + - text, status = @domain.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success'] + .badge{ class: status } + = text + = link_to sprite_icon("redo"), verify_project_pages_domain_path(@project, @domain), method: :post, class: "btn has-tooltip", title: _("Retry verification") + .input-group + = text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true + .input-group-append + = clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block') + %p.form-text.text-muted + - link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership')) + = _("To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration.").html_safe % { link_to_help: link_to_help } diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml index 4aa1e574d93..e06dab9be06 100644 --- a/app/views/projects/pages_domains/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -3,62 +3,25 @@ - @domain.errors.full_messages.each do |msg| = msg -.form-group.row - .col-sm-2.col-form-label - = f.label :domain, _("Domain") - .col-sm-10 - = f.text_field :domain, required: true, autocomplete: "off", class: "form-control", disabled: @domain.persisted? - -- if Gitlab.config.pages.external_https - - - auto_ssl_available = ::Gitlab::LetsEncrypt.enabled? - - auto_ssl_enabled = @domain.auto_ssl_enabled? - - auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled - - - if auto_ssl_available - .form-group.row - .col-sm-2.col-form-label - %label{ for: "pages_domain_auto_ssl_enabled_button" } - - lets_encrypt_link_url = "https://letsencrypt.org/" - - lets_encrypt_link_start = "<a href=\"%{lets_encrypt_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { lets_encrypt_link_url: lets_encrypt_link_url } - - lets_encrypt_link_end = "</a>".html_safe - = _("Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: lets_encrypt_link_end } - - .col-sm-10.js-auto-ssl-toggle-container - %button{ type: "button", id: "pages_domain_auto_ssl_enabled_button", - class: "js-project-feature-toggle project-feature-toggle mt-2 #{"is-checked" if auto_ssl_available_and_enabled}", - "aria-label": _("Automatic certificate management using Let's Encrypt") } - = f.hidden_field :auto_ssl_enabled?, class: "js-project-feature-toggle-input" - %span.toggle-icon - = sprite_icon("status_success_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-checked") - = sprite_icon("status_failed_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-unchecked") - %p.text-secondary.mt-3 - - docs_link_url = help_page_path("user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md") - - docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url } - - docs_link_end = "</a>".html_safe - = _("Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end } - - .js-shown-unless-auto-ssl{ class: ("d-none" if auto_ssl_available_and_enabled) } - .form-group.row - .col-sm-2.col-form-label - = f.label :user_provided_certificate, _("Certificate (PEM)") +.form-group.border-section + .row + - if @domain.persisted? + .col-sm-2 + = _("Domain") .col-sm-10 - = f.text_area :user_provided_certificate, - rows: 5, - class: "form-control js-enabled-unless-auto-ssl", - disabled: auto_ssl_available_and_enabled - %span.help-inline.text-muted= _("Upload a certificate for your domain with all intermediates") - - .form-group.row - .col-sm-2.col-form-label - = f.label :user_provided_key, _("Key (PEM)") + = external_link(@domain.url, @domain.url) + - else + .col-sm-2 + = f.label :domain, _("Domain") .col-sm-10 - = f.text_area :user_provided_key, - rows: 5, - class: "form-control js-enabled-unless-auto-ssl", - disabled: auto_ssl_available_and_enabled - %span.help-inline.text-muted= _("Upload a private key for your certificate") + .input-group + = f.text_field :domain, required: true, autocomplete: "off", class: "form-control" +- if @domain.persisted? + = render 'dns' + +- if Gitlab.config.pages.external_https + = render 'certificate', f: f - else - .nothing-here-block + .border-section.nothing-here-block = _("Support for custom certificates is disabled. Ask your system's administrator to enable it.") diff --git a/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml b/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml new file mode 100644 index 00000000000..d6406a78fca --- /dev/null +++ b/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml @@ -0,0 +1,13 @@ +- if @domain.enabled? + - if @domain.auto_ssl_enabled && !@domain.certificate + .form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) } + .row + .col-sm-10.offset-sm-2 + .bs-callout.bs-callout-info.mt-0 + = _("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.") +- else + .form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) } + .row + .col-sm-10.offset-sm-2 + .bs-callout.bs-callout-warning.mt-0 + = _("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.") diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml index 7c0777e5496..a08be65d7e4 100644 --- a/app/views/projects/pages_domains/edit.html.haml +++ b/app/views/projects/pages_domains/edit.html.haml @@ -1,12 +1,21 @@ - add_to_breadcrumbs _("Pages"), project_pages_path(@project) - breadcrumb_title @domain.domain - page_title @domain.domain + +- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? + +- if verification_enabled && @domain.unverified? + = content_for :flash_message do + .alert.alert-warning + .container-fluid.container-limited + = _("This domain is not verified. You will need to verify ownership before access is enabled.") + %h3.page-title - = @domain.domain + = _('Pages Domain') = render 'projects/pages_domains/helper_text' -%hr.clearfix %div = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f| = render 'form', { f: f } - .form-actions + .form-actions.d-flex.justify-content-between = f.submit _('Save Changes'), class: "btn btn-success" + = link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-default btn-inverse' diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml index e23ccb5d4c6..3210bfe9231 100644 --- a/app/views/projects/pages_domains/new.html.haml +++ b/app/views/projects/pages_domains/new.html.haml @@ -3,7 +3,6 @@ %h3.page-title = _("New Pages Domain") = render 'projects/pages_domains/helper_text' -%hr.clearfix %div = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f| = render 'form', { f: f } diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml index 33837e21c8d..8eec3d51835 100644 --- a/app/views/projects/pages_domains/show.html.haml +++ b/app/views/projects/pages_domains/show.html.haml @@ -58,4 +58,4 @@ %td = _("Certificate") %td - = render 'certificate' + = render 'lets_encrypt_callout', auto_ssl_available_and_enabled: false diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index a8aae03aad7..0fb23adc31f 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -14,7 +14,6 @@ = render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form -%hr .form-group.row = form.label :title, class: 'col-form-label col-sm-2' diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml index 03eebe7c987..29ac17c43b9 100644 --- a/app/views/shared/issuable/form/_branch_chooser.html.haml +++ b/app/views/shared/issuable/form/_branch_chooser.html.haml @@ -20,3 +20,4 @@ = form.hidden_field(:target_branch, { class: 'target_branch js-target-branch-select ref-name mw-xl', data: { placeholder: _('Select branch'), endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }}) +%hr diff --git a/changelogs/unreleased/30660-allow-to-enable-disable-auto-ssl-letsencrypt-support-via-api.yml b/changelogs/unreleased/30660-allow-to-enable-disable-auto-ssl-letsencrypt-support-via-api.yml new file mode 100644 index 00000000000..7ec23301eeb --- /dev/null +++ b/changelogs/unreleased/30660-allow-to-enable-disable-auto-ssl-letsencrypt-support-via-api.yml @@ -0,0 +1,5 @@ +--- +title: Require explicit null parameters to remove pages domain certificate and allow to use Let's Encrypt certificates through API +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/34426-use-new-list-task-icon-in-text-editor.yml b/changelogs/unreleased/34426-use-new-list-task-icon-in-text-editor.yml new file mode 100644 index 00000000000..2b66bd02e93 --- /dev/null +++ b/changelogs/unreleased/34426-use-new-list-task-icon-in-text-editor.yml @@ -0,0 +1,6 @@ +--- +title: Replace task-done icon with list-task icon to better align with other toolbar + list icons +merge_request: +author: +type: other diff --git a/changelogs/unreleased/35547-add-documentation-for-sign-in-application-setting.yml b/changelogs/unreleased/35547-add-documentation-for-sign-in-application-setting.yml new file mode 100644 index 00000000000..d13fa1d8fc5 --- /dev/null +++ b/changelogs/unreleased/35547-add-documentation-for-sign-in-application-setting.yml @@ -0,0 +1,5 @@ +--- +title: Add documentation for sign-in application setting +merge_request: 19561 +author: Horatiu Eugen Vlad +type: added diff --git a/changelogs/unreleased/remove-domain-details.yml b/changelogs/unreleased/remove-domain-details.yml new file mode 100644 index 00000000000..a9eedd580f6 --- /dev/null +++ b/changelogs/unreleased/remove-domain-details.yml @@ -0,0 +1,5 @@ +--- +title: Merge Details Page and Edit Page for Page Domains +merge_request: 16687 +author: +type: added diff --git a/config/application.rb b/config/application.rb index 1d32ebcaa54..cad5c8bbe76 100644 --- a/config/application.rb +++ b/config/application.rb @@ -255,8 +255,8 @@ module Gitlab caching_config_hash[:compress] = false caching_config_hash[:namespace] = Gitlab::Redis::Cache::CACHE_NAMESPACE caching_config_hash[:expires_in] = 2.weeks # Cache should not grow forever - if Sidekiq.server? # threaded context - caching_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5 + if Sidekiq.server? || defined?(::Puma) # threaded context + caching_config_hash[:pool_size] = Gitlab::Redis::Cache.pool_size caching_config_hash[:pool_timeout] = 1 end diff --git a/config/webpack.config.js b/config/webpack.config.js index 1bcd8b68ac9..9c7a3f42c97 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -300,6 +300,11 @@ module.exports = { to: path.join(ROOT_PATH, 'public/assets/webpack/cmaps/'), }, { + from: path.join(ROOT_PATH, 'node_modules/@sourcegraph/code-host-integration/'), + to: path.join(ROOT_PATH, 'public/assets/webpack/sourcegraph/'), + ignore: ['package.json'], + }, + { from: path.join( ROOT_PATH, 'node_modules/@gitlab/visual-review-tools/dist/visual_review_toolbar.js', diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile index 064b8c94805..da5a63633c3 100644 --- a/danger/commit_messages/Dangerfile +++ b/danger/commit_messages/Dangerfile @@ -86,6 +86,12 @@ def unicode_emoji_regex ))x end +def count_filtered_commits(commits) + commits.count do |commit| + !commit.message.start_with?('fixup!', 'squash!') + end +end + def lint_commit(commit) # rubocop:disable Metrics/AbcSize # For now we'll ignore merge commits, as getting rid of those is a problem # separate from enforcing good commit messages. @@ -285,7 +291,7 @@ def lint_commits(commits) end end -if git.commits.length > 10 && !ce_upstream? +if count_filtered_commits(git.commits) > 10 && !ce_upstream? warn( 'This merge request includes more than 10 commits. ' \ 'Please rebase these commits into a smaller number of commits.' diff --git a/db/migrate/20190827222124_add_sourcegraph_configuration_to_application_settings.rb b/db/migrate/20190827222124_add_sourcegraph_configuration_to_application_settings.rb new file mode 100644 index 00000000000..e624642c2fc --- /dev/null +++ b/db/migrate/20190827222124_add_sourcegraph_configuration_to_application_settings.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddSourcegraphConfigurationToApplicationSettings < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + add_column(:application_settings, :sourcegraph_enabled, :boolean, default: false, null: false) + add_column(:application_settings, :sourcegraph_url, :string, null: true, limit: 255) + end + + def down + remove_column(:application_settings, :sourcegraph_enabled) + remove_column(:application_settings, :sourcegraph_url) + end +end diff --git a/db/migrate/20191107173446_add_sourcegraph_admin_and_user_preferences.rb b/db/migrate/20191107173446_add_sourcegraph_admin_and_user_preferences.rb new file mode 100644 index 00000000000..731ed82c999 --- /dev/null +++ b/db/migrate/20191107173446_add_sourcegraph_admin_and_user_preferences.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddSourcegraphAdminAndUserPreferences < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + add_column(:application_settings, :sourcegraph_public_only, :boolean, default: true, null: false) + add_column(:user_preferences, :sourcegraph_enabled, :boolean) + end + + def down + remove_column(:application_settings, :sourcegraph_public_only) + remove_column(:user_preferences, :sourcegraph_enabled) + end +end diff --git a/db/schema.rb b/db/schema.rb index ebf1eb41499..f217253db5e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -352,6 +352,9 @@ ActiveRecord::Schema.define(version: 2019_11_14_173624) do t.string "snowplow_app_id" t.datetime_with_timezone "productivity_analytics_start_date" t.string "default_ci_config_path", limit: 255 + t.boolean "sourcegraph_enabled", default: false, null: false + t.string "sourcegraph_url", limit: 255 + t.boolean "sourcegraph_public_only", default: true, null: false t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id" t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id" t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id" @@ -3771,6 +3774,7 @@ ActiveRecord::Schema.define(version: 2019_11_14_173624) do t.boolean "time_format_in_24h" t.string "projects_sort", limit: 64 t.boolean "show_whitespace_in_diffs", default: true, null: false + t.boolean "sourcegraph_enabled" t.boolean "setup_for_company" t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true end diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 8980cd2d5bf..f51c375860b 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -120,7 +120,7 @@ The Pages daemon doesn't listen to the outside world. 1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: - ```shell + ```ruby pages_external_url 'http://example.io' ``` @@ -145,7 +145,7 @@ outside world. 1. Place the certificate and key inside `/etc/gitlab/ssl` 1. In `/etc/gitlab/gitlab.rb` specify the following configuration: - ```shell + ```ruby pages_external_url 'https://example.io' pages_nginx['redirect_http_to_https'] = true @@ -167,7 +167,7 @@ behavior: 1. Edit `/etc/gitlab/gitlab.rb`. 1. Set the `inplace_chroot` to `true` for GitLab Pages: - ```shell + ```ruby gitlab_pages['inplace_chroot'] = true ``` @@ -202,7 +202,7 @@ world. Custom domains are supported, but no TLS. 1. Edit `/etc/gitlab/gitlab.rb`: - ```shell + ```ruby pages_external_url "http://example.io" nginx['listen_addresses'] = ['192.0.2.1'] pages_nginx['enable'] = false @@ -233,7 +233,7 @@ world. Custom domains and TLS are supported. 1. Edit `/etc/gitlab/gitlab.rb`: - ```shell + ```ruby pages_external_url "https://example.io" nginx['listen_addresses'] = ['192.0.2.1'] pages_nginx['enable'] = false @@ -332,7 +332,7 @@ Follow the steps below to configure verbose logging of GitLab Pages daemon. If you wish to make it log events with level `DEBUG` you must configure this in `/etc/gitlab/gitlab.rb`: - ```shell + ```ruby gitlab_pages['log_verbose'] = true ``` @@ -347,7 +347,7 @@ are stored. If you wish to store them in another location you must set it up in `/etc/gitlab/gitlab.rb`: - ```shell + ```ruby gitlab_rails['pages_path'] = "/mnt/storage/pages" ``` @@ -363,14 +363,14 @@ Omnibus GitLab 11.1. If you wish to disable it you must configure this in `/etc/gitlab/gitlab.rb`: - ```shell + ```ruby gitlab_pages['listen_proxy'] = nil ``` If you wish to make it listen on a different port you must configure this also in `/etc/gitlab/gitlab.rb`: - ```shell + ```ruby gitlab_pages['listen_proxy'] = "localhost:10080" ``` @@ -382,21 +382,26 @@ The maximum size of the unpacked archive per project can be configured in the Admin area under the Application settings in the **Maximum size of pages (MB)**. The default is 100MB. -## Running GitLab Pages in a separate server +## Running GitLab Pages on a separate server -You may want to run GitLab Pages daemon on a separate server in order to decrease the load on your main application server. -Follow the steps below to configure GitLab Pages in a separate server. +You can run the GitLab Pages daemon on a separate server in order to decrease the load on your main application server. -1. Suppose you have the main GitLab application server named `app1`. Prepare - new Linux server (let's call it `app2`), create NFS share there and configure access to - this share from `app1`. Let's use the default GitLab Pages folder `/var/opt/gitlab/gitlab-rails/shared/pages` - as the shared folder on `app2` and mount it to `/mnt/pages` on `app1`. +To configure GitLab Pages on a separate server: -1. On `app2` install GitLab omnibus and modify `/etc/gitlab/gitlab.rb` this way: +1. Set up a new server. This will become the **Pages server**. - ```shell +1. Create an NFS share on the new server and configure this share to + allow access from your main **GitLab server**. For this example, we use the + default GitLab Pages folder `/var/opt/gitlab/gitlab-rails/shared/pages` + as the shared folder on the new server and we will mount it to `/mnt/pages` + on the **GitLab server**. + +1. On the **Pages server**, install Omnibus GitLab and modify `/etc/gitlab/gitlab.rb` + to include: + + ```ruby external_url 'http://<ip-address-of-the-server>' - pages_external_url "http://<your-pages-domain>" + pages_external_url "http://<your-pages-server-URL>" postgresql['enable'] = false redis['enable'] = false prometheus['enable'] = false @@ -409,20 +414,82 @@ Follow the steps below to configure GitLab Pages in a separate server. gitlab_rails['auto_migrate'] = false ``` -1. Run `sudo gitlab-ctl reconfigure`. -1. On `app1` apply the following changes to `/etc/gitlab/gitlab.rb`: +1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. - ```shell +1. On the **GitLab server**, make the following changes to `/etc/gitlab/gitlab.rb`: + + ```ruby gitlab_pages['enable'] = false - pages_external_url "http://<your-pages-domain>" + pages_external_url "http://<your-pages-server-URL>" gitlab_rails['pages_path'] = "/mnt/pages" ``` -1. Run `sudo gitlab-ctl reconfigure`. +1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. + +It is possible to run GitLab Pages on multiple servers if you wish to distribute +the load. You can do this through standard load balancing practices such as +configuring your DNS server to return multiple IPs for your Pages server, +configuring a load balancer to work at the IP level, and so on. If you wish to +set up GitLab Pages on multiple servers, perform the above procedure for each +Pages server. + +### Access control when running GitLab Pages on a separate server + +If you are [running GitLab Pages on a separate server](#running-gitlab-pages-on-a-separate-server), +then you must use the following procedure to configure [access control](#access-control): + +1. On the **GitLab server**, add the following to `/etc/gitlab/gitlab.rb`: + + ```ruby + gitlab_pages['enable'] = true + gitlab_pages['access_control'] = true + ``` + +1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the + changes to take effect. The `gitlab-secrets.json` file is now updated with the + new configuration. + + DANGER: **Danger:** + The `gitlab-secrets.json` file contains secrets that control database encryption. + Do not edit or replace this file on the **GitLab server** or you might + experience permanent data loss. Make a backup copy of this file before proceeding, + as explained in the following steps. + +1. Create a backup of the secrets file on the **GitLab server**: + + ```shell + cp /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json.bak + ``` + +1. Create a backup of the secrets file on the **Pages server**: + + ```shell + cp /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json.bak + ``` + +1. Disable Pages on the **GitLab server** by setting the following in + `/etc/gitlab/gitlab.rb`: + + ```ruby + gitlab_pages['enable'] = false + ``` + +1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. + +1. Copy the `/etc/gitlab/gitlab-secrets.json` file from the **GitLab server** + to the **Pages server**. + +1. On your **Pages server**, add the following to `/etc/gitlab/gitlab.rb`: + + ```ruby + gitlab_pages['gitlab_server'] = "https://<your-gitlab-server-URL>" + ``` + +1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. ## Backup -Pages are part of the [regular backup][backup] so there is nothing to configure. +GitLab Pages are part of the [regular backup][backup], so there is no separate backup to configure. ## Security diff --git a/doc/api/pages_domains.md b/doc/api/pages_domains.md index 9678203eb40..9d482781cde 100644 --- a/doc/api/pages_domains.md +++ b/doc/api/pages_domains.md @@ -22,6 +22,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap "domain": "ssl.domain.example", "url": "https://ssl.domain.example", "project_id": 1337, + "auto_ssl_enabled": false, "certificate": { "expired": false, "expiration": "2020-04-12T14:32:00.000Z" @@ -55,6 +56,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap { "domain": "ssl.domain.example", "url": "https://ssl.domain.example", + "auto_ssl_enabled": false, "certificate": { "subject": "/O=Example, Inc./OU=Example Origin CA/CN=Example Origin Certificate", "expired": false, @@ -76,7 +78,7 @@ GET /projects/:id/pages/domains/:domain | Attribute | Type | Required | Description | | --------- | -------------- | -------- | ---------------------------------------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `domain` | string | yes | The domain | +| `domain` | string | yes | The custom domain indicated by the user | ```bash curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/pages/domains/www.domain.example @@ -97,6 +99,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap { "domain": "ssl.domain.example", "url": "https://ssl.domain.example", + "auto_ssl_enabled": false, "certificate": { "subject": "/O=Example, Inc./OU=Example Origin CA/CN=Example Origin Certificate", "expired": false, @@ -114,12 +117,13 @@ Creates a new pages domain. The user must have permissions to create new pages d POST /projects/:id/pages/domains ``` -| Attribute | Type | Required | Description | -| ------------- | -------------- | -------- | ---------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `domain` | string | yes | The domain | -| `certificate` | file/string | no | The certificate in PEM format with intermediates following in most specific to least specific order.| -| `key` | file/string | no | The certificate key in PEM format. | +| Attribute | Type | Required | Description | +| -------------------| -------------- | -------- | ---------------------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `domain` | string | yes | The custom domain indicated by the user | +| `auto_ssl_enabled` | boolean | no | Enables [automatic generation](../user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md) of SSL certificates issued by Let's Encrypt for custom domains. | +| `certificate` | file/string | no | The certificate in PEM format with intermediates following in most specific to least specific order.| +| `key` | file/string | no | The certificate key in PEM format. | ```bash curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "domain=ssl.domain.example" --form "certificate=@/path/to/cert.pem" --form "key=@/path/to/key.pem" https://gitlab.example.com/api/v4/projects/5/pages/domains @@ -129,10 +133,15 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "domain curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "domain=ssl.domain.example" --form "certificate=$CERT_PEM" --form "key=$KEY_PEM" https://gitlab.example.com/api/v4/projects/5/pages/domains ``` +```bash +curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "domain=ssl.domain.example" --form "auto_ssl_enabled=true" https://gitlab.example.com/api/v4/projects/5/pages/domains +``` + ```json { "domain": "ssl.domain.example", "url": "https://ssl.domain.example", + "auto_ssl_enabled": true, "certificate": { "subject": "/O=Example, Inc./OU=Example Origin CA/CN=Example Origin Certificate", "expired": false, @@ -150,12 +159,15 @@ Updates an existing project pages domain. The user must have permissions to chan PUT /projects/:id/pages/domains/:domain ``` -| Attribute | Type | Required | Description | -| ------------- | -------------- | -------- | ---------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `domain` | string | yes | The domain | -| `certificate` | file/string | no | The certificate in PEM format with intermediates following in most specific to least specific order.| -| `key` | file/string | no | The certificate key in PEM format. | +| Attribute | Type | Required | Description | +| ------------------ | -------------- | -------- | ---------------------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `domain` | string | yes | The custom domain indicated by the user | +| `auto_ssl_enabled` | boolean | no | Enables [automatic generation](../user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md) of SSL certificates issued by Let's Encrypt for custom domains. | +| `certificate` | file/string | no | The certificate in PEM format with intermediates following in most specific to least specific order.| +| `key` | file/string | no | The certificate key in PEM format. | + +### Adding certificate ```bash curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "certificate=@/path/to/cert.pem" --form "key=@/path/to/key.pem" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example @@ -169,6 +181,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "certifi { "domain": "ssl.domain.example", "url": "https://ssl.domain.example", + "auto_ssl_enabled": false, "certificate": { "subject": "/O=Example, Inc./OU=Example Origin CA/CN=Example Origin Certificate", "expired": false, @@ -178,6 +191,36 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "certifi } ``` +### Enabling Let's Encrypt integration for Pages custom domains + +```bash +curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "auto_ssl_enabled=true" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example +``` + +```json +{ + "domain": "ssl.domain.example", + "url": "https://ssl.domain.example", + "auto_ssl_enabled": true +} +``` + +### Removing certificate + +To remove the SSL certificate attached to the Pages domain, run: + +```bash +curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "certificate=" --form "key=" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example +``` + +```json +{ + "domain": "ssl.domain.example", + "url": "https://ssl.domain.example", + "auto_ssl_enabled": false +} +``` + ## Delete pages domain Deletes an existing project pages domain. @@ -189,7 +232,7 @@ DELETE /projects/:id/pages/domains/:domain | Attribute | Type | Required | Description | | --------- | -------------- | -------- | ---------------------------------------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `domain` | string | yes | The domain | +| `domain` | string | yes | The custom domain indicated by the user | ```bash curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example diff --git a/doc/api/settings.md b/doc/api/settings.md index f63466298e3..51d5e5f35d7 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -324,6 +324,9 @@ are listed in the descriptions of the relevant settings. | `snowplow_enabled` | boolean | no | Enable snowplow tracking. | | `snowplow_app_id` | string | no | The Snowplow site name / application id. (e.g. `gitlab`) | | `snowplow_iglu_registry_url` | string | no | The Snowplow base Iglu Schema Registry URL to use for custom context and self describing events'| +| `sourcegraph_enabled` | boolean | no | Enables Sourcegraph integration. Default is `false`. **If enabled, requires** `sourcegraph_url`. | +| `sourcegraph_url` | string | required by: `sourcegraph_enabled` | The Sourcegraph instance URL for integration. | +| `sourcegraph_public_only` | boolean | no | Blocks Sourcegraph from being loaded on private and internal projects. Defaul is `true`. | | `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to `0` for unlimited time. | | `terms` | text | required by: `enforce_terms` | (**Required by:** `enforce_terms`) Markdown content for the ToS. | | `throttle_authenticated_api_enabled` | boolean | no | (**If enabled, requires:** `throttle_authenticated_api_period_in_seconds` and `throttle_authenticated_api_requests_per_period`) Enable authenticated API request rate limit. Helps reduce request volume (e.g. from crawlers or abusive bots). | diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md index 1ce9525d74e..c410c7eae41 100644 --- a/doc/development/feature_flags/development.md +++ b/doc/development/feature_flags/development.md @@ -61,7 +61,7 @@ you'd want to explicitly disable that flag until the frontend half is also ready to be shipped. To make sure this feature is disabled for both GitLab.com and self-managed instances you'd need to explicitly call `Feature.enabled?` method before the `feature_available` method. This ensures the feature_flag is defaulting -to `true`. +to `false`. ## Feature groups diff --git a/doc/integration/README.md b/doc/integration/README.md index 7a2c9b9bc54..3f33aa94cb9 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -54,6 +54,7 @@ GitLab can be integrated with the following enhancements: - Add GitLab actions to [Gmail actions buttons](gmail_action_buttons_for_gitlab.md). - Configure [PlantUML](../administration/integration/plantuml.md) to use diagrams in AsciiDoc documents. - Attach merge requests to [Trello](trello_power_up.md) cards. +- Enable integrated code intelligence powered by [Sourcegraph](sourcegraph.md). ## Project services diff --git a/doc/integration/img/sourcegraph_admin_v12_5.png b/doc/integration/img/sourcegraph_admin_v12_5.png Binary files differnew file mode 100644 index 00000000000..23e38f56619 --- /dev/null +++ b/doc/integration/img/sourcegraph_admin_v12_5.png diff --git a/doc/integration/img/sourcegraph_demo_v12_5.png b/doc/integration/img/sourcegraph_demo_v12_5.png Binary files differnew file mode 100644 index 00000000000..c70448c0a8a --- /dev/null +++ b/doc/integration/img/sourcegraph_demo_v12_5.png diff --git a/doc/integration/img/sourcegraph_popover_v12_5.png b/doc/integration/img/sourcegraph_popover_v12_5.png Binary files differnew file mode 100644 index 00000000000..878d6143646 --- /dev/null +++ b/doc/integration/img/sourcegraph_popover_v12_5.png diff --git a/doc/integration/img/sourcegraph_user_preferences_v12_5.png b/doc/integration/img/sourcegraph_user_preferences_v12_5.png Binary files differnew file mode 100644 index 00000000000..2c0e138e296 --- /dev/null +++ b/doc/integration/img/sourcegraph_user_preferences_v12_5.png diff --git a/doc/integration/sourcegraph.md b/doc/integration/sourcegraph.md new file mode 100644 index 00000000000..fc45b270d80 --- /dev/null +++ b/doc/integration/sourcegraph.md @@ -0,0 +1,119 @@ +--- +type: reference, how-to +--- + +# Sourcegraph integration + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/16556) in GitLab 12.5. Please note that this integration is [behind a feature flag](#enable-the-sourcegraph-feature-flag). + +[Sourcegraph](https://sourcegraph.com) provides code intelligence features, natively integrated into the GitLab UI. + +For GitLab.com users, see [Sourcegraph for GitLab.com](#sourcegraph-for-gitlabcom). + +![Sourcegraph demo](img/sourcegraph_demo_v12_5.png) + +NOTE: **Note:** +This feature requires user opt-in. After Sourcegraph has been enabled for your GitLab instance, +you can choose to enable Sourcegraph [through your user preferences](#enable-sourcegraph-in-user-preferences). + +## Set up for self-managed GitLab instances **(CORE ONLY)** + +Before you can enable Sourcegraph code intelligence in GitLab you will need to: + +- Enable the `sourcegraph` feature flag for your GitLab instance. +- Configure a Sourcegraph instance with your GitLab instance as an external service. + +### Enable the Sourcegraph feature flag + +NOTE: **Note:** +If you are running a self-managed instance, the Sourcegraph integration will not be available +unless the feature flag `sourcegraph` is enabled. This can be done from the Rails console +by instance administrators. + +Use these commands to start the Rails console: + +```sh +# Omnibus GitLab +gitlab-rails console + +# Installation from source +cd /home/git/gitlab +sudo -u git -H bin/rails console RAILS_ENV=production +``` + +Then run the following command to enable the feature flag: + +``` +Feature.enable(:sourcegraph) +``` + +You can also enable the feature flag only for specific projects with: + +``` +Feature.enable(:sourcegraph, Project.find_by_full_path('my_group/my_project')) +``` + +### Set up a self-managed Sourcegraph instance + +If you are new to Sourcegraph, head over to the [Sourcegraph installation documentation](https://docs.sourcegraph.com/admin) and get your instance up and running. + +### Connect your Sourcegraph instance to your GitLab instance + +1. Navigate to the site admin area in Sourcegraph. +1. [Configure your GitLab external service](https://docs.sourcegraph.com/admin/external_service/gitlab). +You can skip this step if you already have your GitLab repositories searchable in Sourcegraph. +1. Validate that you can search your repositories from GitLab in your Sourcegraph instance by running a test query. +1. Add your GitLab instance URL to the [`corsOrigin` setting](https://docs.sourcegraph.com/admin/config/site_config#corsOrigin) in your site configuration. + +### Configure your GitLab instance with Sourcegraph + +1. In GitLab, go to **Admin Area > Settings > Integrations**. +1. Expand the **Sourcegraph** configuration section. +1. Check **Enable Sourcegraph**. +1. Set the Sourcegraph URL to your Sourcegraph instance, e.g., `https://sourcegraph.example.com`. + +![Sourcegraph admin settings](img/sourcegraph_admin_v12_5.png) + +## Enable Sourcegraph in user preferences + +If a GitLab administrator has enabled Sourcegraph, you can enable this feature in your user preferences. + +1. In GitLab, click your avatar in the top-right corner, then click **Settings**. On the left-hand nav, click **Preferences**. +1. Under **Integrations**, find the **Sourcegraph** section. +1. Check **Enable Sourcegraph**. + +![Sourcegraph user preferences](img/sourcegraph_user_preferences_v12_5.png) + +## Using Sourcegraph code intelligence + +Once enabled, participating projects will have a code intelligence popover available in +the following code views: + +- Merge request diffs +- Commit view +- File view + +When visiting one of these views, you can now hover over a code reference to see a popover with: + +- Details on how this reference was defined. +- **Go to definition**, which navigates to the line of code where this reference was defined. +- **Find references**, which navigates to the configured Sourcegraph instance, showing a list of references to the hilighted code. + +![Sourcegraph demo](img/sourcegraph_popover_v12_5.png) + +## Sourcegraph for GitLab.com + +Sourcegraph powered code intelligence will be incrementally rolled out on GitLab.com. It will eventually be +available for all public projects, but for now, it is only available for some specific [`gitlab-org` projects](https://gitlab.com/gitlab-org/). + +If you have a private or internal project and would like integrated code intelligence, please consider +setting up a self-managed GitLab instance. + +## Sourcegraph and Privacy + +From Sourcegraph's [extension documentation](https://docs.sourcegraph.com/integration/browser_extension#privacy) which is the +engine behind the native GitLab integration: + +> Sourcegraph integrations never send any logs, pings, usage statistics, or telemetry to Sourcegraph.com. +> They will only connect to Sourcegraph.com as required to provide code intelligence or other functionality on public code. +> As a result, no private code, private repository names, usernames, or any other specific data is sent to Sourcegraph.com. diff --git a/doc/user/admin_area/settings/img/two_factor_grace_period.png b/doc/user/admin_area/settings/img/two_factor_grace_period.png Binary files differnew file mode 100644 index 00000000000..e7fb52969aa --- /dev/null +++ b/doc/user/admin_area/settings/img/two_factor_grace_period.png diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md index 4ca91ae5339..42f496bfbfa 100644 --- a/doc/user/admin_area/settings/index.md +++ b/doc/user/admin_area/settings/index.md @@ -14,6 +14,7 @@ include: - [Continuous Integration and Deployment](continuous_integration.md) - [Email](email.md) - [Sign up restrictions](sign_up_restrictions.md) +- [Sign in restrictions](sign_in_restrictions.md) - [Terms](terms.md) - [Third party offers](third_party_offers.md) - [Usage statistics](usage_statistics.md) diff --git a/doc/user/admin_area/settings/sign_in_restrictions.md b/doc/user/admin_area/settings/sign_in_restrictions.md new file mode 100644 index 00000000000..0975766400f --- /dev/null +++ b/doc/user/admin_area/settings/sign_in_restrictions.md @@ -0,0 +1,56 @@ +--- +type: reference +--- + +# Sign-in restrictions **(CORE ONLY)** + +You can use sign-in restrictions to limit the authentication with password +for web interface and Git over HTTP(S), two-factor authentication enforcing, as well as +as configuring the home page URL and after sign-out path. + +## Password authentication enabled + +You can restrict the password authentication for web interface and Git over HTTP(S): + +- **Web interface**: When this feature is disabled, an [external authentication provider](../../../administration/auth/README.md) must be used. +- **Git over HTTP(S)**: When this feature is disabled, a [Personal Access Token](../../profile/personal_access_tokens.md) must be used to authenticate. + +## Two-factor authentication + +When this feature enabled, all users will have to use the [two-factor authentication](../../profile/account/two_factor_authentication.md). + +Once the two-factor authentication is configured as mandatory, the users will be allowed +to skip forced configuration of two-factor authentication for the configurable grace +period in hours. + +![Two-factor grace period](img/two_factor_grace_period.png) + +## Sign-in information + +All users that are not logged-in will be redirected to the page represented by the configured +"Home page URL" if value is not empty. + +All users will be redirect to the page represented by the configured "After sign out path" +after sign out if value is not empty. + +If a "Sign in text" in Markdown format is provided, then every user will be presented with +this message after logging-in. + +## Settings + +To access this feature: + +1. Navigate to the **Settings > General** in the Admin area. +1. Expand the **Sign-in restrictions** section. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md index 9b43f6317d0..b299c74c8f4 100644 --- a/doc/user/profile/preferences.md +++ b/doc/user/profile/preferences.md @@ -128,6 +128,19 @@ You can choose one of the following options as the first day of the week: If you select **System Default**, the system-wide default setting will be used. +## Integrations + +Configure your preferences with third-party services which provide enhancements to your GitLab experience. + +### Sourcegraph + +NOTE: **Note:** +This setting is only visible if Sourcegraph has been enabled by a GitLab administrator. + +Manage the availability of integrated code intelligence features powered by +Sourcegraph. View [the Sourcegraph feature documentation](../../integration/sourcegraph.md#enable-sourcegraph-in-user-preferences) +for more information. + <!-- ## Troubleshooting Include any troubleshooting steps that you can foresee. If you know beforehand what issues diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 5aa16b117fd..9617f1a8acf 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1681,6 +1681,7 @@ module API expose :verified?, as: :verified expose :verification_code, as: :verification_code expose :enabled_until + expose :auto_ssl_enabled expose :certificate, as: :certificate_expiration, @@ -1696,6 +1697,7 @@ module API expose :verified?, as: :verified expose :verification_code, as: :verification_code expose :enabled_until + expose :auto_ssl_enabled expose :certificate, if: ->(pages_domain, _) { pages_domain.certificate? }, diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb index ec2fe8270b7..2d02a4e624c 100644 --- a/lib/api/pages_domains.rb +++ b/lib/api/pages_domains.rb @@ -92,8 +92,10 @@ module API requires :domain, type: String, desc: 'The domain' # rubocop:disable Scalability/FileUploads # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 - optional :certificate, allow_blank: false, types: [File, String], desc: 'The certificate', as: :user_provided_certificate - optional :key, allow_blank: false, types: [File, String], desc: 'The key', as: :user_provided_key + optional :certificate, types: [File, String], desc: 'The certificate', as: :user_provided_certificate + optional :key, types: [File, String], desc: 'The key', as: :user_provided_key + optional :auto_ssl_enabled, allow_blank: false, type: Boolean, default: false, + desc: "Enables automatic generation of SSL certificates issued by Let's Encrypt for custom domains." # rubocop:enable Scalability/FileUploads all_or_none_of :user_provided_certificate, :user_provided_key end @@ -116,14 +118,16 @@ module API requires :domain, type: String, desc: 'The domain' # rubocop:disable Scalability/FileUploads # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 - optional :certificate, allow_blank: false, types: [File, String], desc: 'The certificate', as: :user_provided_certificate - optional :key, allow_blank: false, types: [File, String], desc: 'The key', as: :user_provided_key + optional :certificate, types: [File, String], desc: 'The certificate', as: :user_provided_certificate + optional :key, types: [File, String], desc: 'The key', as: :user_provided_key + optional :auto_ssl_enabled, allow_blank: true, type: Boolean, + desc: "Enables automatic generation of SSL certificates issued by Let's Encrypt for custom domains." # rubocop:enable Scalability/FileUploads end put ":id/pages/domains/:domain", requirements: PAGES_DOMAINS_ENDPOINT_REQUIREMENTS do authorize! :update_pages, user_project - pages_domain_params = declared(params, include_parent_namespaces: false) + pages_domain_params = declared(params, include_parent_namespaces: false, include_missing: false) # Remove empty private key if certificate is not empty. if pages_domain_params[:user_provided_certificate] && !pages_domain_params[:user_provided_key] diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 88076614f73..5362b3060c1 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -136,6 +136,11 @@ module API optional :sign_in_text, type: String, desc: 'The sign in text of the GitLab application' optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5 optional :signup_enabled, type: Boolean, desc: 'Flag indicating if sign up is enabled' + optional :sourcegraph_enabled, type: Boolean, desc: 'Enable Sourcegraph' + optional :sourcegraph_public_only, type: Boolean, desc: 'Only allow public projects to communicate with Sourcegraph' + given sourcegraph_enabled: ->(val) { val } do + requires :sourcegraph_url, type: String, desc: 'The configured Sourcegraph instance URL' + end optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins' diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb index fa1615a5953..412d00c6939 100644 --- a/lib/gitlab/redis/wrapper.rb +++ b/lib/gitlab/redis/wrapper.rb @@ -25,6 +25,8 @@ module Gitlab if Sidekiq.server? # the pool will be used in a multi-threaded context size += Sidekiq.options[:concurrency] + elsif defined?(::Puma) + size += Puma.cli_config.options[:max_threads] end size diff --git a/lib/gitlab/sourcegraph.rb b/lib/gitlab/sourcegraph.rb new file mode 100644 index 00000000000..d0f12c8364a --- /dev/null +++ b/lib/gitlab/sourcegraph.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Gitlab + class Sourcegraph + class << self + def feature_conditional? + feature.conditional? + end + + def feature_available? + # The sourcegraph_bundle feature could be conditionally applied, so check if `!off?` + !feature.off? + end + + def feature_enabled?(thing = nil) + feature.enabled?(thing) + end + + private + + def feature + Feature.get(:sourcegraph) + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e98f06864ce..204c138b9d6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8232,10 +8232,10 @@ msgstr "" msgid "GitLabPages|Configure pages" msgstr "" -msgid "GitLabPages|Details" +msgid "GitLabPages|Domains" msgstr "" -msgid "GitLabPages|Domains" +msgid "GitLabPages|Edit" msgstr "" msgid "GitLabPages|Expired" @@ -12541,6 +12541,9 @@ msgstr "" msgid "Preferences|Choose what content you want to see on a project’s overview page." msgstr "" +msgid "Preferences|Customize integrations with third party services." +msgstr "" + msgid "Preferences|Customize the appearance of the application header and navigation sidebar." msgstr "" @@ -12550,9 +12553,15 @@ msgstr "" msgid "Preferences|Display time in 24-hour format" msgstr "" +msgid "Preferences|Enable integrated code intelligence on code views" +msgstr "" + msgid "Preferences|For example: 30 mins ago." msgstr "" +msgid "Preferences|Integrations" +msgstr "" + msgid "Preferences|Layout width" msgstr "" @@ -12565,6 +12574,9 @@ msgstr "" msgid "Preferences|Show whitespace in diffs" msgstr "" +msgid "Preferences|Sourcegraph" +msgstr "" + msgid "Preferences|Syntax highlighting theme" msgstr "" @@ -16172,6 +16184,51 @@ msgstr "" msgid "Source project cannot be found." msgstr "" +msgid "Sourcegraph" +msgstr "" + +msgid "SourcegraphAdmin|Block on private and internal projects" +msgstr "" + +msgid "SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects." +msgstr "" + +msgid "SourcegraphAdmin|Enable Sourcegraph" +msgstr "" + +msgid "SourcegraphAdmin|Enable code intelligence powered by %{link_start}Sourcegraph%{link_end} on your GitLab instance's code views and merge requests." +msgstr "" + +msgid "SourcegraphAdmin|If checked, only public projects will have code intelligence and communicate with Sourcegraph." +msgstr "" + +msgid "SourcegraphAdmin|More information" +msgstr "" + +msgid "SourcegraphAdmin|Save changes" +msgstr "" + +msgid "SourcegraphAdmin|Sourcegraph URL" +msgstr "" + +msgid "SourcegraphAdmin|e.g. https://sourcegraph.example.com" +msgstr "" + +msgid "SourcegraphPreferences|This feature is experimental and currently limited to certain projects." +msgstr "" + +msgid "SourcegraphPreferences|This feature is experimental and limited to public projects." +msgstr "" + +msgid "SourcegraphPreferences|This feature is experimental." +msgstr "" + +msgid "SourcegraphPreferences|Uses %{link_start}Sourcegraph.com%{link_end}." +msgstr "" + +msgid "SourcegraphPreferences|Uses a custom %{link_start}Sourcegraph instance%{link_end}." +msgstr "" + msgid "Spam Logs" msgstr "" diff --git a/package.json b/package.json index 2a439be9514..1cf88523c97 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@gitlab/svgs": "^1.82.0", "@gitlab/ui": "7.11.0", "@gitlab/visual-review-tools": "1.2.0", + "@sourcegraph/code-host-integration": "^0.0.13", "@sentry/browser": "^5.7.1", "apollo-cache-inmemory": "^1.6.3", "apollo-client": "^2.6.4", diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 0842f7871ee..ed872783856 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -1,5 +1,4 @@ [[ "$TRACE" ]] && set -x -export TILLER_NAMESPACE="$KUBE_NAMESPACE" function deploy_exists() { local namespace="${1}" @@ -14,16 +13,18 @@ function deploy_exists() { } function previous_deploy_failed() { - local deploy="${1}" + local namespace="${1}" + local deploy="${2}" + echoinfo "Checking for previous deployment of ${deploy}" true - helm status "${deploy}" >/dev/null 2>&1 + helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1 local status=$? # if `status` is `0`, deployment exists, has a status if [ $status -eq 0 ]; then echoinfo "Previous deployment found, checking status..." - deployment_status=$(helm status "${deploy}" | grep ^STATUS | cut -d' ' -f2) + deployment_status=$(helm status --tiller-namespace "${namespace}" "${deploy}" | grep ^STATUS | cut -d' ' -f2) echoinfo "Previous deployment state: ${deployment_status}" if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then status=0; @@ -37,16 +38,17 @@ function previous_deploy_failed() { } function delete_release() { - if [ -z "$CI_ENVIRONMENT_SLUG" ]; then + local namespace="${KUBE_NAMESPACE}" + local deploy="${CI_ENVIRONMENT_SLUG}" + + if [ -z "$deploy" ]; then echoerr "No release given, aborting the delete!" return fi - local name="$CI_ENVIRONMENT_SLUG" - - echoinfo "Deleting release '$name'..." true + echoinfo "Deleting release '$deploy'..." true - helm delete --purge "$name" + helm delete --purge --tiller-namespace "${namespace}" "${deploy}" } function delete_failed_release() { @@ -59,7 +61,7 @@ function delete_failed_release() { echoinfo "No Review App with ${CI_ENVIRONMENT_SLUG} is currently deployed." else # Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade` - if previous_deploy_failed "$CI_ENVIRONMENT_SLUG" ; then + if previous_deploy_failed "${KUBE_NAMESPACE}" "$CI_ENVIRONMENT_SLUG" ; then echoinfo "Review App deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG" delete_release else @@ -117,6 +119,7 @@ function ensure_namespace() { } function install_tiller() { + local TILLER_NAMESPACE="$KUBE_NAMESPACE" echoinfo "Checking deployment/tiller-deploy status in the ${TILLER_NAMESPACE} namespace..." true echoinfo "Initiating the Helm client..." @@ -131,11 +134,12 @@ function install_tiller() { --override "spec.template.spec.tolerations[0].key"="dedicated" \ --override "spec.template.spec.tolerations[0].operator"="Equal" \ --override "spec.template.spec.tolerations[0].value"="helm" \ - --override "spec.template.spec.tolerations[0].effect"="NoSchedule" + --override "spec.template.spec.tolerations[0].effect"="NoSchedule" \ + --tiller-namespace "${TILLER_NAMESPACE}" kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy" - if ! helm version --debug; then + if ! helm version --debug --tiller-namespace "${TILLER_NAMESPACE}"; then echo "Failed to init Tiller." return 1 fi @@ -147,7 +151,7 @@ function install_external_dns() { domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}') echoinfo "Installing external DNS for domain ${domain}..." true - if ! deploy_exists "${KUBE_NAMESPACE}" "${release_name}" || previous_deploy_failed "${release_name}" ; then + if ! deploy_exists "${KUBE_NAMESPACE}" "${release_name}" || previous_deploy_failed "${KUBE_NAMESPACE}" "${release_name}" ; then echoinfo "Installing external-dns Helm chart" helm repo update # Default requested: CPU => 0, memory => 0 diff --git a/spec/controllers/concerns/sourcegraph_gon_spec.rb b/spec/controllers/concerns/sourcegraph_gon_spec.rb new file mode 100644 index 00000000000..4fb7e37d148 --- /dev/null +++ b/spec/controllers/concerns/sourcegraph_gon_spec.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SourcegraphGon do + let_it_be(:enabled_user) { create(:user, sourcegraph_enabled: true) } + let_it_be(:disabled_user) { create(:user, sourcegraph_enabled: false) } + let_it_be(:public_project) { create(:project, :public) } + let_it_be(:internal_project) { create(:project, :internal) } + + let(:sourcegraph_url) { 'http://sourcegraph.gitlab.com' } + let(:feature_enabled) { true } + let(:sourcegraph_enabled) { true } + let(:sourcegraph_public_only) { false } + let(:format) { :html } + let(:user) { enabled_user } + let(:project) { internal_project } + + controller(ApplicationController) do + include SourcegraphGon # rubocop:disable RSpec/DescribedClass + + def index + head :ok + end + end + + before do + Feature.get(:sourcegraph).enable(feature_enabled) + + stub_application_setting(sourcegraph_url: sourcegraph_url, sourcegraph_enabled: sourcegraph_enabled, sourcegraph_public_only: sourcegraph_public_only) + + allow(controller).to receive(:project).and_return(project) + + Gon.clear + + sign_in user if user + end + + after do + Feature.get(:sourcegraph).disable + end + + subject do + get :index, format: format + + Gon.sourcegraph + end + + shared_examples 'enabled' do + it { is_expected.to eq({ url: sourcegraph_url }) } + end + + shared_examples 'disabled' do + it { is_expected.to be_nil } + end + + context 'with feature enabled, application enabled, and user enabled' do + it_behaves_like 'enabled' + end + + context 'with feature enabled for specific project' do + let(:feature_enabled) { project } + + it_behaves_like 'enabled' + end + + context 'with feature enabled for different project' do + let(:feature_enabled) { create(:project) } + + it_behaves_like 'disabled' + end + + context 'with feature disabled' do + let(:feature_enabled) { false } + + it_behaves_like 'disabled' + end + + context 'with admin settings disabled' do + let(:sourcegraph_enabled) { false } + + it_behaves_like 'disabled' + end + + context 'with public only' do + let(:sourcegraph_public_only) { true } + + context 'with internal project' do + let(:project) { internal_project } + + it_behaves_like 'disabled' + end + + context 'with public project' do + let(:project) { public_project } + + it_behaves_like 'enabled' + end + end + + context 'with user disabled' do + let(:user) { disabled_user } + + it_behaves_like 'disabled' + end + + context 'with no user' do + let(:user) { nil } + + it_behaves_like 'disabled' + end + + context 'with non-html format' do + let(:format) { :json } + + it_behaves_like 'disabled' + end +end diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb index 1db1fff02d8..3987bebb124 100644 --- a/spec/controllers/projects/pages_domains_controller_spec.rb +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -32,10 +32,10 @@ describe Projects::PagesDomainsController do get(:show, params: request_params.merge(id: pages_domain.domain)) end - it "displays the 'show' page" do + it "redirects to the 'edit' page" do make_request - expect(response).to have_gitlab_http_status(200) - expect(response).to render_template('show') + + expect(response).to redirect_to(edit_project_pages_domain_path(project, pages_domain.domain)) end context 'when user is developer' do @@ -69,7 +69,7 @@ describe Projects::PagesDomainsController do created_domain = PagesDomain.reorder(:id).last expect(created_domain).to be_present - expect(response).to redirect_to(project_pages_domain_path(project, created_domain)) + expect(response).to redirect_to(edit_project_pages_domain_path(project, created_domain)) end end @@ -160,7 +160,7 @@ describe Projects::PagesDomainsController do post :verify, params: params - expect(response).to redirect_to project_pages_domain_path(project, pages_domain) + expect(response).to redirect_to edit_project_pages_domain_path(project, pages_domain) expect(flash[:notice]).to eq('Successfully verified domain ownership') end @@ -169,7 +169,7 @@ describe Projects::PagesDomainsController do post :verify, params: params - expect(response).to redirect_to project_pages_domain_path(project, pages_domain) + expect(response).to redirect_to edit_project_pages_domain_path(project, pages_domain) expect(flash[:alert]).to eq('Failed to verify domain ownership') end diff --git a/spec/features/projects/pages_lets_encrypt_spec.rb b/spec/features/projects/pages_lets_encrypt_spec.rb index c33a2896952..19c61487e16 100644 --- a/spec/features/projects/pages_lets_encrypt_spec.rb +++ b/spec/features/projects/pages_lets_encrypt_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe "Pages with Let's Encrypt", :https_pages_enabled do include LetsEncryptHelpers - let(:project) { create(:project) } + let(:project) { create(:project, pages_https_only: false) } let(:user) { create(:user) } let(:role) { :maintainer } let(:certificate_pem) { attributes_for(:pages_domain)[:certificate] } @@ -34,14 +34,14 @@ describe "Pages with Let's Encrypt", :https_pages_enabled do expect(domain.auto_ssl_enabled).to eq false expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'false' - expect(page).to have_field 'Certificate (PEM)', type: 'textarea' - expect(page).to have_field 'Key (PEM)', type: 'textarea' + expect(page).to have_selector '.card-header', text: 'Certificate' + expect(page).to have_text domain.subject find('.js-auto-ssl-toggle-container .project-feature-toggle').click expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'true' - expect(page).not_to have_field 'Certificate (PEM)', type: 'textarea' - expect(page).not_to have_field 'Key (PEM)', type: 'textarea' + expect(page).not_to have_selector '.card-header', text: 'Certificate' + expect(page).not_to have_text domain.subject click_on 'Save Changes' @@ -67,9 +67,6 @@ describe "Pages with Let's Encrypt", :https_pages_enabled do expect(page).to have_field 'Certificate (PEM)', type: 'textarea' expect(page).to have_field 'Key (PEM)', type: 'textarea' - fill_in 'Certificate (PEM)', with: certificate_pem - fill_in 'Key (PEM)', with: certificate_key - click_on 'Save Changes' expect(domain.reload.auto_ssl_enabled).to eq false @@ -81,7 +78,8 @@ describe "Pages with Let's Encrypt", :https_pages_enabled do it 'user do not see private key' do visit edit_project_pages_domain_path(project, domain) - expect(find_field('Key (PEM)', visible: :all, disabled: :all).value).to be_blank + expect(page).not_to have_selector '.card-header', text: 'Certificate' + expect(page).not_to have_text domain.subject end end @@ -100,10 +98,21 @@ describe "Pages with Let's Encrypt", :https_pages_enabled do context 'when certificate is provided by user' do let(:domain) { create(:pages_domain, project: project) } - it 'user sees private key' do + it 'user sees certificate subject' do + visit edit_project_pages_domain_path(project, domain) + + expect(page).to have_selector '.card-header', text: 'Certificate' + expect(page).to have_text domain.subject + end + + it 'user can delete the certificate', :js do visit edit_project_pages_domain_path(project, domain) - expect(find_field('Key (PEM)').value).not_to be_blank + expect(page).to have_selector '.card-header', text: 'Certificate' + expect(page).to have_text domain.subject + within('.card') { accept_confirm { click_on 'Remove' } } + expect(page).to have_field 'Certificate (PEM)', with: '' + expect(page).to have_field 'Key (PEM)', with: '' end end end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 17a2d30e784..eab0aefbe84 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' shared_examples 'pages settings editing' do - let(:project) { create(:project) } + let_it_be(:project) { create(:project, pages_https_only: false) } let(:user) { create(:user) } let(:role) { :maintainer } @@ -185,6 +185,21 @@ shared_examples 'pages settings editing' do expect(page).to have_content('my.test.domain.com') end + describe 'with dns verification enabled' do + before do + stub_application_setting(pages_domain_verification_enabled: true) + end + + it 'shows the DNS verification record' do + domain = create(:pages_domain, project: project) + + visit project_pages_path(project) + + within('#content-body') { click_link 'Edit' } + expect(page).to have_field :domain_verification, with: "#{domain.verification_domain} TXT #{domain.keyed_verification_code}" + end + end + describe 'updating the certificate for an existing domain' do let!(:domain) do create(:pages_domain, project: project) @@ -193,19 +208,22 @@ shared_examples 'pages settings editing' do it 'allows the certificate to be updated' do visit project_pages_path(project) - within('#content-body') { click_link 'Details' } - click_link 'Edit' + within('#content-body') { click_link 'Edit' } click_button 'Save Changes' expect(page).to have_content('Domain was updated') end context 'when the certificate is invalid' do + let_it_be(:domain) do + create(:pages_domain, :without_certificate, :without_key, project: project) + end + it 'tells the user what the problem is' do visit project_pages_path(project) - within('#content-body') { click_link 'Details' } - click_link 'Edit' + within('#content-body') { click_link 'Edit' } + fill_in 'Certificate (PEM)', with: 'invalid data' click_button 'Save Changes' @@ -214,6 +232,27 @@ shared_examples 'pages settings editing' do expect(page).to have_content("Key doesn't match the certificate") end end + + it 'allows the certificate to be removed', :js do + visit project_pages_path(project) + + within('#content-body') { click_link 'Edit' } + + accept_confirm { click_link 'Remove' } + + expect(page).to have_field('Certificate (PEM)', with: '') + expect(page).to have_field('Key (PEM)', with: '') + domain.reload + expect(domain.certificate).to be_nil + expect(domain.key).to be_nil + end + + it 'shows the DNS CNAME record' do + visit project_pages_path(project) + + within('#content-body') { click_link 'Edit' } + expect(page).to have_field :domain_dns, with: "#{domain.domain} CNAME #{domain.project.pages_subdomain}.#{Settings.pages.host}." + end end end end @@ -250,7 +289,7 @@ shared_examples 'pages settings editing' do end end - describe 'HTTPS settings', :js, :https_pages_enabled do + describe 'HTTPS settings', :https_pages_enabled do before do project.namespace.update(owner: user) @@ -358,18 +397,21 @@ shared_examples 'pages settings editing' do expect(page).to have_link('Remove pages') - click_link 'Remove pages' + accept_confirm { click_link 'Remove pages' } - expect(project.pages_deployed?).to be_falsey + expect(page).to have_content('Pages were removed') + expect(project.reload.pages_deployed?).to be_falsey end end end end -describe 'Pages' do +describe 'Pages', :js do include LetsEncryptHelpers - include_examples 'pages settings editing' + context 'when editing normally' do + include_examples 'pages settings editing' + end context 'when letsencrypt support is enabled' do before do diff --git a/spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json b/spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json index ed8ed9085c0..721b8d4641f 100644 --- a/spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json +++ b/spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json @@ -7,6 +7,7 @@ "verified": { "type": "boolean" }, "verification_code": { "type": ["string", "null"] }, "enabled_until": { "type": ["date", "null"] }, + "auto_ssl_enabled": { "type": "boolean" }, "certificate_expiration": { "type": "object", "properties": { @@ -17,6 +18,6 @@ "additionalProperties": false } }, - "required": ["domain", "url", "project_id", "verified", "verification_code", "enabled_until"], + "required": ["domain", "url", "project_id", "verified", "verification_code", "enabled_until", "auto_ssl_enabled"], "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/public_api/v4/pages_domain/detail.json b/spec/fixtures/api/schemas/public_api/v4/pages_domain/detail.json index b57d544f896..3dd80a6f11b 100644 --- a/spec/fixtures/api/schemas/public_api/v4/pages_domain/detail.json +++ b/spec/fixtures/api/schemas/public_api/v4/pages_domain/detail.json @@ -6,6 +6,7 @@ "verified": { "type": "boolean" }, "verification_code": { "type": ["string", "null"] }, "enabled_until": { "type": ["date", "null"] }, + "auto_ssl_enabled": { "type": "boolean" }, "certificate": { "type": "object", "properties": { @@ -18,6 +19,6 @@ "additionalProperties": false } }, - "required": ["domain", "url", "verified", "verification_code", "enabled_until"], + "required": ["domain", "url", "verified", "verification_code", "enabled_until", "auto_ssl_enabled"], "additionalProperties": false } diff --git a/spec/frontend/repository/log_tree_spec.js b/spec/frontend/repository/log_tree_spec.js index 9eee89cd07b..ad42f8b2ffc 100644 --- a/spec/frontend/repository/log_tree_spec.js +++ b/spec/frontend/repository/log_tree_spec.js @@ -1,6 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; -import { normalizeData, resolveCommit, fetchLogsTree } from '~/repository/log_tree'; +import { resolveCommit, fetchLogsTree } from '~/repository/log_tree'; const mockData = [ { @@ -15,22 +15,6 @@ const mockData = [ }, ]; -describe('normalizeData', () => { - it('normalizes data into LogTreeCommit object', () => { - expect(normalizeData(mockData)).toEqual([ - { - sha: '123', - message: 'testing message', - committedDate: '2019-01-01', - commitPath: 'https://test.com', - fileName: 'index.js', - type: 'blob', - __typename: 'LogTreeCommit', - }, - ]); - }); -}); - describe('resolveCommit', () => { it('calls resolve when commit found', () => { const resolver = { diff --git a/spec/frontend/repository/utils/commit_spec.js b/spec/frontend/repository/utils/commit_spec.js new file mode 100644 index 00000000000..2d75358106c --- /dev/null +++ b/spec/frontend/repository/utils/commit_spec.js @@ -0,0 +1,30 @@ +import { normalizeData } from '~/repository/utils/commit'; + +const mockData = [ + { + commit: { + id: '123', + message: 'testing message', + committed_date: '2019-01-01', + }, + commit_path: `https://test.com`, + file_name: 'index.js', + type: 'blob', + }, +]; + +describe('normalizeData', () => { + it('normalizes data into LogTreeCommit object', () => { + expect(normalizeData(mockData)).toEqual([ + { + sha: '123', + message: 'testing message', + committedDate: '2019-01-01', + commitPath: 'https://test.com', + fileName: 'index.js', + type: 'blob', + __typename: 'LogTreeCommit', + }, + ]); + }); +}); diff --git a/spec/helpers/sourcegraph_helper_spec.rb b/spec/helpers/sourcegraph_helper_spec.rb new file mode 100644 index 00000000000..830bbb3129f --- /dev/null +++ b/spec/helpers/sourcegraph_helper_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SourcegraphHelper do + describe '#sourcegraph_url_message' do + let(:sourcegraph_url) { 'http://sourcegraph.example.com' } + + before do + allow(Gitlab::CurrentSettings).to receive(:sourcegraph_url).and_return(sourcegraph_url) + allow(Gitlab::CurrentSettings).to receive(:sourcegraph_url_is_com?).and_return(is_com) + end + + subject { helper.sourcegraph_url_message } + + context 'with .com sourcegraph url' do + let(:is_com) { true } + + it { is_expected.to have_text('Uses Sourcegraph.com') } + it { is_expected.to have_link('Sourcegraph.com', href: sourcegraph_url) } + end + + context 'with custom sourcegraph url' do + let(:is_com) { false } + + it { is_expected.to have_text('Uses a custom Sourcegraph instance') } + it { is_expected.to have_link('Sourcegraph instance', href: sourcegraph_url) } + + context 'with unsafe url' do + let(:sourcegraph_url) { '\" onload=\"alert(1);\"' } + + it { is_expected.to have_link('Sourcegraph instance', href: sourcegraph_url) } + end + end + end + + context '#sourcegraph_experimental_message' do + let(:feature_conditional) { false } + let(:public_only) { false } + + before do + allow(Gitlab::CurrentSettings).to receive(:sourcegraph_public_only).and_return(public_only) + allow(Gitlab::Sourcegraph).to receive(:feature_conditional?).and_return(feature_conditional) + end + + subject { helper.sourcegraph_experimental_message } + + context 'when not limited by feature or public only' do + it { is_expected.to eq "This feature is experimental." } + end + + context 'when limited by feature' do + let(:feature_conditional) { true } + + it { is_expected.to eq "This feature is experimental and currently limited to certain projects." } + end + + context 'when limited by public only' do + let(:public_only) { true } + + it { is_expected.to eq "This feature is experimental and limited to public projects." } + end + end +end diff --git a/spec/lib/gitlab/sourcegraph_spec.rb b/spec/lib/gitlab/sourcegraph_spec.rb new file mode 100644 index 00000000000..e081ae32175 --- /dev/null +++ b/spec/lib/gitlab/sourcegraph_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Sourcegraph do + let_it_be(:user) { create(:user) } + let(:feature_scope) { true } + + before do + Feature.enable(:sourcegraph, feature_scope) + end + + describe '.feature_conditional?' do + subject { described_class.feature_conditional? } + + context 'when feature is enabled globally' do + it { is_expected.to be_falsey } + end + + context 'when feature is enabled only to a resource' do + let(:feature_scope) { user } + + it { is_expected.to be_truthy } + end + end + + describe '.feature_available?' do + subject { described_class.feature_available? } + + context 'when feature is enabled globally' do + it { is_expected.to be_truthy } + end + + context 'when feature is enabled only to a resource' do + let(:feature_scope) { user } + + it { is_expected.to be_truthy } + end + end + + describe '.feature_enabled?' do + let(:current_user) { nil } + + subject { described_class.feature_enabled?(current_user) } + + context 'when feature is enabled globally' do + it { is_expected.to be_truthy } + end + + context 'when feature is enabled only to a resource' do + let(:feature_scope) { user } + + context 'for the same resource' do + let(:current_user) { user } + + it { is_expected.to be_truthy } + end + + context 'for a different resource' do + let(:current_user) { create(:user) } + + it { is_expected.to be_falsey } + end + end + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 4aa8f2d959d..ba3b99f4421 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe ApplicationSetting do + using RSpec::Parameterized::TableSyntax + subject(:setting) { described_class.create_from_defaults } it { include(CacheableAttributes) } @@ -495,6 +497,15 @@ describe ApplicationSetting do it { is_expected.not_to allow_value(nil).for(:static_objects_external_storage_auth_token) } end end + + context 'sourcegraph settings' do + it 'is invalid if sourcegraph is enabled and no url is provided' do + allow(subject).to receive(:sourcegraph_enabled).and_return(true) + + expect(subject.sourcegraph_url).to be_nil + is_expected.to be_invalid + end + end end context 'restrict creating duplicates' do @@ -583,5 +594,24 @@ describe ApplicationSetting do end end + describe '#sourcegraph_url_is_com?' do + where(:url, :is_com) do + 'https://sourcegraph.com' | true + 'https://sourcegraph.com/' | true + 'https://www.sourcegraph.com' | true + 'shttps://www.sourcegraph.com' | false + 'https://sourcegraph.example.com/' | false + 'https://sourcegraph.org/' | false + end + + with_them do + it 'matches the url with sourcegraph.com' do + setting.sourcegraph_url = url + + expect(setting.sourcegraph_url_is_com?).to eq(is_com) + end + end + end + it_behaves_like 'application settings examples' end diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb index ee3ee9c4d23..6b774e9335e 100644 --- a/spec/requests/api/pages_domains_spec.rb +++ b/spec/requests/api/pages_domains_spec.rb @@ -3,15 +3,20 @@ require 'spec_helper' describe API::PagesDomains do - set(:project) { create(:project, path: 'my.project', pages_https_only: false) } - set(:user) { create(:user) } - set(:admin) { create(:admin) } + let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) } + let_it_be(:user) { create(:user) } + let_it_be(:admin) { create(:admin) } - set(:pages_domain) { create(:pages_domain, :without_key, :without_certificate, domain: 'www.domain.test', project: project) } - set(:pages_domain_secure) { create(:pages_domain, domain: 'ssl.domain.test', project: project) } - set(:pages_domain_expired) { create(:pages_domain, :with_expired_certificate, domain: 'expired.domain.test', project: project) } + let_it_be(:pages_domain) { create(:pages_domain, :without_key, :without_certificate, domain: 'www.domain.test', project: project) } + let_it_be(:pages_domain_secure) { create(:pages_domain, domain: 'ssl.domain.test', project: project) } + let_it_be(:pages_domain_with_letsencrypt) { create(:pages_domain, :letsencrypt, domain: 'letsencrypt.domain.test', project: project) } + let_it_be(:pages_domain_expired) { create(:pages_domain, :with_expired_certificate, domain: 'expired.domain.test', project: project) } let(:pages_domain_params) { build(:pages_domain, :without_key, :without_certificate, domain: 'www.other-domain.test').slice(:domain) } + let(:pages_domain_with_letsencrypt_params) do + build(:pages_domain, :without_key, :without_certificate, domain: 'www.other-domain.test', auto_ssl_enabled: true) + .slice(:domain, :auto_ssl_enabled) + end let(:pages_domain_secure_params) { build(:pages_domain, domain: 'ssl.other-domain.test', project: project).slice(:domain, :certificate, :key) } let(:pages_domain_secure_key_missmatch_params) {build(:pages_domain, :with_trusted_chain, project: project).slice(:domain, :certificate, :key) } let(:pages_domain_secure_missing_chain_params) {build(:pages_domain, :with_missing_chain, project: project).slice(:certificate) } @@ -22,6 +27,7 @@ describe API::PagesDomains do let(:route_secure_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_secure.domain}" } let(:route_expired_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_expired.domain}" } let(:route_vacant_domain) { "/projects/#{project.id}/pages/domains/www.vacant-domain.test" } + let(:route_letsencrypt_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_with_letsencrypt.domain}" } before do allow(Gitlab.config.pages).to receive(:enabled).and_return(true) @@ -47,9 +53,10 @@ describe API::PagesDomains do expect(response).to match_response_schema('public_api/v4/pages_domain_basics') expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.size).to eq(3) + expect(json_response.size).to eq(4) expect(json_response.last).to have_key('domain') expect(json_response.last).to have_key('project_id') + expect(json_response.last).to have_key('auto_ssl_enabled') expect(json_response.last).to have_key('certificate_expiration') expect(json_response.last['certificate_expiration']['expired']).to be true expect(json_response.first).not_to have_key('certificate_expiration') @@ -73,7 +80,7 @@ describe API::PagesDomains do expect(response).to match_response_schema('public_api/v4/pages_domains') expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.size).to eq(3) + expect(json_response.size).to eq(4) expect(json_response.map { |pages_domain| pages_domain['domain'] }).to include(pages_domain.domain) expect(json_response.last).to have_key('domain') end @@ -166,6 +173,7 @@ describe API::PagesDomains do expect(json_response['url']).to eq(pages_domain_secure.url) expect(json_response['certificate']['subject']).to eq(pages_domain_secure.subject) expect(json_response['certificate']['expired']).to be false + expect(json_response['auto_ssl_enabled']).to be false end it 'returns pages domain with an expired certificate' do @@ -175,6 +183,18 @@ describe API::PagesDomains do expect(response).to match_response_schema('public_api/v4/pages_domain/detail') expect(json_response['certificate']['expired']).to be true end + + it 'returns pages domain with letsencrypt' do + get api(route_letsencrypt_domain, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/pages_domain/detail') + expect(json_response['domain']).to eq(pages_domain_with_letsencrypt.domain) + expect(json_response['url']).to eq(pages_domain_with_letsencrypt.url) + expect(json_response['certificate']['subject']).to eq(pages_domain_with_letsencrypt.subject) + expect(json_response['certificate']['expired']).to be false + expect(json_response['auto_ssl_enabled']).to be true + end end context 'when domain is vacant' do @@ -246,6 +266,7 @@ describe API::PagesDomains do expect(pages_domain.domain).to eq(params[:domain]) expect(pages_domain.certificate).to be_nil expect(pages_domain.key).to be_nil + expect(pages_domain.auto_ssl_enabled).to be false end it 'creates a new secure pages domain' do @@ -257,6 +278,29 @@ describe API::PagesDomains do expect(pages_domain.domain).to eq(params_secure[:domain]) expect(pages_domain.certificate).to eq(params_secure[:certificate]) expect(pages_domain.key).to eq(params_secure[:key]) + expect(pages_domain.auto_ssl_enabled).to be false + end + + it 'creates domain with letsencrypt enabled' do + post api(route, user), params: pages_domain_with_letsencrypt_params + pages_domain = PagesDomain.find_by(domain: json_response['domain']) + + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/pages_domain/detail') + expect(pages_domain.domain).to eq(pages_domain_with_letsencrypt_params[:domain]) + expect(pages_domain.auto_ssl_enabled).to be true + end + + it 'creates domain with letsencrypt enabled and provided certificate' do + post api(route, user), params: params_secure.merge(auto_ssl_enabled: true) + pages_domain = PagesDomain.find_by(domain: json_response['domain']) + + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/pages_domain/detail') + expect(pages_domain.domain).to eq(params_secure[:domain]) + expect(pages_domain.certificate).to eq(params_secure[:certificate]) + expect(pages_domain.key).to eq(params_secure[:key]) + expect(pages_domain.auto_ssl_enabled).to be true end it 'fails to create pages domain without key' do @@ -323,13 +367,14 @@ describe API::PagesDomains do shared_examples_for 'put pages domain' do it 'updates pages domain removing certificate' do - put api(route_secure_domain, user) + put api(route_secure_domain, user), params: { certificate: nil, key: nil } pages_domain_secure.reload expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/pages_domain/detail') expect(pages_domain_secure.certificate).to be_nil expect(pages_domain_secure.key).to be_nil + expect(pages_domain_secure.auto_ssl_enabled).to be false end it 'updates pages domain adding certificate' do @@ -342,6 +387,37 @@ describe API::PagesDomains do expect(pages_domain.key).to eq(params_secure[:key]) end + it 'updates pages domain adding certificate with letsencrypt' do + put api(route_domain, user), params: params_secure.merge(auto_ssl_enabled: true) + pages_domain.reload + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/pages_domain/detail') + expect(pages_domain.certificate).to eq(params_secure[:certificate]) + expect(pages_domain.key).to eq(params_secure[:key]) + expect(pages_domain.auto_ssl_enabled).to be true + end + + it 'updates pages domain enabling letsencrypt' do + put api(route_domain, user), params: { auto_ssl_enabled: true } + pages_domain.reload + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/pages_domain/detail') + expect(pages_domain.auto_ssl_enabled).to be true + end + + it 'updates pages domain disabling letsencrypt while preserving the certificate' do + put api(route_letsencrypt_domain, user), params: { auto_ssl_enabled: false } + pages_domain_with_letsencrypt.reload + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/pages_domain/detail') + expect(pages_domain_with_letsencrypt.auto_ssl_enabled).to be false + expect(pages_domain_with_letsencrypt.key).to be + expect(pages_domain_with_letsencrypt.certificate).to be + end + it 'updates pages domain with expired certificate' do put api(route_expired_domain, user), params: params_secure pages_domain_expired.reload diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 3190de03d1a..b7586307929 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -19,6 +19,9 @@ describe API::Settings, 'Settings' do expect(json_response['plantuml_enabled']).to be_falsey expect(json_response['plantuml_url']).to be_nil expect(json_response['default_ci_config_path']).to be_nil + expect(json_response['sourcegraph_enabled']).to be_falsey + expect(json_response['sourcegraph_url']).to be_nil + expect(json_response['sourcegraph_public_only']).to be_truthy expect(json_response['default_project_visibility']).to be_a String expect(json_response['default_snippet_visibility']).to be_a String expect(json_response['default_group_visibility']).to be_a String @@ -45,6 +48,7 @@ describe API::Settings, 'Settings' do storages = Gitlab.config.repositories.storages .merge({ 'custom' => 'tmp/tests/custom_repositories' }) allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + Feature.get(:sourcegraph).enable end it "updates application settings" do @@ -57,6 +61,9 @@ describe API::Settings, 'Settings' do repository_storages: ['custom'], plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com', + sourcegraph_enabled: true, + sourcegraph_url: 'https://sourcegraph.com', + sourcegraph_public_only: false, default_snippet_visibility: 'internal', restricted_visibility_levels: ['public'], default_artifacts_expire_in: '2 days', @@ -89,6 +96,9 @@ describe API::Settings, 'Settings' do expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['plantuml_enabled']).to be_truthy expect(json_response['plantuml_url']).to eq('http://plantuml.example.com') + expect(json_response['sourcegraph_enabled']).to be_truthy + expect(json_response['sourcegraph_url']).to eq('https://sourcegraph.com') + expect(json_response['sourcegraph_public_only']).to eq(false) expect(json_response['default_snippet_visibility']).to eq('internal') expect(json_response['restricted_visibility_levels']).to eq(['public']) expect(json_response['default_artifacts_expire_in']).to eq('2 days') @@ -355,5 +365,14 @@ describe API::Settings, 'Settings' do expect(json_response['domain_blacklist']).to eq(['domain3.com', '*.domain4.com']) end end + + context "missing sourcegraph_url value when sourcegraph_enabled is true" do + it "returns a blank parameter error message" do + put api("/application/settings", admin), params: { sourcegraph_enabled: true } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('sourcegraph_url is missing') + end + end end end diff --git a/spec/views/admin/application_settings/integrations.html.haml_spec.rb b/spec/views/admin/application_settings/integrations.html.haml_spec.rb new file mode 100644 index 00000000000..392d43ef2d4 --- /dev/null +++ b/spec/views/admin/application_settings/integrations.html.haml_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'admin/application_settings/integrations.html.haml' do + let(:app_settings) { build(:application_setting) } + + describe 'sourcegraph integration' do + let(:sourcegraph_flag) { true } + + before do + assign(:application_setting, app_settings) + allow(Gitlab::Sourcegraph).to receive(:feature_available?).and_return(sourcegraph_flag) + end + + context 'when sourcegraph feature is enabled' do + it 'show the form' do + render + + expect(rendered).to have_field('application_setting_sourcegraph_enabled') + end + end + + context 'when sourcegraph feature is disabled' do + let(:sourcegraph_flag) { false } + + it 'show the form' do + render + + expect(rendered).not_to have_field('application_setting_sourcegraph_enabled') + end + end + end +end diff --git a/spec/views/profiles/preferences/show.html.haml_spec.rb b/spec/views/profiles/preferences/show.html.haml_spec.rb new file mode 100644 index 00000000000..52933c42621 --- /dev/null +++ b/spec/views/profiles/preferences/show.html.haml_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'profiles/preferences/show' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { build(:user) } + + before do + assign(:user, user) + allow(controller).to receive(:current_user).and_return(user) + end + + context 'sourcegraph' do + def have_sourcegraph_field(*args) + have_field('user_sourcegraph_enabled', *args) + end + + def have_integrations_section + have_css('.profile-settings-sidebar', { text: 'Integrations' }) + end + + before do + # Can't use stub_feature_flags because we use Feature.get to check if conditinally applied + Feature.get(:sourcegraph).enable sourcegraph_feature + stub_application_setting(sourcegraph_enabled: sourcegraph_enabled) + end + + context 'when not fully enabled' do + where(:feature, :admin_enabled) do + false | false + false | true + true | false + end + + with_them do + let(:sourcegraph_feature) { feature } + let(:sourcegraph_enabled) { admin_enabled } + + before do + render + end + + it 'does not display sourcegraph field' do + expect(rendered).not_to have_sourcegraph_field + end + + it 'does not display integrations settings' do + expect(rendered).not_to have_integrations_section + end + end + end + + context 'when fully enabled' do + let(:sourcegraph_feature) { true } + let(:sourcegraph_enabled) { true } + + before do + render + end + + it 'displays the sourcegraph field' do + expect(rendered).to have_sourcegraph_field + end + + it 'displays the integrations section' do + expect(rendered).to have_integrations_section + end + end + end +end diff --git a/spec/views/projects/pages_domains/show.html.haml_spec.rb b/spec/views/projects/pages_domains/show.html.haml_spec.rb index ba0544a49b0..331bfe63f28 100644 --- a/spec/views/projects/pages_domains/show.html.haml_spec.rb +++ b/spec/views/projects/pages_domains/show.html.haml_spec.rb @@ -30,39 +30,5 @@ describe 'projects/pages_domains/show' do expect(rendered).to have_content("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.") end end - - context 'when certificate is present' do - let(:domain) { create(:pages_domain, :letsencrypt, project: project) } - - it 'shows certificate info' do - render - - # test just a random part of cert represenations(X509v3 Subject Key Identifier:) - expect(rendered).to have_content("C6:5F:56:4B:10:69:AC:1D:33:D2:26:C9:B3:7A:D7:12:4D:3E:F7:90") - end - end - end - - context 'when auto_ssl is disabled' do - context 'when certificate is present' do - let(:domain) { create(:pages_domain, project: project) } - - it 'shows certificate info' do - render - - # test just a random part of cert represenations(X509v3 Subject Key Identifier:) - expect(rendered).to have_content("C6:5F:56:4B:10:69:AC:1D:33:D2:26:C9:B3:7A:D7:12:4D:3E:F7:90") - end - end - - context 'when certificate is absent' do - let(:domain) { create(:pages_domain, :without_certificate, :without_key, project: project) } - - it 'shows missing certificate' do - render - - expect(rendered).to have_content("missing") - end - end end end diff --git a/yarn.lock b/yarn.lock index 61173c11bf3..f50795dec08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -970,6 +970,11 @@ "@sentry/types" "5.7.1" tslib "^1.9.3" +"@sourcegraph/code-host-integration@^0.0.13": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.13.tgz#4fd5fe1e0088c63b2a26be231c5a2a4ca79b1596" + integrity sha512-IjF9gb9e8dG8p12DKg5Z7UMOVQO/ClH3AyMCPfX/qH7DH/0b55WH6stYVqZu6y776quFonO4Z9gWYM8pQZjzKw== + "@types/anymatch@*": version "1.3.0" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.0.tgz#d1d55958d1fccc5527d4aba29fc9c4b942f563ff" |