diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-13 12:08:04 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-13 12:08:04 +0000 |
commit | eb30dd6e28f6fc9eb8021d205f6ed84511f001e2 (patch) | |
tree | 9557e4782c762f4d08f57c9e04991bf988695085 /app | |
parent | c3ad57034cc1cbd6d0ad02de7ac57f6004440c83 (diff) | |
download | gitlab-ce-eb30dd6e28f6fc9eb8021d205f6ed84511f001e2.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
19 files changed, 212 insertions, 25 deletions
diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_network_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_network_dropdown.vue new file mode 100644 index 00000000000..12b6070a79a --- /dev/null +++ b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_network_dropdown.vue @@ -0,0 +1,53 @@ +<script> +import { createNamespacedHelpers, mapState, mapGetters, mapActions } from 'vuex'; + +import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue'; + +const { mapState: mapDropdownState } = createNamespacedHelpers('networks'); +const { mapActions: mapSubnetworkActions } = createNamespacedHelpers('subnetworks'); + +export default { + components: { + ClusterFormDropdown, + }, + props: { + fieldName: { + type: String, + required: true, + }, + }, + computed: { + ...mapState(['selectedNetwork']), + ...mapDropdownState(['items', 'isLoadingItems', 'loadingItemsError']), + ...mapGetters(['hasZone', 'projectId', 'region']), + }, + methods: { + ...mapActions(['setNetwork', 'setSubnetwork']), + ...mapSubnetworkActions({ fetchSubnetworks: 'fetchItems' }), + setNetworkAndFetchSubnetworks(network) { + const { projectId: project, region } = this; + + this.setSubnetwork(''); + this.setNetwork(network); + this.fetchSubnetworks({ project, region, network: network.selfLink }); + }, + }, +}; +</script> +<template> + <cluster-form-dropdown + :field-name="fieldName" + :value="selectedNetwork" + :items="items" + :disabled="!hasZone" + :loading="isLoadingItems" + :has-errors="Boolean(loadingItemsError)" + :loading-text="s__('ClusterIntegration|Loading networks')" + :placeholder="s__('ClusterIntergation|Select a network')" + :search-field-placeholder="s__('ClusterIntegration|Search networks')" + :empty-text="s__('ClusterIntegration|No networks found')" + :error-message="s__('ClusterIntegration|Could not load networks')" + :disabled-text="s__('ClusterIntegration|Select a zone to choose a network')" + @input="setNetworkAndFetchSubnetworks" + /> +</template> diff --git a/app/assets/javascripts/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue new file mode 100644 index 00000000000..ec7889e2907 --- /dev/null +++ b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue @@ -0,0 +1,44 @@ +<script> +import { createNamespacedHelpers, mapState, mapGetters, mapActions } from 'vuex'; + +import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue'; + +const { mapState: mapDropdownState } = createNamespacedHelpers('subnetworks'); + +export default { + components: { + ClusterFormDropdown, + }, + props: { + fieldName: { + type: String, + required: true, + }, + }, + computed: { + ...mapState(['selectedSubnetwork']), + ...mapDropdownState(['items', 'isLoadingItems', 'loadingItemsError']), + ...mapGetters(['hasNetwork']), + }, + methods: { + ...mapActions(['setSubnetwork']), + }, +}; +</script> +<template> + <cluster-form-dropdown + :field-name="fieldName" + :value="selectedSubnetwork" + :items="items" + :disabled="!hasNetwork" + :loading="isLoadingItems" + :has-errors="Boolean(loadingItemsError)" + :loading-text="s__('ClusterIntegration|Loading subnetworks')" + :placeholder="s__('ClusterIntergation|Select a subnetwork')" + :search-field-placeholder="s__('ClusterIntegration|Search subnetworks')" + :empty-text="s__('ClusterIntegration|No subnetworks found')" + :error-message="s__('ClusterIntegration|Could not load subnetworks')" + :disabled-text="s__('ClusterIntegration|Select a network to choose a subnetwork')" + @input="setSubnetwork" + /> +</template> diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue index 45c890769a0..20b0c52dbda 100644 --- a/app/assets/javascripts/reports/components/report_section.vue +++ b/app/assets/javascripts/reports/components/report_section.vue @@ -165,21 +165,23 @@ export default { <template> <section class="media-section"> <div class="media"> - <status-icon :status="statusIconName" :size="24" /> - <div class="media-body d-flex flex-align-self-center"> - <span class="js-code-text code-text"> - {{ headerText }} - <slot :name="slotName"></slot> - - <popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" /> - </span> + <status-icon :status="statusIconName" :size="24" class="align-self-center" /> + <div class="media-body d-flex flex-align-self-center align-items-center"> + <div class="js-code-text code-text"> + <div> + {{ headerText }} + <slot :name="slotName"></slot> + <popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" /> + </div> + <slot name="subHeading"></slot> + </div> <slot name="actionButtons"></slot> <button v-if="isCollapsible" type="button" - class="js-collapse-btn btn float-right btn-sm align-self-start qa-expand-report-button" + class="js-collapse-btn btn float-right btn-sm align-self-center qa-expand-report-button" @click="toggleCollapsed" > {{ collapseText }} diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index dd392bd39a8..1d9f81aaa23 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -387,6 +387,7 @@ class ProjectsController < Projects::ApplicationController :merge_method, :initialize_with_readme, :autoclose_referenced_issues, + :suggestion_commit_message, project_feature_attributes: %i[ builds_access_level diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb index 5a0d53d9683..48da44123f6 100644 --- a/app/finders/pipelines_finder.rb +++ b/app/finders/pipelines_finder.rb @@ -17,7 +17,7 @@ class PipelinesFinder return Ci::Pipeline.none end - items = pipelines + items = pipelines.no_child items = by_scope(items) items = by_status(items) items = by_ref(items) diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 0bd2b0c81d9..31cde7b6d48 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -104,6 +104,8 @@ module Types description: 'Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project' field :autoclose_referenced_issues, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if issues referenced by merge requests and commits within the default branch are closed automatically' + field :suggestion_commit_message, GraphQL::STRING_TYPE, null: true, + description: 'The commit message used to apply merge request suggestions' field :namespace, Types::NamespaceType, null: true, description: 'Namespace of the project' diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 804e01bfab0..e6d41dd2779 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -53,6 +53,10 @@ module Ci def to_partial_path 'projects/generic_commit_statuses/generic_commit_status' end + + def yaml_for_downstream + nil + end end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 7a48fa8595b..5ad3a9b4431 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -60,7 +60,9 @@ module Ci has_one :chat_data, class_name: 'Ci::PipelineChatData' has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline + has_many :child_pipelines, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :sourced_pipelines, source: :pipeline has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline + has_one :parent_pipeline, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :source_pipeline, source: :source_pipeline has_one :source_job, through: :source_pipeline, source: :source_job has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline @@ -212,6 +214,7 @@ module Ci end scope :internal, -> { where(source: internal_sources) } + scope :no_child, -> { where.not(source: :parent_pipeline) } scope :ci_sources, -> { where(config_source: ::Ci::PipelineEnums.ci_config_sources_values) } scope :for_user, -> (user) { where(user: user) } scope :for_sha, -> (sha) { where(sha: sha) } @@ -507,10 +510,6 @@ module Ci builds.skipped.after_stage(stage_idx).find_each(&:process) end - def child? - false - end - def latest? return false unless git_ref && commit.present? @@ -693,6 +692,24 @@ module Ci all_merge_requests.order(id: :desc) end + # If pipeline is a child of another pipeline, include the parent + # and the siblings, otherwise return only itself. + def same_family_pipeline_ids + if (parent = parent_pipeline) + [parent.id] + parent.child_pipelines.pluck(:id) + else + [self.id] + end + end + + def child? + parent_pipeline.present? + end + + def parent? + child_pipelines.exists? + end + def detailed_status(current_user) Gitlab::Ci::Status::Pipeline::Factory .new(self, current_user) diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index 3cd88807969..fde169d2f03 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -23,10 +23,13 @@ module Ci schedule: 4, api: 5, external: 6, + # TODO: Rename `pipeline` to `cross_project_pipeline` in 13.0 + # https://gitlab.com/gitlab-org/gitlab/issues/195991 pipeline: 7, chat: 8, merge_request_event: 10, - external_pull_request_event: 11 + external_pull_request_event: 11, + parent_pipeline: 12 } end @@ -38,7 +41,8 @@ module Ci repository_source: 1, auto_devops_source: 2, remote_source: 4, - external_project_source: 5 + external_project_source: 5, + bridge_source: 6 } end diff --git a/app/models/ci/sources/pipeline.rb b/app/models/ci/sources/pipeline.rb index feaec27281c..d71e3b55b9a 100644 --- a/app/models/ci/sources/pipeline.rb +++ b/app/models/ci/sources/pipeline.rb @@ -18,6 +18,8 @@ module Ci validates :source_project, presence: true validates :source_job, presence: true validates :source_pipeline, presence: true + + scope :same_project, -> { where(arel_table[:source_project_id].eq(arel_table[:project_id])) } end end end diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb index 71589ac8315..a4ab1d399bc 100644 --- a/app/serializers/pipeline_details_entity.rb +++ b/app/serializers/pipeline_details_entity.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class PipelineDetailsEntity < PipelineEntity + expose :project, using: ProjectEntity + expose :flags do expose :latest?, as: :latest end diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index b25a1ea9209..be535a5d414 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -41,6 +41,7 @@ class PipelineSerializer < BaseSerializer def preloaded_relations [ :latest_statuses_ordered_by_stage, + :project, :stages, { failed_builds: %i(project metadata) diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index ce3a9eb0772..2daf3a51235 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -23,7 +23,7 @@ module Ci Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze # rubocop: disable Metrics/ParameterLists - def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, **options, &block) + def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, bridge: nil, **options, &block) @pipeline = Ci::Pipeline.new command = Gitlab::Ci::Pipeline::Chain::Command.new( @@ -46,6 +46,7 @@ module Ci current_user: current_user, push_options: params[:push_options] || {}, chat_data: params[:chat_data], + bridge: bridge, **extra_options(options)) sequence = Gitlab::Ci::Pipeline::Chain::Sequence @@ -104,14 +105,14 @@ module Ci if Feature.enabled?(:ci_support_interruptible_pipelines, project, default_enabled: true) project.ci_pipelines .where(ref: pipeline.ref) - .where.not(id: pipeline.id) + .where.not(id: pipeline.same_family_pipeline_ids) .where.not(sha: project.commit(pipeline.ref).try(:id)) .alive_or_scheduled .with_only_interruptible_builds else project.ci_pipelines .where(ref: pipeline.ref) - .where.not(id: pipeline.id) + .where.not(id: pipeline.same_family_pipeline_ids) .where.not(sha: project.commit(pipeline.ref).try(:id)) .created_or_pending end diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb index 8ba50e22b09..a6485e42bdb 100644 --- a/app/services/suggestions/apply_service.rb +++ b/app/services/suggestions/apply_service.rb @@ -2,6 +2,24 @@ module Suggestions class ApplyService < ::BaseService + DEFAULT_SUGGESTION_COMMIT_MESSAGE = 'Apply suggestion to %{file_path}' + + PLACEHOLDERS = { + 'project_path' => ->(suggestion, user) { suggestion.project.path }, + 'project_name' => ->(suggestion, user) { suggestion.project.name }, + 'file_path' => ->(suggestion, user) { suggestion.file_path }, + 'branch_name' => ->(suggestion, user) { suggestion.branch }, + 'username' => ->(suggestion, user) { user.username }, + 'user_full_name' => ->(suggestion, user) { user.name } + }.freeze + + # This regex is built dynamically using the keys from the PLACEHOLDER struct. + # So, we can easily add new placeholder just by modifying the PLACEHOLDER hash. + # This regex will build the new PLACEHOLDER_REGEX with the new information + PLACEHOLDERS_REGEX = Regexp.union(PLACEHOLDERS.keys.map { |key| Regexp.new(Regexp.escape(key)) }).freeze + + attr_reader :current_user + def initialize(current_user) @current_user = current_user end @@ -22,7 +40,7 @@ module Suggestions end params = file_update_params(suggestion, diff_file) - result = ::Files::UpdateService.new(suggestion.project, @current_user, params).execute + result = ::Files::UpdateService.new(suggestion.project, current_user, params).execute if result[:status] == :success suggestion.update(commit_id: result[:result], applied: true) @@ -46,13 +64,14 @@ module Suggestions def file_update_params(suggestion, diff_file) blob = diff_file.new_blob + project = suggestion.project file_path = suggestion.file_path branch_name = suggestion.branch file_content = new_file_content(suggestion, blob) - commit_message = "Apply suggestion to #{file_path}" + commit_message = processed_suggestion_commit_message(suggestion) file_last_commit = - Gitlab::Git::Commit.last_for_path(suggestion.project.repository, + Gitlab::Git::Commit.last_for_path(project.repository, blob.commit_id, blob.path) @@ -75,5 +94,17 @@ module Suggestions content.join end + + def suggestion_commit_message(project) + project.suggestion_commit_message || DEFAULT_SUGGESTION_COMMIT_MESSAGE + end + + def processed_suggestion_commit_message(suggestion) + message = suggestion_commit_message(suggestion.project) + + Gitlab::StringPlaceholderReplacer.replace_string_placeholders(message, PLACEHOLDERS_REGEX) do |key| + PLACEHOLDERS[key].call(suggestion, current_user) + end + end end end diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index f49cdfbf8da..08675c16124 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -8,7 +8,7 @@ = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) %div = f.label 'Two-Factor Authentication code', name: :otp_attempt - = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.' + = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.', inputmode: 'numeric', pattern: '[0-9]*' %p.form-text.text-muted.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. .prepend-top-20 = f.submit "Verify code", class: "btn btn-success" diff --git a/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml b/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml index 5d163d03c73..e3142ff96a1 100644 --- a/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml +++ b/app/views/devise/shared/_experimental_separate_sign_up_flow_box.html.haml @@ -5,7 +5,8 @@ = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f| .devise-errors.mt-0 = render "devise/shared/error_messages", resource: resource - = invisible_captcha + - if Feature.enabled?(:invisible_captcha) + = invisible_captcha .username.form-group = f.label :username, class: 'label-bold' = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") @@ -27,5 +28,8 @@ - accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link } = accept_terms_label.html_safe = render_if_exists 'devise/shared/email_opted_in', f: f + %div + - if show_recaptcha_sign_up? + = recaptcha_tags .submit-container.mt-3 = f.submit _("Register"), class: "btn-register btn btn-block btn-success mb-0 p-2", data: { qa_selector: 'new_user_register_button' } diff --git a/app/views/projects/_merge_request_merge_suggestions_settings.html.haml b/app/views/projects/_merge_request_merge_suggestions_settings.html.haml new file mode 100644 index 00000000000..06bb9056e61 --- /dev/null +++ b/app/views/projects/_merge_request_merge_suggestions_settings.html.haml @@ -0,0 +1,17 @@ +- form = local_assigns.fetch(:form) + +.form-group + %b= s_('ProjectSettings|Merge suggestions') + %p.text-secondary + = s_('ProjectSettings|The commit message used to apply merge request suggestions') + = link_to icon('question-circle'), + help_page_path('user/discussions/index.md', + anchor: 'configure-the-commit-message-for-applied-suggestions'), + target: '_blank' + .mb-2 + = form.text_field :suggestion_commit_message, class: 'form-control mb-2', placeholder: Suggestions::ApplyService::DEFAULT_SUGGESTION_COMMIT_MESSAGE + %p.form-text.text-muted + = s_('ProjectSettings|The variables GitLab supports:') + - Suggestions::ApplyService::PLACEHOLDERS.keys.each do |placeholder| + %code + = "%{#{placeholder}}".html_safe diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index f2ba38387a3..dc3a3fcc647 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -5,3 +5,5 @@ = render 'projects/merge_request_merge_options_settings', project: @project, form: form = render 'projects/merge_request_merge_checks_settings', project: @project, form: form + += render 'projects/merge_request_merge_suggestions_settings', project: @project, form: form diff --git a/app/views/projects/_merge_request_settings_description_text.html.haml b/app/views/projects/_merge_request_settings_description_text.html.haml index 42964c900b3..dc9dc92675d 100644 --- a/app/views/projects/_merge_request_settings_description_text.html.haml +++ b/app/views/projects/_merge_request_settings_description_text.html.haml @@ -1 +1 @@ -%p= s_('ProjectSettings|Choose your merge method, merge options, and merge checks.') +%p= s_('ProjectSettings|Choose your merge method, merge options, merge checks, and merge suggestions.') |