diff options
Diffstat (limited to 'app')
24 files changed, 171 insertions, 96 deletions
diff --git a/app/assets/javascripts/filtered_search/constants.js b/app/assets/javascripts/filtered_search/constants.js new file mode 100644 index 00000000000..8ca0e94cfeb --- /dev/null +++ b/app/assets/javascripts/filtered_search/constants.js @@ -0,0 +1,2 @@ +/* eslint-disable import/prefer-default-export */ +export const TOKEN_TYPES = ['author', 'assignee']; diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 315cd6f64da..7f6457242ef 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -1,4 +1,4 @@ -import VisualTokenValue from 'ee_else_ce/filtered_search/visual_token_value'; +import VisualTokenValue from './visual_token_value'; import { objectToQueryString } from '~/lib/utils/common_utils'; import FilteredSearchContainer from './container'; diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js index a54b445fb0a..018207541b3 100644 --- a/app/assets/javascripts/filtered_search/visual_token_value.js +++ b/app/assets/javascripts/filtered_search/visual_token_value.js @@ -6,6 +6,7 @@ import DropdownUtils from '~/filtered_search/dropdown_utils'; import Flash from '~/flash'; import UsersCache from '~/lib/utils/users_cache'; import { __ } from '~/locale'; +import { TOKEN_TYPES } from 'ee_else_ce/filtered_search/constants'; export default class VisualTokenValue { constructor(tokenValue, tokenType) { @@ -22,7 +23,7 @@ export default class VisualTokenValue { if (tokenType === 'label') { this.updateLabelTokenColor(tokenValueContainer); - } else if (tokenType === 'author' || tokenType === 'assignee') { + } else if (TOKEN_TYPES.includes(tokenType)) { this.updateUserTokenAppearance(tokenValueContainer, tokenValueElement); } else if (tokenType === 'my-reaction') { this.updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement); diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue index 92e20e92d66..d611b370ab9 100644 --- a/app/assets/javascripts/jobs/components/job_log.vue +++ b/app/assets/javascripts/jobs/components/job_log.vue @@ -17,10 +17,19 @@ export default { ...mapState(['isScrolledToBottomBeforeReceivingTrace']), }, updated() { - this.$nextTick(() => this.handleScrollDown()); + this.$nextTick(() => { + this.handleScrollDown(); + this.handleCollapsibleRows(); + }); }, mounted() { - this.$nextTick(() => this.handleScrollDown()); + this.$nextTick(() => { + this.handleScrollDown(); + this.handleCollapsibleRows(); + }); + }, + destroyed() { + this.removeEventListener(); }, methods: { ...mapActions(['scrollBottom']), @@ -38,21 +47,45 @@ export default { }, 0); } }, + removeEventListener() { + this.$el + .querySelectorAll('.js-section-start') + .forEach(el => el.removeEventListener('click', this.handleSectionClick)); + }, + /** + * The collapsible rows are sent in HTML from the backend + * We need tos add a onclick handler for the divs that match `.js-section-start` + * + */ + handleCollapsibleRows() { + this.$el + .querySelectorAll('.js-section-start') + .forEach(el => el.addEventListener('click', this.handleSectionClick)); + }, + /** + * On click, we toggle the hidden class of + * all the rows that match the `data-section` selector + */ + handleSectionClick(evt) { + const clickedArrow = evt.currentTarget; + // toggle the arrow class + clickedArrow.classList.toggle('fa-caret-right'); + clickedArrow.classList.toggle('fa-caret-down'); + + const { section } = clickedArrow.dataset; + const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`); + + sibilings.forEach(row => row.classList.toggle('hidden')); + }, }, }; </script> <template> <pre class="js-build-trace build-trace qa-build-trace"> - <code - class="bash" - v-html="trace" - > + <code class="bash" v-html="trace"> </code> - <div - v-if="!isComplete" - class="js-log-animation build-loader-animation" - > + <div v-if="!isComplete" class="js-log-animation build-loader-animation"> <div class="dot"></div> <div class="dot"></div> <div class="dot"></div> diff --git a/app/assets/javascripts/mr_popover/queries/merge_request.graphql b/app/assets/javascripts/mr_popover/queries/merge_request.graphql index 0bb9bc03bc7..37d4bc88a69 100644 --- a/app/assets/javascripts/mr_popover/queries/merge_request.graphql +++ b/app/assets/javascripts/mr_popover/queries/merge_request.graphql @@ -1,4 +1,4 @@ -query mergeRequest($projectPath: ID!, $mergeRequestIID: ID!) { +query mergeRequest($projectPath: ID!, $mergeRequestIID: String!) { project(fullPath: $projectPath) { mergeRequest(iid: $mergeRequestIID) { createdAt diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index abe5bdd2901..34cdb70ce14 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -49,6 +49,7 @@ export default { required: false, default: () => ({ sourceProjectId: '', + sourceProjectPath: '', mergeRequestId: '', appUrl: '', }), @@ -184,11 +185,6 @@ export default { :link="deploymentExternalUrl" :css-class="`deploy-link js-deploy-url inline ${slotProps.className}`" /> - <visual-review-app-link - v-if="showVisualReviewApp" - :link="deploymentExternalUrl" - :app-metadata="visualReviewAppMeta" - /> </template> <template slot="result" slot-scope="slotProps"> @@ -213,12 +209,12 @@ export default { :link="deploymentExternalUrl" css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline" /> - <visual-review-app-link - v-if="showVisualReviewApp" - :link="deploymentExternalUrl" - :app-metadata="visualReviewAppMeta" - /> </template> + <visual-review-app-link + v-if="showVisualReviewApp" + :link="deploymentExternalUrl" + :app-metadata="visualReviewAppMeta" + /> </template> <span v-if="deployment.stop_url" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue index ba9a4d2a187..17ac8ada32d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue @@ -53,6 +53,7 @@ export default { appUrl: this.mr.appUrl, mergeRequestId: this.mr.iid, sourceProjectId: this.mr.sourceProjectId, + sourceProjectPath: this.mr.sourceProjectFullPath, }; }, pipeline() { @@ -85,7 +86,7 @@ export default { :class="deploymentClass" :deployment="deployment" :show-metrics="hasDeploymentMetrics" - :show-visual-review-app="true" + :show-visual-review-app="showVisualReviewAppLink" :visual-review-app-meta="visualReviewAppMeta" /> </div> diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 6fc742871e7..6e98908eeed 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -124,6 +124,10 @@ float: left; padding-left: $gl-padding-8; } + + .section-header ~ .section.line { + margin-left: $gl-padding; + } } .build-header { diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 0a9c56f5625..3b62121eb0d 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -340,6 +340,11 @@ .deprecated-service { cursor: default; + + a { + font-weight: $gl-font-weight-bold; + color: $white-light; + } } .personal-access-tokens-never-expires-label { diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 80ee7c35906..ec8077d18e3 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -128,6 +128,7 @@ class Clusters::ClustersController < Clusters::BaseController :enabled, :name, :environment_scope, + :managed, :base_domain, platform_kubernetes_attributes: [ :api_url, @@ -140,6 +141,7 @@ class Clusters::ClustersController < Clusters::BaseController params.require(:cluster).permit( :enabled, :environment_scope, + :managed, :base_domain, platform_kubernetes_attributes: [ :namespace diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 9cf25915e92..88a0690938a 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -104,6 +104,12 @@ module IssuableCollections # Used by view to highlight active option @sort = options[:sort] + # When a user looks for an exact iid, we do not filter by search but only by iid + if params[:search] =~ /^#(?<iid>\d+)\z/ + options[:iids] = Regexp.last_match[:iid] + params[:search] = nil + end + if @project options[:project_id] = @project.id options[:attempt_project_search_optimizations] = true diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index d4b50b7ecfb..01ccf163b45 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -39,7 +39,7 @@ module ServicesHelper end def disable_fields_service?(service) - !current_controller?("admin/services") && service.deprecated? + service.is_a?(KubernetesService) || (!current_controller?("admin/services") && service.deprecated?) end extend self diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index ccc877fb924..8c044c86c47 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -193,21 +193,40 @@ module Clusters platform_kubernetes.kubeclient if kubernetes? end + ## + # This is subtly different to #find_or_initialize_kubernetes_namespace_for_project + # below because it will ignore any namespaces that have not got a service account + # token. This provides a guarantee that any namespace selected here can be used + # for cluster operations - a namespace needs to have a service account configured + # before it it can be used. + # + # This is used for selecting a namespace to use when querying a cluster, or + # generating variables to pass to CI. def kubernetes_namespace_for(project) - find_or_initialize_kubernetes_namespace_for_project(project).namespace + find_or_initialize_kubernetes_namespace_for_project( + project, scope: kubernetes_namespaces.has_service_account_token + ).namespace end - def find_or_initialize_kubernetes_namespace_for_project(project) + ## + # This is subtly different to #kubernetes_namespace_for because it will include + # namespaces that have yet to receive a service account token. This allows + # the namespace configuration process to be repeatable - if a namespace has + # already been created without a token we don't need to create another + # record entirely, just set the token on the pre-existing namespace. + # + # This is used for configuring cluster namespaces. + def find_or_initialize_kubernetes_namespace_for_project(project, scope: kubernetes_namespaces) attributes = { project: project } attributes[:cluster_project] = cluster_project if project_type? - kubernetes_namespaces.find_or_initialize_by(attributes).tap do |namespace| + scope.find_or_initialize_by(attributes).tap do |namespace| namespace.set_defaults end end def allow_user_defined_namespace? - project_type? + project_type? || !managed? end def kube_ingress_domain diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 8e06156c73d..272861cacf0 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -80,9 +80,18 @@ module Clusters .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true) end - if kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project) + if !cluster.managed? + project_namespace = namespace.presence || "#{project.path}-#{project.id}".downcase + + variables + .append(key: 'KUBE_URL', value: api_url) + .append(key: 'KUBE_TOKEN', value: token, public: false, masked: true) + .append(key: 'KUBE_NAMESPACE', value: project_namespace) + .append(key: 'KUBECONFIG', value: kubeconfig(project_namespace), public: false, file: true) + + elsif kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project) variables.concat(kubernetes_namespace.predefined_variables) - elsif cluster.project_type? || !cluster.managed? + elsif cluster.project_type? # As of 11.11 a user can create a cluster that they manage themselves, # which replicates the existing project-level cluster behaviour. # Once we have marked all project-level clusters that make use of this diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb deleted file mode 100644 index 80aa2101509..00000000000 --- a/app/models/project_services/deployment_service.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -# Base class for deployment services -# -# These services integrate with a deployment solution like Kubernetes/OpenShift, -# Mesosphere, etc, to provide additional features to environments. -class DeploymentService < Service - default_value_for :category, 'deployment' - - def self.supported_events - %w() - end - - def predefined_variables(project:) - [] - end - - # Environments may have a number of terminals. Should return an array of - # hashes describing them, e.g.: - # - # [{ - # :selectors => {"a" => "b", "foo" => "bar"}, - # :url => "wss://external.example.com/exec", - # :headers => {"Authorization" => "Token xxx"}, - # :subprotocols => ["foo"], - # :ca_pem => "----BEGIN CERTIFICATE...", # optional - # :created_at => Time.now.utc - # }] - # - # Selectors should be a set of values that uniquely identify a particular - # terminal - def terminals(environment) - raise NotImplementedError - end - - def can_test? - false - end -end diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index aa6b4aa1d5e..edf7e886e77 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -5,10 +5,12 @@ # We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic. # After we've migrated data, we'll remove KubernetesService. This would happen in a few months. # If you're modyfiyng this class, please note that you should update the same change in Clusters::Platforms::Kubernetes. -class KubernetesService < DeploymentService +class KubernetesService < Service include Gitlab::Kubernetes include ReactiveCaching + default_value_for :category, 'deployment' + self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] } # Namespace defaults to the project path, but can be overridden in case that @@ -32,7 +34,10 @@ class KubernetesService < DeploymentService before_validation :enforce_namespace_to_lower_case - validate :deprecation_validation, unless: :template? + attr_accessor :skip_deprecation_validation + + validate :deprecation_validation, unless: :skip_deprecation_validation + validates :namespace, allow_blank: true, length: 1..63, @@ -44,6 +49,14 @@ class KubernetesService < DeploymentService after_save :clear_reactive_cache! + def self.supported_events + %w() + end + + def can_test? + false + end + def initialize_properties self.properties = {} if properties.nil? end @@ -56,11 +69,6 @@ class KubernetesService < DeploymentService 'Kubernetes / OpenShift integration' end - def help - 'To enable terminal access to Kubernetes environments, label your ' \ - 'deployments with `app=$CI_ENVIRONMENT_SLUG`' - end - def self.to_param 'kubernetes' end @@ -153,14 +161,25 @@ class KubernetesService < DeploymentService end def deprecated? - !active + true + end + + def editable? + false end def deprecation_message - content = _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % { - deprecated_message_content: deprecated_message_content, - url: Gitlab::Routing.url_helpers.project_clusters_path(project) - } + content = if project + _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % { + deprecated_message_content: deprecated_message_content, + url: Gitlab::Routing.url_helpers.project_clusters_path(project) + } + else + _("The instance-level Kubernetes service integration is deprecated. Your data has been migrated to an <a href=\"%{url}\"/>instance-level cluster</a>.") % { + url: Gitlab::Routing.url_helpers.admin_clusters_path + } + end + content.html_safe end @@ -243,10 +262,6 @@ class KubernetesService < DeploymentService end def deprecated_message_content - if active? - _("Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure") - else - _("Fields on this page are now uneditable, you can configure") - end + _("Fields on this page are now uneditable, you can configure") end end diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb index 7ab1687f8ba..1103cb11e73 100644 --- a/app/models/project_services/mock_deployment_service.rb +++ b/app/models/project_services/mock_deployment_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true -class MockDeploymentService < DeploymentService +class MockDeploymentService < Service + default_value_for :category, 'deployment' + def title 'Mock deployment' end @@ -17,4 +19,16 @@ class MockDeploymentService < DeploymentService def terminals(environment) [] end + + def self.supported_events + %w() + end + + def predefined_variables(project:) + [] + end + + def can_test? + false + end end diff --git a/app/models/service.rb b/app/models/service.rb index 16fbe6648cc..40033003f3b 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -120,7 +120,7 @@ class Service < ApplicationRecord end def self.event_names - self.supported_events.map { |event| "#{event}_events" } + self.supported_events.map { |event| ServicesHelper.service_event_field_name(event) } end def event_field(event) @@ -152,7 +152,7 @@ class Service < ApplicationRecord end def self.supported_events - %w(push tag_push issue confidential_issue merge_request wiki_page) + %w(commit push tag_push issue confidential_issue merge_request wiki_page) end def execute(data) diff --git a/app/views/admin/services/_deprecated_message.html.haml b/app/views/admin/services/_deprecated_message.html.haml new file mode 100644 index 00000000000..fea9506a4bb --- /dev/null +++ b/app/views/admin/services/_deprecated_message.html.haml @@ -0,0 +1,3 @@ +.flash-container.flash-container-page + .flash-alert.deprecated-service + %span= @service.deprecation_message diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml index 1798b44bbb7..97373a3c350 100644 --- a/app/views/admin/services/_form.html.haml +++ b/app/views/admin/services/_form.html.haml @@ -6,5 +6,6 @@ = form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'fieldset-form' } do |form| = render 'shared/service_settings', form: form, subject: @service - .footer-block.row-content-block - = form.submit 'Save', class: 'btn btn-success' + - unless @service.is_a?(KubernetesService) + .footer-block.row-content-block + = form.submit 'Save', class: 'btn btn-success' diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml index 512176649e6..79f5ab0d77d 100644 --- a/app/views/admin/services/edit.html.haml +++ b/app/views/admin/services/edit.html.haml @@ -1,4 +1,7 @@ - add_to_breadcrumbs "Service Templates", admin_application_settings_services_path - breadcrumb_title @service.title - page_title @service.title, "Service Templates" + += render 'deprecated_message' if @service.deprecation_message + = render 'form' diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml index c1727cf9079..f2e44462226 100644 --- a/app/views/clusters/platforms/kubernetes/_form.html.haml +++ b/app/views/clusters/platforms/kubernetes/_form.html.haml @@ -48,7 +48,7 @@ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') .form-group - = field.check_box :managed, { disabled: true, label: s_('ClusterIntegration|GitLab-managed cluster'), + = field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'), label_class: 'label-bold' } .form-text.text-muted = s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.') diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml index 7f2c9dcacfd..4f09f47d795 100644 --- a/app/views/projects/merge_requests/_mr_box.html.haml +++ b/app/views/projects/merge_requests/_mr_box.html.haml @@ -1,10 +1,10 @@ .detail-page-description - %h2.title + %h2.title.qa-title = markdown_field(@merge_request, :title) %div - if @merge_request.description.present? - .description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' } + .description.qa-description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' } .md = markdown_field(@merge_request, :description) %textarea.hidden.js-task-list-field diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml index 7c7c0a363ac..4365e3f6877 100644 --- a/app/views/shared/projects/_search_form.html.haml +++ b/app/views/shared/projects/_search_form.html.haml @@ -1,7 +1,7 @@ - form_field_classes = local_assigns[:admin_view] || !Feature.enabled?(:project_list_filter_bar) ? 'input-short js-projects-list-filter' : '' - placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : 'Filter by name...' -= form_tag filter_projects_path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| += form_tag filter_projects_path, method: :get, class: 'project-filter-form qa-project-filter-form', id: 'project-filter-form' do |f| = search_field_tag :name, params[:name], placeholder: placeholder, class: "project-filter-form-field form-control #{form_field_classes}", |