diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-20 18:08:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-20 18:08:44 +0300 |
commit | b9bac6dbf78a5a7976fba14aaeef96bdeb0da612 (patch) | |
tree | ffe277b562256f718b0818e8fd3c8fd8766d0269 | |
parent | 8c4198cbe631278e87fee04157d23494fbb80cdb (diff) |
Add latest changes from gitlab-org/gitlab@master
46 files changed, 790 insertions, 441 deletions
diff --git a/.gitlab/issue_templates/Technical Evaluation.md b/.gitlab/issue_templates/Technical Evaluation.md new file mode 100644 index 00000000000..f703f727113 --- /dev/null +++ b/.gitlab/issue_templates/Technical Evaluation.md @@ -0,0 +1,23 @@ +<!-- Instructions: Use this template for a proof of concept or when a deeper technical evaluation is required. Please weigh tech evaluation issues and follow the instructions below accordingly. --> + +### Topic to Evaluate + +<!-- Describe the related issue and challenge we need to establish a proof of concept for--> +* [Link to other Issue](link) + +### Tasks to Evaluate + +<!-- Outline the tasks with issues that you need evaluate as a part of the implementation issue --> + +- [ ] Add task +- [ ] Add task +- [ ] Add task + +### Risks and Implementation Considerations + +<!-- Idenitfy any risks found in the research, whether this is performance, impacts to other functionality or other bugs --> + +### Team + +- [ ] Add ~"workflow::planning breakdown" ~feature and the corresponding `~devops::<stage>` and `~group::<group>` labels. +- [ ] Ping the PM and EM. diff --git a/app/assets/javascripts/monitoring/components/charts/column.vue b/app/assets/javascripts/monitoring/components/charts/column.vue index 0acdfe7675c..82857424ff7 100644 --- a/app/assets/javascripts/monitoring/components/charts/column.vue +++ b/app/assets/javascripts/monitoring/components/charts/column.vue @@ -90,11 +90,7 @@ export default { }; </script> <template> - <div v-gl-resize-observer-directive="onResize" class="prometheus-graph"> - <div class="prometheus-graph-header"> - <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5> - <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div> - </div> + <div v-gl-resize-observer-directive="onResize"> <gl-column-chart ref="columnChart" v-bind="$attrs" diff --git a/app/assets/javascripts/monitoring/components/charts/empty_chart.vue b/app/assets/javascripts/monitoring/components/charts/empty_chart.vue index eedc5162e0c..5588d9ac060 100644 --- a/app/assets/javascripts/monitoring/components/charts/empty_chart.vue +++ b/app/assets/javascripts/monitoring/components/charts/empty_chart.vue @@ -27,10 +27,7 @@ export default { }; </script> <template> - <div class="prometheus-graph d-flex flex-column justify-content-center"> - <div class="prometheus-graph-header"> - <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5> - </div> + <div class="d-flex flex-column justify-content-center"> <div class="prepend-top-8 svg-w-100 d-flex align-items-center" :style="svgContainerStyle" diff --git a/app/assets/javascripts/monitoring/components/charts/heatmap.vue b/app/assets/javascripts/monitoring/components/charts/heatmap.vue index 881904cbd0c..0a0165a113e 100644 --- a/app/assets/javascripts/monitoring/components/charts/heatmap.vue +++ b/app/assets/javascripts/monitoring/components/charts/heatmap.vue @@ -2,13 +2,11 @@ import { GlResizeObserverDirective } from '@gitlab/ui'; import { GlHeatmap } from '@gitlab/ui/dist/charts'; import dateformat from 'dateformat'; -import PrometheusHeader from '../shared/prometheus_header.vue'; import { graphDataValidatorForValues } from '../../utils'; export default { components: { GlHeatmap, - PrometheusHeader, }, directives: { GlResizeObserverDirective, @@ -65,8 +63,7 @@ export default { }; </script> <template> - <div v-gl-resize-observer-directive="onResize" class="prometheus-graph col-12 col-lg-6"> - <prometheus-header :graph-title="graphData.title" /> + <div v-gl-resize-observer-directive="onResize" class="col-12 col-lg-6"> <gl-heatmap ref="heatmapChart" v-bind="$attrs" diff --git a/app/assets/javascripts/monitoring/components/charts/single_stat.vue b/app/assets/javascripts/monitoring/components/charts/single_stat.vue index 3368be4df75..225fcfda165 100644 --- a/app/assets/javascripts/monitoring/components/charts/single_stat.vue +++ b/app/assets/javascripts/monitoring/components/charts/single_stat.vue @@ -42,10 +42,7 @@ export default { }; </script> <template> - <div class="prometheus-graph"> - <div class="prometheus-graph-header"> - <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5> - </div> + <div> <gl-single-stat :value="statValue" :title="graphTitle" variant="success" /> </div> </template> diff --git a/app/assets/javascripts/monitoring/components/charts/stacked_column.vue b/app/assets/javascripts/monitoring/components/charts/stacked_column.vue index 55ae4a3bdb2..56a747b9d1f 100644 --- a/app/assets/javascripts/monitoring/components/charts/stacked_column.vue +++ b/app/assets/javascripts/monitoring/components/charts/stacked_column.vue @@ -81,11 +81,7 @@ export default { }; </script> <template> - <div v-gl-resize-observer-directive="onResize" class="prometheus-graph"> - <div class="prometheus-graph-header"> - <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5> - <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div> - </div> + <div v-gl-resize-observer-directive="onResize"> <gl-stacked-column-chart ref="chart" v-bind="$attrs" diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue index d2b1e4da3fd..8abb16f58ca 100644 --- a/app/assets/javascripts/monitoring/components/charts/time_series.vue +++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue @@ -112,7 +112,6 @@ export default { isDeployment: false, sha: '', }, - showTitleTooltip: false, width: 0, height: chartHeight, svgs: {}, @@ -285,12 +284,6 @@ export default { return `${this.graphData.y_label}`; }, }, - mounted() { - const graphTitleEl = this.$refs.graphTitle; - if (graphTitleEl && graphTitleEl.scrollWidth > graphTitleEl.offsetWidth) { - this.showTitleTooltip = true; - } - }, created() { this.setSvg('rocket'); this.setSvg('scroll-handle'); @@ -387,24 +380,7 @@ export default { </script> <template> - <div v-gl-resize-observer-directive="onResize" class="prometheus-graph"> - <div class="prometheus-graph-header"> - <h5 - ref="graphTitle" - class="prometheus-graph-title js-graph-title text-truncate append-right-8" - > - {{ graphData.title }} - </h5> - <gl-tooltip :target="() => $refs.graphTitle" :disabled="!showTitleTooltip"> - {{ graphData.title }} - </gl-tooltip> - <div - class="prometheus-graph-widgets js-graph-widgets flex-fill" - data-qa-selector="prometheus_graph_widgets" - > - <slot></slot> - </div> - </div> + <div v-gl-resize-observer-directive="onResize"> <component :is="glChartComponent" ref="chart" diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue index 22fab1b03f2..f2f0fff694e 100644 --- a/app/assets/javascripts/monitoring/components/panel_type.vue +++ b/app/assets/javascripts/monitoring/components/panel_type.vue @@ -3,10 +3,12 @@ import { mapState } from 'vuex'; import { pickBy } from 'lodash'; import invalidUrl from '~/lib/utils/invalid_url'; import { + GlResizeObserverDirective, GlDropdown, GlDropdownItem, GlModal, GlModalDirective, + GlTooltip, GlTooltipDirective, } from '@gitlab/ui'; import { __ } from '~/locale'; @@ -29,11 +31,13 @@ export default { MonitorStackedColumnChart, MonitorEmptyChart, Icon, + GlTooltip, GlDropdown, GlDropdownItem, GlModal, }, directives: { + GlResizeObserver: GlResizeObserverDirective, GlModal: GlModalDirective, GlTooltip: GlTooltipDirective, TrackEvent: TrackEventDirective, @@ -61,11 +65,15 @@ export default { }, data() { return { + showTitleTooltip: false, zoomedTimeRange: null, }; }, computed: { ...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']), + title() { + return this.graphData.title || ''; + }, alertWidgetAvailable() { return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData; }, @@ -97,12 +105,24 @@ export default { const data = new Blob([this.csvText], { type: 'text/plain' }); return window.URL.createObjectURL(data); }, - monitorChartComponent() { + timeChartComponent() { if (this.isPanelType('anomaly-chart')) { return MonitorAnomalyChart; } return MonitorTimeSeriesChart; }, + isContextualMenuShown() { + return ( + this.graphDataHasMetrics && + !this.isPanelType('single-stat') && + !this.isPanelType('heatmap') && + !this.isPanelType('column') && + !this.isPanelType('stacked-column') + ); + }, + }, + mounted() { + this.refreshTitleTooltip(); }, methods: { getGraphAlerts(queries) { @@ -119,9 +139,18 @@ export default { showToast() { this.$toast.show(__('Link copied')); }, + refreshTitleTooltip() { + const { graphTitle } = this.$refs; + this.showTitleTooltip = + Boolean(graphTitle) && graphTitle.scrollWidth > graphTitle.offsetWidth; + }, + downloadCSVOptions, generateLinkToChartOptions, + onResize() { + this.refreshTitleTooltip(); + }, onDatazoom({ start, end }) { this.zoomedTimeRange = { start, end }; }, @@ -129,88 +158,109 @@ export default { }; </script> <template> - <monitor-single-stat-chart - v-if="isPanelType('single-stat') && graphDataHasMetrics" - :graph-data="graphData" - /> - <monitor-heatmap-chart - v-else-if="isPanelType('heatmap') && graphDataHasMetrics" - :graph-data="graphData" - /> - <monitor-column-chart - v-else-if="isPanelType('column') && graphDataHasMetrics" - :graph-data="graphData" - /> - <monitor-stacked-column-chart - v-else-if="isPanelType('stacked-column') && graphDataHasMetrics" - :graph-data="graphData" - /> - <component - :is="monitorChartComponent" - v-else-if="graphDataHasMetrics" - ref="timeChart" - :graph-data="graphData" - :deployment-data="deploymentData" - :project-path="projectPath" - :thresholds="getGraphAlertValues(graphData.metrics)" - :group-id="groupId" - @datazoom="onDatazoom" - > - <div class="d-flex align-items-center"> - <alert-widget - v-if="alertWidgetAvailable && graphData" - :modal-id="`alert-modal-${index}`" - :alerts-endpoint="alertsEndpoint" - :relevant-queries="graphData.metrics" - :alerts-to-manage="getGraphAlerts(graphData.metrics)" - @setAlerts="setAlerts" - /> - <gl-dropdown - v-gl-tooltip - class="ml-auto mx-3" - toggle-class="btn btn-transparent border-0" - data-qa-selector="prometheus_widgets_dropdown" - :right="true" - :no-caret="true" - :title="__('More actions')" + <div v-gl-resize-observer="onResize" class="prometheus-graph"> + <div class="prometheus-graph-header"> + <h5 + ref="graphTitle" + class="prometheus-graph-title gl-font-size-large font-weight-bold text-truncate append-right-8" > - <template slot="button-content"> - <icon name="ellipsis_v" class="text-secondary" /> - </template> + {{ title }} + </h5> + <gl-tooltip :target="() => $refs.graphTitle" :disabled="!showTitleTooltip"> + {{ title }} + </gl-tooltip> + <div + v-if="isContextualMenuShown" + class="prometheus-graph-widgets js-graph-widgets flex-fill" + data-qa-selector="prometheus_graph_widgets" + > + <div class="d-flex align-items-center"> + <alert-widget + v-if="alertWidgetAvailable && graphData" + :modal-id="`alert-modal-${index}`" + :alerts-endpoint="alertsEndpoint" + :relevant-queries="graphData.metrics" + :alerts-to-manage="getGraphAlerts(graphData.metrics)" + @setAlerts="setAlerts" + /> + <gl-dropdown + v-gl-tooltip + class="ml-auto mx-3" + toggle-class="btn btn-transparent border-0" + data-qa-selector="prometheus_widgets_dropdown" + right + no-caret + :title="__('More actions')" + > + <template slot="button-content"> + <icon name="ellipsis_v" class="text-secondary" /> + </template> - <gl-dropdown-item - v-if="logsPathWithTimeRange" - ref="viewLogsLink" - :href="logsPathWithTimeRange" - > - {{ s__('Metrics|View logs') }} - </gl-dropdown-item> + <gl-dropdown-item + v-if="logsPathWithTimeRange" + ref="viewLogsLink" + :href="logsPathWithTimeRange" + > + {{ s__('Metrics|View logs') }} + </gl-dropdown-item> - <gl-dropdown-item - v-track-event="downloadCSVOptions(graphData.title)" - :href="downloadCsv" - download="chart_metrics.csv" - > - {{ __('Download CSV') }} - </gl-dropdown-item> - <gl-dropdown-item - v-if="clipboardText" - ref="copyChartLink" - v-track-event="generateLinkToChartOptions(clipboardText)" - :data-clipboard-text="clipboardText" - @click="showToast(clipboardText)" - > - {{ __('Generate link to chart') }} - </gl-dropdown-item> - <gl-dropdown-item - v-if="alertWidgetAvailable" - v-gl-modal="`alert-modal-${index}`" - data-qa-selector="alert_widget_menu_item" - > - {{ __('Alerts') }} - </gl-dropdown-item> - </gl-dropdown> + <gl-dropdown-item + v-if="csvText" + ref="downloadCsvLink" + v-track-event="downloadCSVOptions(title)" + :href="downloadCsv" + download="chart_metrics.csv" + > + {{ __('Download CSV') }} + </gl-dropdown-item> + <gl-dropdown-item + v-if="clipboardText" + ref="copyChartLink" + v-track-event="generateLinkToChartOptions(clipboardText)" + :data-clipboard-text="clipboardText" + @click="showToast(clipboardText)" + > + {{ __('Generate link to chart') }} + </gl-dropdown-item> + <gl-dropdown-item + v-if="alertWidgetAvailable" + v-gl-modal="`alert-modal-${index}`" + data-qa-selector="alert_widget_menu_item" + > + {{ __('Alerts') }} + </gl-dropdown-item> + </gl-dropdown> + </div> + </div> </div> - </component> - <monitor-empty-chart v-else :graph-title="graphData.title" /> + + <monitor-single-stat-chart + v-if="isPanelType('single-stat') && graphDataHasMetrics" + :graph-data="graphData" + /> + <monitor-heatmap-chart + v-else-if="isPanelType('heatmap') && graphDataHasMetrics" + :graph-data="graphData" + /> + <monitor-column-chart + v-else-if="isPanelType('column') && graphDataHasMetrics" + :graph-data="graphData" + /> + <monitor-stacked-column-chart + v-else-if="isPanelType('stacked-column') && graphDataHasMetrics" + :graph-data="graphData" + /> + <component + :is="timeChartComponent" + v-else-if="graphDataHasMetrics" + ref="timeChart" + :graph-data="graphData" + :deployment-data="deploymentData" + :project-path="projectPath" + :thresholds="getGraphAlertValues(graphData.metrics)" + :group-id="groupId" + @datazoom="onDatazoom" + /> + <monitor-empty-chart v-else :graph-title="title" v-bind="$attrs" v-on="$listeners" /> + </div> </template> diff --git a/app/assets/javascripts/monitoring/components/shared/prometheus_header.vue b/app/assets/javascripts/monitoring/components/shared/prometheus_header.vue deleted file mode 100644 index ceeec51ee65..00000000000 --- a/app/assets/javascripts/monitoring/components/shared/prometheus_header.vue +++ /dev/null @@ -1,15 +0,0 @@ -<script> -export default { - props: { - graphTitle: { - type: String, - required: true, - }, - }, -}; -</script> -<template> - <div class="prometheus-graph-header"> - <h5 ref="title" class="prometheus-graph-title">{{ graphTitle }}</h5> - </div> -</template> diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss index 8133a167687..30ef047bf04 100644 --- a/app/assets/stylesheets/pages/prometheus.scss +++ b/app/assets/stylesheets/pages/prometheus.scss @@ -91,10 +91,6 @@ margin-bottom: $gl-padding-8; } -.prometheus-graph-title { - font-size: $gl-font-size-large; -} - .alert-current-setting { max-width: 240px; } diff --git a/app/finders/serverless_domain_finder.rb b/app/finders/serverless_domain_finder.rb new file mode 100644 index 00000000000..3a8a55022bb --- /dev/null +++ b/app/finders/serverless_domain_finder.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +class ServerlessDomainFinder + attr_reader :match, :serverless_domain_cluster, :environment + + def initialize(uri) + @match = ::Serverless::Domain::REGEXP.match(uri) + end + + def execute + return unless serverless? + + @serverless_domain_cluster = ::Serverless::DomainCluster.for_uuid(serverless_domain_cluster_uuid) + return unless serverless_domain_cluster + + @environment = ::Environment.for_id_and_slug(match[:environment_id].to_i(16), match[:environment_slug]) + return unless environment + + ::Serverless::Domain.new( + function_name: match[:function_name], + serverless_domain_cluster: serverless_domain_cluster, + environment: environment + ) + end + + def serverless_domain_cluster_uuid + return unless serverless? + + match[:cluster_left] + match[:cluster_middle] + match[:cluster_right] + end + + def serverless? + !!match + end +end diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb index b94f2b15846..297d00aa281 100644 --- a/app/models/clusters/concerns/application_core.rb +++ b/app/models/clusters/concerns/application_core.rb @@ -15,7 +15,7 @@ module Clusters def set_initial_status return unless not_installable? - self.status = status_states[:installable] if cluster&.application_helm_available? || Feature.enabled?(:managed_apps_local_tiller) + self.status = status_states[:installable] if cluster&.application_helm_available? || ::Gitlab::Kubernetes::Helm.local_tiller_enabled? end def can_uninstall? diff --git a/app/models/clusters/concerns/application_data.rb b/app/models/clusters/concerns/application_data.rb index 3479fea415e..77c606553d2 100644 --- a/app/models/clusters/concerns/application_data.rb +++ b/app/models/clusters/concerns/application_data.rb @@ -23,7 +23,7 @@ module Clusters @files ||= begin files = { 'values.yaml': values } - files.merge!(certificate_files) if cluster.application_helm.has_ssl? + files.merge!(certificate_files) if use_tiller_ssl? files end @@ -31,6 +31,12 @@ module Clusters private + def use_tiller_ssl? + return false if ::Gitlab::Kubernetes::Helm.local_tiller_enabled? + + cluster.application_helm.has_ssl? + end + def certificate_files { 'ca.pem': ca_cert, diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb index b63a596dfee..14237439a8d 100644 --- a/app/models/clusters/concerns/application_status.rb +++ b/app/models/clusters/concerns/application_status.rb @@ -92,7 +92,10 @@ module Clusters # When installing any application we are also performing an update # of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so # therefore we need to reflect that in the database. - application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) + + unless ::Gitlab::Kubernetes::Helm.local_tiller_enabled? + application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) + end end after_transition any => [:uninstalling], :use_transactions => false do |application, _| diff --git a/app/models/environment.rb b/app/models/environment.rb index bb41c4a066e..4224a32a6d7 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -95,6 +95,10 @@ class Environment < ApplicationRecord end end + def self.for_id_and_slug(id, slug) + find_by(id: id, slug: slug) + end + def self.max_deployment_id_sql Deployment.select(Deployment.arel_table[:id].maximum) .where(Deployment.arel_table[:environment_id].eq(arel_table[:id])) diff --git a/app/models/serverless/domain.rb b/app/models/serverless/domain.rb new file mode 100644 index 00000000000..2fef3b66b08 --- /dev/null +++ b/app/models/serverless/domain.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Serverless + class Domain + include ActiveModel::Model + + REGEXP = %r{^(?<scheme>https?://)?(?<function_name>[^.]+)-(?<cluster_left>\h{2})a1(?<cluster_middle>\h{10})f2(?<cluster_right>\h{2})(?<environment_id>\h+)-(?<environment_slug>[^.]+)\.(?<pages_domain_name>.+)}.freeze + UUID_LENGTH = 14 + + attr_accessor :function_name, :serverless_domain_cluster, :environment + + validates :function_name, presence: true, allow_blank: false + validates :serverless_domain_cluster, presence: true + validates :environment, presence: true + + def self.generate_uuid + SecureRandom.hex(UUID_LENGTH / 2) + end + + def uri + URI("https://#{function_name}-#{serverless_domain_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}") + end + + def knative_uri + URI("http://#{function_name}.#{namespace}.#{serverless_domain_cluster.knative.hostname}") + end + + private + + def namespace + serverless_domain_cluster.cluster.kubernetes_namespace_for(environment) + end + + def serverless_domain_cluster_uuid + [ + serverless_domain_cluster.uuid[0..1], + 'a1', + serverless_domain_cluster.uuid[2..-3], + 'f2', + serverless_domain_cluster.uuid[-2..-1] + ].join + end + end +end diff --git a/app/models/serverless/domain_cluster.rb b/app/models/serverless/domain_cluster.rb index 94d90d3e305..9f914d5c3f8 100644 --- a/app/models/serverless/domain_cluster.rb +++ b/app/models/serverless/domain_cluster.rb @@ -16,11 +16,18 @@ module Serverless algorithm: 'aes-256-gcm' validates :pages_domain, :knative, presence: true - validates :uuid, presence: true, uniqueness: true, length: { is: Gitlab::Serverless::Domain::UUID_LENGTH }, + validates :uuid, presence: true, uniqueness: true, length: { is: ::Serverless::Domain::UUID_LENGTH }, format: { with: HEX_REGEXP, message: 'only allows hex characters' } - default_value_for(:uuid, allows_nil: false) { Gitlab::Serverless::Domain.generate_uuid } + default_value_for(:uuid, allows_nil: false) { ::Serverless::Domain.generate_uuid } delegate :domain, to: :pages_domain + delegate :cluster, to: :knative + + def self.for_uuid(uuid) + joins(:pages_domain, :knative) + .includes(:pages_domain, :knative) + .find_by(uuid: uuid) + end end end diff --git a/app/views/shared/badges/_badge_settings.html.haml b/app/views/shared/badges/_badge_settings.html.haml index b7c250d3b1c..4d54981cb0a 100644 --- a/app/views/shared/badges/_badge_settings.html.haml +++ b/app/views/shared/badges/_badge_settings.html.haml @@ -1,4 +1,2 @@ #badge-settings{ data: { api_endpoint_url: @badge_api_endpoint, docs_url: help_page_path('user/project/badges')} } - .text-center.prepend-top-default - = icon('spinner spin 2x') diff --git a/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-shared-badges.yml b/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-shared-badges.yml new file mode 100644 index 00000000000..3cac9a566a0 --- /dev/null +++ b/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-shared-badges.yml @@ -0,0 +1,5 @@ +--- +title: Remove unused loading spinner from badge_settings partial +merge_request: 25044 +author: nuwe1 +type: other diff --git a/doc/user/packages/index.md b/doc/user/packages/index.md index 142541b28cf..78ddc06173c 100644 --- a/doc/user/packages/index.md +++ b/doc/user/packages/index.md @@ -13,7 +13,7 @@ The Packages feature allows GitLab to act as a repository for the following: | [Conan Repository](conan_repository/index.md) **(PREMIUM)** | The GitLab Conan Repository enables every project in GitLab to have its own space to store [Conan](https://conan.io/) packages. | 12.6+ | | [Maven Repository](maven_repository/index.md) **(PREMIUM)** | The GitLab Maven Repository enables every project in GitLab to have its own space to store [Maven](https://maven.apache.org/) packages. | 11.3+ | | [NPM Registry](npm_registry/index.md) **(PREMIUM)** | The GitLab NPM Registry enables every project in GitLab to have its own space to store [NPM](https://www.npmjs.com/) packages. | 11.7+ | -| [NuGet Repository](nuget_repository/index.md) **(PREMIUM)** | *PLANNED* The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ | +| [NuGet Repository](nuget_repository/index.md) **(PREMIUM)** | The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ | ## Suggested contributions diff --git a/doc/user/packages/npm_registry/index.md b/doc/user/packages/npm_registry/index.md index 7504137fbea..5a3754685da 100644 --- a/doc/user/packages/npm_registry/index.md +++ b/doc/user/packages/npm_registry/index.md @@ -280,7 +280,7 @@ page. To work with NPM commands within [GitLab CI](./../../../ci/README.md), you can use `CI_JOB_TOKEN` in place of the personal access token in your commands. -A simple example `gitlab-ci.yml` file for publishing NPM packages: +A simple example `.gitlab-ci.yml` file for publishing NPM packages: ```yml image: node:latest diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index d32683e1d40..ff4c78c5abf 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -2,18 +2,21 @@ > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/20050) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.8. -CAUTION: **Work in progress** -This feature is in development, sections on uploading and installing packages will be coming soon, please follow along and help us make sure we're building the right solution for you in the [NuGet issue](https://gitlab.com/gitlab-org/gitlab/issues/20050). - With the GitLab NuGet Repository, every project can have its own space to store NuGet packages. -The GitLab NuGet Repository works with either [nuget CLI](https://www.nuget.org/) or [Visual Studio](https://visualstudio.microsoft.com/vs/). +The GitLab NuGet Repository works with: + +- [NuGet CLI](https://docs.microsoft.com/en-us/nuget/reference/nuget-exe-cli-reference) +- [.NET Core CLI](https://docs.microsoft.com/en-us/dotnet/core/tools/) +- [Visual Studio](https://visualstudio.microsoft.com/vs/) ## Setting up your development environment -You will need [nuget CLI](https://www.nuget.org/) 5.2 or above. Previous versions have not been tested against the GitLab NuGet Repository and might not work. You can install it by visiting the [downloads page](https://www.nuget.org/downloads). +You will need [NuGet CLI 5.2 or later](https://www.nuget.org/downloads). Earlier versions have not been tested +against the GitLab NuGet Repository and might not work. If you have [Visual Studio](https://visualstudio.microsoft.com/vs/), +NuGet CLI is probably already installed. -If you have [Visual Studio](https://visualstudio.microsoft.com/vs/), [nuget CLI](https://www.nuget.org/) is probably already installed. +Alternatively, you can use [.NET SDK 3.0 or later](https://dotnet.microsoft.com/download/dotnet-core/3.0), which installs NuGet CLI. You can confirm that [nuget CLI](https://www.nuget.org/) is properly installed with: @@ -37,7 +40,7 @@ Available commands: NOTE: **Note:** This option is available only if your GitLab administrator has -[enabled support for the NuGet Repository](../../../administration/packages/index.md).**(PREMIUM ONLY)** +[enabled support for the Package Registry](../../../administration/packages/index.md). **(PREMIUM ONLY)** After the NuGet Repository is enabled, it will be available for all new projects by default. To enable it for existing projects, or if you want to disable it: @@ -48,7 +51,7 @@ by default. To enable it for existing projects, or if you want to disable it: You should then be able to see the **Packages** section on the left sidebar. -## Adding the GitLab NuGet Repository as a source to nuget +## Adding the GitLab NuGet Repository as a source to NuGet You will need the following: @@ -57,23 +60,23 @@ You will need the following: - A suitable name for your source. - Your project ID which can be found on the home page of your project. -You can now add a new source to nuget either using [nuget CLI](https://www.nuget.org/) or [Visual Studio](https://visualstudio.microsoft.com/vs/). +You can now add a new source to NuGet with: + +- [NuGet CLI](#add-nuget-repository-source-with-nuget-cli) +- [Visual Studio](#add-nuget-repository-source-with-visual-studio). +- [.NET CLI](#add-nuget-repository-source-with-net-cli) -### Using nuget CLI +### Add NuGet Repository source with NuGet CLI To add the GitLab NuGet Repository as a source with `nuget`: ```shell -nuget source Add -Name <source_name> -Source "https://example.gitlab.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username> -Password <gitlab_token> +nuget source Add -Name <source_name> -Source "https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username> -Password <gitlab_personal_access_token> ``` -Replace: +Where: -- `<source_name>` with your desired source name. -- `<your_project_id>` with your project ID. -- `<gitlab-username>` with your GitLab username. -- `<gitlab-token>` with your personal access token. -- `example.gitlab.com` with the URL of the GitLab instance you're using. +- `<source_name>` is your desired source name. For example: @@ -81,7 +84,7 @@ For example: nuget source Add -Name "GitLab" -Source "https//gitlab.example/api/v4/projects/10/packages/nuget/index.json" -UserName carol -Password 12345678asdf ``` -### Using Visual Studio +### Add NuGet Repository source with Visual Studio 1. Open [Visual Studio](https://visualstudio.microsoft.com/vs/). 1. Open the **FILE > OPTIONS** (Windows) or **Visual Studio > Preferences** (Mac OS). @@ -102,3 +105,109 @@ nuget source Add -Name "GitLab" -Source "https//gitlab.example/api/v4/projects/1 ![Visual Studio NuGet source added](img/visual_studio_nuget_source_added.png) In case of any warning, please make sure that the **Location**, **Username** and **Password** are correct. + +### Add NuGet Repository source with .NET CLI + +To add the GitLab NuGet Repository as a source for .NET, create a file named `nuget.config` in the root of your project with the following content: + +```xml +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <packageSources> + <clear /> + <add key="gitlab" value="https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" /> + </packageSources> + <packageSourceCredentials> + <gitlab> + <add key="Username" value="<gitlab_username>" /> + <add key="ClearTextPassword" value="<gitlab_personal_access_token>" /> + </gitlab> + </packageSourceCredentials> +</configuration> +``` + +## Uploading packages + +When uploading packages, note that: + +- The maximum allowed size is 50 Megabytes. +- If you upload the same package with the same version multiple times, each consecutive upload + is saved as a separate file. When installing a package, GitLab will serve the most recent file. +- When uploading packages to GitLab, they will not be displayed in the packages UI of your project + immediately. It can take up to 10 minutes to process a package. + +### Upload packages with NuGet CLI + +This section assumes that your project is properly built and you already [created a NuGet package with NuGet CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package). +Upload your package using the following command: + +```shell +nuget push <package_file> -Source <source_name> +``` + +Where: + +- `<package_file>` is your package filename, ending in `.nupkg`. +- `<source_name>` is the [source name used during setup](#adding-the-gitlab-nuget-repository-as-a-source-to-nuget). + +### Upload packages with .NET CLI + +This section assumes that your project is properly built and you already [created a NuGet package with .NET CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package-dotnet-cli.). +Upload your package using the following command: + +```shell +dotnet nuget push <package_file> --source <source_name> +``` + +Where: + +- `<package_file>` is your package filename, ending in `.nupkg`. +- `<source_name>` is the [source name used during setup](#adding-the-gitlab-nuget-repository-as-a-source-to-nuget). + +For example: + +```shell +dotnet nuget push MyPackage.1.0.0.nupkg --source gitlab +``` + +## Install packages + +### Install a package with NuGet CLI + +CAUTION: **Warning:** +By default, `nuget` checks the official source at `nuget.org` first. If you have a package in the +GitLab NuGet Repository with the same name as a package at `nuget.org`, you must specify the source +name or the wrong package will be installed. +Install the latest version of a package using the following command: + +```shell +nuget install <package_id> -OutputDirectory <output_directory> \ + -Version <package_version> \ + -Source <source_name> +``` + +Where: + +- `<package_id>` is the package id. +- `<output_directory>` is the output directory, where the package will be installed. +- `<package_version>` (Optional) is the package version. +- `<source_name>` (Optional) is the source name. + +### Install a package with .NET CLI + +CAUTION: **Warning:** +If you have a package in the GitLab NuGet Repository with the same name as a package at a different source, +you should verify the order in which `dotnet` checks sources during install. This is defined in the +`nuget.config` file. + +Install the latest version of a package using the following command: + +```shell +dotnet add package <package_id> \ + -v <package_version> +``` + +Where: + +- `<package_id>` is the package id. +- `<package_version>` (Optional) is the package version. diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb index c7c348ce9eb..9f66f35b5ab 100644 --- a/lib/gitlab/kubernetes/helm.rb +++ b/lib/gitlab/kubernetes/helm.rb @@ -10,6 +10,12 @@ module Gitlab SERVICE_ACCOUNT = 'tiller' CLUSTER_ROLE_BINDING = 'tiller-admin' CLUSTER_ROLE = 'cluster-admin' + + MANAGED_APPS_LOCAL_TILLER_FEATURE_FLAG = :managed_apps_local_tiller + + def self.local_tiller_enabled? + Feature.enabled?(MANAGED_APPS_LOCAL_TILLER_FEATURE_FLAG) + end end end end diff --git a/lib/gitlab/kubernetes/helm/client_command.rb b/lib/gitlab/kubernetes/helm/client_command.rb index b953ce24c4a..e7ade7e4d39 100644 --- a/lib/gitlab/kubernetes/helm/client_command.rb +++ b/lib/gitlab/kubernetes/helm/client_command.rb @@ -59,7 +59,7 @@ module Gitlab end def local_tiller_enabled? - Feature.enabled?(:managed_apps_local_tiller) + ::Gitlab::Kubernetes::Helm.local_tiller_enabled? end end end diff --git a/lib/gitlab/serverless/domain.rb b/lib/gitlab/serverless/domain.rb deleted file mode 100644 index ec7c68764d1..00000000000 --- a/lib/gitlab/serverless/domain.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Serverless - class Domain - UUID_LENGTH = 14 - - def self.generate_uuid - SecureRandom.hex(UUID_LENGTH / 2) - end - end - end -end diff --git a/lib/gitlab/serverless/function_uri.rb b/lib/gitlab/serverless/function_uri.rb deleted file mode 100644 index c0e0cf00f35..00000000000 --- a/lib/gitlab/serverless/function_uri.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Serverless - class FunctionURI < URI::HTTPS - SERVERLESS_DOMAIN_REGEXP = %r{^(?<scheme>https?://)?(?<function>[^.]+)-(?<cluster_left>\h{2})a1(?<cluster_middle>\h{10})f2(?<cluster_right>\h{2})(?<environment_id>\h+)-(?<environment_slug>[^.]+)\.(?<domain>.+)}.freeze - - attr_reader :function, :cluster, :environment - - def initialize(function: nil, cluster: nil, environment: nil) - initialize_required_argument(:function, function) - initialize_required_argument(:cluster, cluster) - initialize_required_argument(:environment, environment) - - @host = "#{function}-#{cluster.uuid[0..1]}a1#{cluster.uuid[2..-3]}f2#{cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{cluster.domain}" - - super('https', nil, host, nil, nil, nil, nil, nil, nil) - end - - def self.parse(uri) - match = SERVERLESS_DOMAIN_REGEXP.match(uri) - return unless match - - cluster = ::Serverless::DomainCluster.find(match[:cluster_left] + match[:cluster_middle] + match[:cluster_right]) - return unless cluster - - environment = ::Environment.find(match[:environment_id].to_i(16)) - return unless environment&.slug == match[:environment_slug] - - new( - function: match[:function], - cluster: cluster, - environment: environment - ) - end - - private - - def initialize_required_argument(name, value) - raise ArgumentError.new("missing argument: #{name}") unless value - - instance_variable_set("@#{name}".to_sym, value) - end - end - end -end diff --git a/lib/gitlab/serverless/service.rb b/lib/gitlab/serverless/service.rb index 643e076c587..c3ab2e9ddeb 100644 --- a/lib/gitlab/serverless/service.rb +++ b/lib/gitlab/serverless/service.rb @@ -60,7 +60,11 @@ class Gitlab::Serverless::Service def proxy_url if cluster&.serverless_domain - Gitlab::Serverless::FunctionURI.new(function: name, cluster: cluster.serverless_domain, environment: environment) + ::Serverless::Domain.new( + function_name: name, + serverless_domain_cluster: cluster.serverless_domain, + environment: environment + ).uri.to_s end end diff --git a/qa/qa/page/project/operations/metrics.rb b/qa/qa/page/project/operations/metrics.rb index 418cc925186..710cb68ed41 100644 --- a/qa/qa/page/project/operations/metrics.rb +++ b/qa/qa/page/project/operations/metrics.rb @@ -12,11 +12,8 @@ module QA element :prometheus_graphs end - view 'app/assets/javascripts/monitoring/components/charts/time_series.vue' do - element :prometheus_graph_widgets - end - view 'app/assets/javascripts/monitoring/components/panel_type.vue' do + element :prometheus_graph_widgets element :prometheus_widgets_dropdown element :alert_widget_menu_item end diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index db7533eb609..203e1e49994 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -135,7 +135,7 @@ describe Projects::Serverless::FunctionsController do context 'when there is no serverless domain for a cluster' do it 'keeps function URL as it was' do - expect(Gitlab::Serverless::Domain).not_to receive(:new) + expect(::Serverless::Domain).not_to receive(:new) get :index, params: params({ format: :json }) expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index ff9fc882dcc..0a4f0fba9ab 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -73,39 +73,71 @@ FactoryBot.define do factory :clusters_applications_ingress, class: 'Clusters::Applications::Ingress' do modsecurity_enabled { false } cluster factory: %i(cluster with_installed_helm provided_by_gcp) + + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + end end factory :clusters_applications_cert_manager, class: 'Clusters::Applications::CertManager' do email { 'admin@example.com' } cluster factory: %i(cluster with_installed_helm provided_by_gcp) + + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + end end factory :clusters_applications_elastic_stack, class: 'Clusters::Applications::ElasticStack' do cluster factory: %i(cluster with_installed_helm provided_by_gcp) + + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + end end factory :clusters_applications_crossplane, class: 'Clusters::Applications::Crossplane' do stack { 'gcp' } cluster factory: %i(cluster with_installed_helm provided_by_gcp) + + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + end end factory :clusters_applications_prometheus, class: 'Clusters::Applications::Prometheus' do cluster factory: %i(cluster with_installed_helm provided_by_gcp) + + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + end end factory :clusters_applications_runner, class: 'Clusters::Applications::Runner' do runner factory: %i(ci_runner) cluster factory: %i(cluster with_installed_helm provided_by_gcp) + + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + end end factory :clusters_applications_knative, class: 'Clusters::Applications::Knative' do hostname { 'example.com' } cluster factory: %i(cluster with_installed_helm provided_by_gcp) + + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + end end factory :clusters_applications_jupyter, class: 'Clusters::Applications::Jupyter' do oauth_application factory: :oauth_application cluster factory: %i(cluster with_installed_helm provided_by_gcp project) + + trait :no_helm_installed do + cluster factory: %i(cluster provided_by_gcp) + end end end end diff --git a/spec/factories/serverless/domain.rb b/spec/factories/serverless/domain.rb new file mode 100644 index 00000000000..7a6a048fb34 --- /dev/null +++ b/spec/factories/serverless/domain.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :serverless_domain, class: '::Serverless::Domain' do + function_name { 'test-function' } + serverless_domain_cluster { create(:serverless_domain_cluster) } + environment { create(:environment) } + + skip_create + end +end diff --git a/spec/finders/serverless_domain_finder_spec.rb b/spec/finders/serverless_domain_finder_spec.rb new file mode 100644 index 00000000000..3fe82264cda --- /dev/null +++ b/spec/finders/serverless_domain_finder_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ServerlessDomainFinder do + let(:function_name) { 'test-function' } + let(:pages_domain_name) { 'serverless.gitlab.io' } + let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: pages_domain_name) } + let!(:serverless_domain_cluster) { create(:serverless_domain_cluster, uuid: 'abcdef12345678', pages_domain: pages_domain) } + let(:valid_cluster_uuid) { 'aba1cdef123456f278' } + let(:invalid_cluster_uuid) { 'aba1cdef123456f178' } + let!(:environment) { create(:environment, name: 'test') } + + let(:valid_uri) { "https://#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" } + let(:valid_fqdn) { "#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" } + let(:invalid_uri) { "https://#{function_name}-#{invalid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" } + + let(:valid_finder) { described_class.new(valid_uri) } + let(:invalid_finder) { described_class.new(invalid_uri) } + + describe '#serverless?' do + context 'with a valid URI' do + subject { valid_finder.serverless? } + + it { is_expected.to be_truthy } + end + + context 'with an invalid URI' do + subject { invalid_finder.serverless? } + + it { is_expected.to be_falsy } + end + end + + describe '#serverless_domain_cluster_uuid' do + context 'with a valid URI' do + subject { valid_finder.serverless_domain_cluster_uuid } + + it { is_expected.to eq serverless_domain_cluster.uuid } + end + + context 'with an invalid URI' do + subject { invalid_finder.serverless_domain_cluster_uuid } + + it { is_expected.to be_nil } + end + end + + describe '#execute' do + context 'with a valid URI' do + let(:serverless_domain) do + create( + :serverless_domain, + function_name: function_name, + serverless_domain_cluster: serverless_domain_cluster, + environment: environment + ) + end + + subject { valid_finder.execute } + + it 'has the correct function_name' do + expect(subject.function_name).to eq function_name + end + + it 'has the correct serverless_domain_cluster' do + expect(subject.serverless_domain_cluster).to eq serverless_domain_cluster + end + + it 'has the correct environment' do + expect(subject.environment).to eq environment + end + end + + context 'with an invalid URI' do + subject { invalid_finder.execute } + + it { is_expected.to be_nil } + end + end +end diff --git a/spec/frontend/monitoring/components/charts/anomaly_spec.js b/spec/frontend/monitoring/components/charts/anomaly_spec.js index cea22d075ec..e2d001c3058 100644 --- a/spec/frontend/monitoring/components/charts/anomaly_spec.js +++ b/spec/frontend/monitoring/components/charts/anomaly_spec.js @@ -11,7 +11,6 @@ import { } from '../../mock_data'; import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue'; -const mockWidgets = 'mockWidgets'; const mockProjectPath = `${TEST_HOST}${mockProjectDir}`; jest.mock('~/lib/utils/icon_utils'); // mock getSvgIconPathContent @@ -35,9 +34,6 @@ describe('Anomaly chart component', () => { const setupAnomalyChart = props => { wrapper = shallowMount(Anomaly, { propsData: { ...props }, - slots: { - default: mockWidgets, - }, }); }; const findTimeSeries = () => wrapper.find(MonitorTimeSeriesChart); diff --git a/spec/frontend/monitoring/components/charts/empty_chart_spec.js b/spec/frontend/monitoring/components/charts/empty_chart_spec.js index bbfca27dc5a..d755ed7c104 100644 --- a/spec/frontend/monitoring/components/charts/empty_chart_spec.js +++ b/spec/frontend/monitoring/components/charts/empty_chart_spec.js @@ -13,14 +13,6 @@ describe('Empty Chart component', () => { }); }); - afterEach(() => { - emptyChart.destroy(); - }); - - it('render the chart title', () => { - expect(emptyChart.find({ ref: 'graphTitle' }).text()).toBe(graphTitle); - }); - describe('Computed props', () => { it('sets the height for the svg container', () => { expect(emptyChart.vm.svgContainerStyle.height).toBe('300px'); diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index a911b925b66..49f2a70a8b2 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -16,8 +16,6 @@ import { } from '../../mock_data'; import * as iconUtils from '~/lib/utils/icon_utils'; -const mockWidgets = 'mockWidgets'; - const mockSvgPathContent = 'mockSvgPathContent'; jest.mock('lodash/throttle', () => @@ -65,9 +63,6 @@ describe('Time series component', () => { deploymentData: store.state.monitoringDashboard.deploymentData, projectPath: `${mockHost}${mockProjectDir}`, }, - slots: { - default: mockWidgets, - }, store, }); }); @@ -82,14 +77,6 @@ describe('Time series component', () => { timeSeriesChart.vm.$nextTick(done); }); - it('renders chart title', () => { - expect(timeSeriesChart.find('.js-graph-title').text()).toBe(mockGraphData.title); - }); - - it('contains graph widgets from slot', () => { - expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets); - }); - it('allows user to override max value label text using prop', () => { timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' }); diff --git a/spec/frontend/monitoring/components/panel_type_spec.js b/spec/frontend/monitoring/components/panel_type_spec.js index 0d79babf386..dbbe3f55298 100644 --- a/spec/frontend/monitoring/components/panel_type_spec.js +++ b/spec/frontend/monitoring/components/panel_type_spec.js @@ -74,6 +74,18 @@ describe('Panel Type component', () => { glEmptyChart = wrapper.find(EmptyChart); }); + it('renders the chart title', () => { + expect(wrapper.find({ ref: 'graphTitle' }).text()).toBe(graphDataNoResult.title); + }); + + it('renders the no download csv link', () => { + expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false); + }); + + it('does not contain graph widgets', () => { + expect(wrapper.find('.js-graph-widgets').exists()).toBe(false); + }); + it('is a Vue instance', () => { expect(glEmptyChart.isVueInstance()).toBe(true); }); @@ -97,6 +109,15 @@ describe('Panel Type component', () => { wrapper.destroy(); }); + it('renders the chart title', () => { + expect(wrapper.find({ ref: 'graphTitle' }).text()).toBe(graphDataPrometheusQueryRange.title); + }); + + it('contains graph widgets', () => { + expect(wrapper.find('.js-graph-widgets').exists()).toBe(true); + expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(true); + }); + it('sets no clipboard copy link on dropdown by default', () => { expect(findCopyLink().exists()).toBe(false); }); diff --git a/spec/frontend/monitoring/shared/prometheus_header_spec.js b/spec/frontend/monitoring/shared/prometheus_header_spec.js deleted file mode 100644 index b216bfb72d8..00000000000 --- a/spec/frontend/monitoring/shared/prometheus_header_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import PrometheusHeader from '~/monitoring/components/shared/prometheus_header.vue'; - -describe('Prometheus Header component', () => { - let prometheusHeader; - - beforeEach(() => { - prometheusHeader = shallowMount(PrometheusHeader, { - propsData: { - graphTitle: 'graph header', - }, - }); - }); - - afterEach(() => { - prometheusHeader.destroy(); - }); - - describe('Prometheus header component', () => { - it('should show a title', () => { - const title = prometheusHeader.find({ ref: 'title' }).text(); - - expect(title).toBe('graph header'); - }); - }); -}); diff --git a/spec/lib/gitlab/serverless/domain_spec.rb b/spec/lib/gitlab/serverless/domain_spec.rb deleted file mode 100644 index ae5551977d4..00000000000 --- a/spec/lib/gitlab/serverless/domain_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Serverless::Domain do - describe '.generate_uuid' do - it 'has 14 characters' do - expect(described_class.generate_uuid.length).to eq(described_class::UUID_LENGTH) - end - - it 'consists of only hexadecimal characters' do - expect(described_class.generate_uuid).to match(/\A\h+\z/) - end - - it 'uses random characters' do - uuid = 'abcd1234567890' - - expect(SecureRandom).to receive(:hex).with(described_class::UUID_LENGTH / 2).and_return(uuid) - expect(described_class.generate_uuid).to eq(uuid) - end - end -end diff --git a/spec/lib/gitlab/serverless/function_uri_spec.rb b/spec/lib/gitlab/serverless/function_uri_spec.rb deleted file mode 100644 index cd4abeb89f5..00000000000 --- a/spec/lib/gitlab/serverless/function_uri_spec.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Serverless::FunctionURI do - let(:function) { 'test-function' } - let(:domain) { 'serverless.gitlab.io' } - let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: domain) } - let!(:cluster) { create(:serverless_domain_cluster, uuid: 'abcdef12345678', pages_domain: pages_domain) } - let(:valid_cluster) { 'aba1cdef123456f278' } - let(:invalid_cluster) { 'aba1cdef123456f178' } - let!(:environment) { create(:environment, name: 'test') } - - let(:valid_uri) { "https://#{function}-#{valid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" } - let(:valid_fqdn) { "#{function}-#{valid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" } - let(:invalid_uri) { "https://#{function}-#{invalid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" } - - shared_examples 'a valid FunctionURI class' do - describe '#to_s' do - it 'matches valid URI' do - expect(subject.to_s).to eq valid_uri - end - end - - describe '#function' do - it 'returns function' do - expect(subject.function).to eq function - end - end - - describe '#cluster' do - it 'returns cluster' do - expect(subject.cluster).to eq cluster - end - end - - describe '#environment' do - it 'returns environment' do - expect(subject.environment).to eq environment - end - end - end - - describe '.new' do - context 'with valid arguments' do - subject { described_class.new(function: function, cluster: cluster, environment: environment) } - - it_behaves_like 'a valid FunctionURI class' - end - - context 'with invalid arguments' do - subject { described_class.new(function: function, environment: environment) } - - it 'raises an exception' do - expect { subject }.to raise_error(ArgumentError) - end - end - end - - describe '.parse' do - context 'with valid URI' do - subject { described_class.parse(valid_uri) } - - it_behaves_like 'a valid FunctionURI class' - end - - context 'with valid FQDN' do - subject { described_class.parse(valid_fqdn) } - - it_behaves_like 'a valid FunctionURI class' - end - - context 'with invalid URI' do - subject { described_class.parse(invalid_uri) } - - it 'returns nil' do - expect(subject).to be_nil - end - end - end -end diff --git a/spec/lib/gitlab/serverless/service_spec.rb b/spec/lib/gitlab/serverless/service_spec.rb index f618dd02cdb..6db8b9cd0ba 100644 --- a/spec/lib/gitlab/serverless/service_spec.rb +++ b/spec/lib/gitlab/serverless/service_spec.rb @@ -94,17 +94,19 @@ describe Gitlab::Serverless::Service do end describe '#url' do + let(:serverless_domain) { instance_double(::Serverless::Domain, uri: URI('https://proxy.example.com')) } + it 'returns proxy URL if cluster has serverless domain' do # cluster = create(:cluster) knative = create(:clusters_applications_knative, :installed, cluster: cluster) create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id) service = Gitlab::Serverless::Service.new(attributes.merge('cluster' => cluster)) - expect(Gitlab::Serverless::FunctionURI).to receive(:new).with( - function: service.name, - cluster: service.cluster.serverless_domain, + expect(::Serverless::Domain).to receive(:new).with( + function_name: service.name, + serverless_domain_cluster: service.cluster.serverless_domain, environment: service.environment - ).and_return('https://proxy.example.com') + ).and_return(serverless_domain) expect(service.url).to eq('https://proxy.example.com') end diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index ba344a234b8..04e4d261b1c 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -274,7 +274,8 @@ describe Clusters::Applications::Prometheus do subject { application.files_with_replaced_values({ hello: :world }) } it 'does not modify #files' do - expect(subject[:'values.yaml']).not_to eq(files) + expect(subject[:'values.yaml']).not_to eq(files[:'values.yaml']) + expect(files[:'values.yaml']).to eq(application.values) end @@ -282,27 +283,17 @@ describe Clusters::Applications::Prometheus do expect(subject[:'values.yaml']).to eq({ hello: :world }) end - it 'includes cert files' do - expect(subject[:'ca.pem']).to be_present - expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) - - expect(subject[:'cert.pem']).to be_present - expect(subject[:'key.pem']).to be_present - - cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem']) - expect(cert.not_after).to be < 60.minutes.from_now - end - - context 'when the helm application does not have a ca_cert' do - before do - application.cluster.application_helm.ca_cert = nil - end - - it 'does not include cert files' do - expect(subject[:'ca.pem']).not_to be_present - expect(subject[:'cert.pem']).not_to be_present - expect(subject[:'key.pem']).not_to be_present - end + it 'uses values from #files, except for values.yaml' do + allow(application).to receive(:files).and_return({ + 'values.yaml': 'some value specific to files', + 'file_a.txt': 'file_a', + 'file_b.txt': 'file_b' + }) + + expect(subject.except(:'values.yaml')).to eq({ + 'file_a.txt': 'file_a', + 'file_b.txt': 'file_b' + }) end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 72143d69fc8..48cabd4301c 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -1264,6 +1264,14 @@ describe Environment, :use_clean_rails_memory_store_caching do end end + describe '.for_id_and_slug' do + subject { described_class.for_id_and_slug(environment.id, environment.slug) } + + let(:environment) { create(:environment) } + + it { is_expected.not_to be_nil } + end + describe '.find_or_create_by_name' do it 'finds an existing environment if it exists' do env = create(:environment) diff --git a/spec/models/serverless/domain_cluster_spec.rb b/spec/models/serverless/domain_cluster_spec.rb index bd645b7d0aa..f5e1eb304a1 100644 --- a/spec/models/serverless/domain_cluster_spec.rb +++ b/spec/models/serverless/domain_cluster_spec.rb @@ -10,7 +10,7 @@ describe ::Serverless::DomainCluster do it { is_expected.to validate_presence_of(:knative) } it { is_expected.to validate_presence_of(:uuid) } - it { is_expected.to validate_length_of(:uuid).is_equal_to(Gitlab::Serverless::Domain::UUID_LENGTH) } + it { is_expected.to validate_length_of(:uuid).is_equal_to(::Serverless::Domain::UUID_LENGTH) } it { is_expected.to validate_uniqueness_of(:uuid) } it 'validates that uuid has only hex characters' do @@ -31,7 +31,7 @@ describe ::Serverless::DomainCluster do context 'when nil' do it 'generates a value by default' do attributes = build(:serverless_domain_cluster).attributes.merge(uuid: nil) - expect(Gitlab::Serverless::Domain).to receive(:generate_uuid).and_call_original + expect(::Serverless::Domain).to receive(:generate_uuid).and_call_original subject = Serverless::DomainCluster.new(attributes) @@ -47,6 +47,10 @@ describe ::Serverless::DomainCluster do end end + describe 'cluster' do + it { is_expected.to respond_to(:cluster) } + end + describe 'domain' do it { is_expected.to respond_to(:domain) } end diff --git a/spec/models/serverless/domain_spec.rb b/spec/models/serverless/domain_spec.rb new file mode 100644 index 00000000000..ba54e05b4e3 --- /dev/null +++ b/spec/models/serverless/domain_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Serverless::Domain do + let(:function_name) { 'test-function' } + let(:pages_domain_name) { 'serverless.gitlab.io' } + let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: pages_domain_name) } + let!(:serverless_domain_cluster) { create(:serverless_domain_cluster, uuid: 'abcdef12345678', pages_domain: pages_domain) } + let(:valid_cluster_uuid) { 'aba1cdef123456f278' } + let(:invalid_cluster_uuid) { 'aba1cdef123456f178' } + let!(:environment) { create(:environment, name: 'test') } + + let(:valid_uri) { "https://#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" } + let(:valid_fqdn) { "#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" } + let(:invalid_uri) { "https://#{function_name}-#{invalid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" } + + shared_examples 'a valid Domain' do + describe '#uri' do + it 'matches valid URI' do + expect(subject.uri.to_s).to eq valid_uri + end + end + + describe '#function_name' do + it 'returns function_name' do + expect(subject.function_name).to eq function_name + end + end + + describe '#serverless_domain_cluster' do + it 'returns serverless_domain_cluster' do + expect(subject.serverless_domain_cluster).to eq serverless_domain_cluster + end + end + + describe '#environment' do + it 'returns environment' do + expect(subject.environment).to eq environment + end + end + end + + describe '.new' do + context 'with valid arguments' do + subject do + described_class.new( + function_name: function_name, + serverless_domain_cluster: serverless_domain_cluster, + environment: environment + ) + end + + it_behaves_like 'a valid Domain' + end + + context 'with invalid arguments' do + subject do + described_class.new( + function_name: function_name, + environment: environment + ) + end + + it { is_expected.not_to be_valid } + end + + context 'with nil cluster argument' do + subject do + described_class.new( + function_name: function_name, + serverless_domain_cluster: nil, + environment: environment + ) + end + + it { is_expected.not_to be_valid } + end + end + + describe '.generate_uuid' do + it 'has 14 characters' do + expect(described_class.generate_uuid.length).to eq(described_class::UUID_LENGTH) + end + + it 'consists of only hexadecimal characters' do + expect(described_class.generate_uuid).to match(/\A\h+\z/) + end + + it 'uses random characters' do + uuid = 'abcd1234567890' + + expect(SecureRandom).to receive(:hex).with(described_class::UUID_LENGTH / 2).and_return(uuid) + expect(described_class.generate_uuid).to eq(uuid) + end + end +end diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb index d5c425dea51..fa6b0c3afdd 100644 --- a/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_helm_cert_shared_examples.rb @@ -28,22 +28,46 @@ RSpec.shared_examples 'cluster application helm specs' do |application_name| describe '#files' do subject { application.files } - context 'when the helm application does not have a ca_cert' do + context 'managed_apps_local_tiller feature flag is disabled' do before do - application.cluster.application_helm.ca_cert = nil + stub_feature_flags(managed_apps_local_tiller: false) end - it 'does not include cert files when there is no ca_cert entry' do - expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem') + context 'when the helm application does not have a ca_cert' do + before do + application.cluster.application_helm.ca_cert = nil + end + + it 'does not include cert files when there is no ca_cert entry' do + expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem') + end + end + + it 'includes cert files when there is a ca_cert entry' do + expect(subject).to include(:'ca.pem', :'cert.pem', :'key.pem') + expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) + + cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem']) + expect(cert.not_after).to be < 60.minutes.from_now end end - it 'includes cert files when there is a ca_cert entry' do - expect(subject).to include(:'ca.pem', :'cert.pem', :'key.pem') - expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) + context 'managed_apps_local_tiller feature flag is enabled' do + before do + stub_feature_flags(managed_apps_local_tiller: true) + end + + it 'does not include cert files' do + expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem') + end + + context 'when cluster does not have helm installed' do + let(:application) { create(application_name, :no_helm_installed) } - cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem']) - expect(cert.not_after).to be < 60.minutes.from_now + it 'does not include cert files' do + expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem') + end + end end end end diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index e4e49b94e42..6c772ddf897 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -48,14 +48,44 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| expect(subject).to be_installed end - it 'updates helm version' do - subject.cluster.application_helm.update!(version: '1.2.3') + context 'managed_apps_local_tiller feature flag disabled' do + before do + stub_feature_flags(managed_apps_local_tiller: false) + end - subject.make_installed! + it 'updates helm version' do + subject.cluster.application_helm.update!(version: '1.2.3') - subject.cluster.application_helm.reload + subject.make_installed! - expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION) + subject.cluster.application_helm.reload + + expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION) + end + end + + context 'managed_apps_local_tiller feature flag enabled' do + before do + stub_feature_flags(managed_apps_local_tiller: true) + end + + it 'does not update the helm version' do + subject.cluster.application_helm.update!(version: '1.2.3') + + expect do + subject.make_installed! + + subject.cluster.application_helm.reload + end.not_to change { subject.cluster.application_helm.version } + end + + context 'the cluster has no helm installed' do + subject { create(application_name, :installing, :no_helm_installed) } + + it 'runs without errors' do + expect { subject.make_installed! }.not_to raise_error + end + end end it 'sets the correct version of the application' do @@ -77,14 +107,44 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| expect(subject).to be_updated end - it 'updates helm version' do - subject.cluster.application_helm.update!(version: '1.2.3') + context 'managed_apps_local_tiller feature flag disabled' do + before do + stub_feature_flags(managed_apps_local_tiller: false) + end - subject.make_installed! + it 'updates helm version' do + subject.cluster.application_helm.update!(version: '1.2.3') - subject.cluster.application_helm.reload + subject.make_installed! - expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION) + subject.cluster.application_helm.reload + + expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION) + end + end + + context 'managed_apps_local_tiller feature flag enabled' do + before do + stub_feature_flags(managed_apps_local_tiller: true) + end + + it 'does not update the helm version' do + subject.cluster.application_helm.update!(version: '1.2.3') + + expect do + subject.make_installed! + + subject.cluster.application_helm.reload + end.not_to change { subject.cluster.application_helm.version } + end + + context 'the cluster has no helm installed' do + subject { create(application_name, :updating, :no_helm_installed) } + + it 'runs without errors' do + expect { subject.make_installed! }.not_to raise_error + end + end end it 'updates the version of the application' do |