<!-- 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.
...@@ -90,11 +90,7 @@ export default { ...@@ -90,11 +90,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph"> <div v-gl-resize-observer-directive="onResize">
<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>
<gl-column-chart <gl-column-chart
ref="columnChart" ref="columnChart"
v-bind="$attrs" v-bind="$attrs"
... ...
......
...@@ -27,10 +27,7 @@ export default { ...@@ -27,10 +27,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="prometheus-graph d-flex flex-column justify-content-center"> <div class="d-flex flex-column justify-content-center">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
<div <div
class="prepend-top-8 svg-w-100 d-flex align-items-center" class="prepend-top-8 svg-w-100 d-flex align-items-center"
:style="svgContainerStyle" :style="svgContainerStyle"
... ...
......
...@@ -2,13 +2,11 @@ ...@@ -2,13 +2,11 @@
import { GlResizeObserverDirective } from '@gitlab/ui'; import { GlResizeObserverDirective } from '@gitlab/ui';
import { GlHeatmap } from '@gitlab/ui/dist/charts'; import { GlHeatmap } from '@gitlab/ui/dist/charts';
import dateformat from 'dateformat'; import dateformat from 'dateformat';
import PrometheusHeader from '../shared/prometheus_header.vue';
import { graphDataValidatorForValues } from '../../utils'; import { graphDataValidatorForValues } from '../../utils';
export default { export default {
components: { components: {
GlHeatmap, GlHeatmap,
PrometheusHeader,
}, },
directives: { directives: {
GlResizeObserverDirective, GlResizeObserverDirective,
...@@ -65,8 +63,7 @@ export default { ...@@ -65,8 +63,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph col-12 col-lg-6"> <div v-gl-resize-observer-directive="onResize" class="col-12 col-lg-6">
<prometheus-header :graph-title="graphData.title" />
<gl-heatmap <gl-heatmap
ref="heatmapChart" ref="heatmapChart"
v-bind="$attrs" v-bind="$attrs"
... ...
......
...@@ -42,10 +42,7 @@ export default { ...@@ -42,10 +42,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="prometheus-graph"> <div>
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
<gl-single-stat :value="statValue" :title="graphTitle" variant="success" /> <gl-single-stat :value="statValue" :title="graphTitle" variant="success" />
</div> </div>
</template> </template>
...@@ -81,11 +81,7 @@ export default { ...@@ -81,11 +81,7 @@ export default {
}; };
</script> </script>
<template> <template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph"> <div v-gl-resize-observer-directive="onResize">
<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>
<gl-stacked-column-chart <gl-stacked-column-chart
ref="chart" ref="chart"
v-bind="$attrs" v-bind="$attrs"
... ...
......
...@@ -112,7 +112,6 @@ export default { ...@@ -112,7 +112,6 @@ export default {
isDeployment: false, isDeployment: false,
sha: '', sha: '',
}, },
showTitleTooltip: false,
width: 0, width: 0,
height: chartHeight, height: chartHeight,
svgs: {}, svgs: {},
...@@ -285,12 +284,6 @@ export default { ...@@ -285,12 +284,6 @@ export default {
return `${this.graphData.y_label}`; return `${this.graphData.y_label}`;
}, },
}, },
mounted() {
const graphTitleEl = this.$refs.graphTitle;
if (graphTitleEl && graphTitleEl.scrollWidth > graphTitleEl.offsetWidth) {
this.showTitleTooltip = true;
}
},
created() { created() {
this.setSvg('rocket'); this.setSvg('rocket');
this.setSvg('scroll-handle'); this.setSvg('scroll-handle');
...@@ -387,24 +380,7 @@ export default { ...@@ -387,24 +380,7 @@ export default {
</script> </script>
<template> <template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph"> <div v-gl-resize-observer-directive="onResize">
<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>
<component <component
:is="glChartComponent" :is="glChartComponent"
ref="chart" ref="chart"
... ...
......
...@@ -3,10 +3,12 @@ import { mapState } from 'vuex'; ...@@ -3,10 +3,12 @@ import { mapState } from 'vuex';
import { pickBy } from 'lodash'; import { pickBy } from 'lodash';
import invalidUrl from '~/lib/utils/invalid_url'; import invalidUrl from '~/lib/utils/invalid_url';
import { import {
GlResizeObserverDirective,
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlModal, GlModal,
GlModalDirective, GlModalDirective,
GlTooltip,
GlTooltipDirective, GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -29,11 +31,13 @@ export default { ...@@ -29,11 +31,13 @@ export default {
MonitorStackedColumnChart, MonitorStackedColumnChart,
MonitorEmptyChart, MonitorEmptyChart,
Icon, Icon,
GlTooltip,
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlModal, GlModal,
}, },
directives: { directives: {
GlResizeObserver: GlResizeObserverDirective,
GlModal: GlModalDirective, GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
TrackEvent: TrackEventDirective, TrackEvent: TrackEventDirective,
...@@ -61,11 +65,15 @@ export default { ...@@ -61,11 +65,15 @@ export default {
}, },
data() { data() {
return { return {
showTitleTooltip: false,
zoomedTimeRange: null, zoomedTimeRange: null,
}; };
}, },
computed: { computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']), ...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
title() {
return this.graphData.title || '';
},
alertWidgetAvailable() { alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData; return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
}, },
...@@ -97,12 +105,24 @@ export default { ...@@ -97,12 +105,24 @@ export default {
const data = new Blob([this.csvText], { type: 'text/plain' }); const data = new Blob([this.csvText], { type: 'text/plain' });
return window.URL.createObjectURL(data); return window.URL.createObjectURL(data);
}, },
monitorChartComponent() { timeChartComponent() {
if (this.isPanelType('anomaly-chart')) { if (this.isPanelType('anomaly-chart')) {
return MonitorAnomalyChart; return MonitorAnomalyChart;
} }
return MonitorTimeSeriesChart; return MonitorTimeSeriesChart;
}, },
isContextualMenuShown() {
return (
this.graphDataHasMetrics &&
!this.isPanelType('single-stat') &&
!this.isPanelType('heatmap') &&
!this.isPanelType('column') &&
!this.isPanelType('stacked-column')
);
},
},
mounted() {
this.refreshTitleTooltip();
}, },
methods: { methods: {
getGraphAlerts(queries) { getGraphAlerts(queries) {
...@@ -119,9 +139,18 @@ export default { ...@@ -119,9 +139,18 @@ export default {
showToast() { showToast() {
this.$toast.show(__('Link copied')); this.$toast.show(__('Link copied'));
}, },
refreshTitleTooltip() {
const { graphTitle } = this.$refs;
this.showTitleTooltip =
Boolean(graphTitle) && graphTitle.scrollWidth > graphTitle.offsetWidth;
},
downloadCSVOptions, downloadCSVOptions,
generateLinkToChartOptions, generateLinkToChartOptions,
onResize() {
this.refreshTitleTooltip();
},
onDatazoom({ start, end }) { onDatazoom({ start, end }) {
this.zoomedTimeRange = { start, end }; this.zoomedTimeRange = { start, end };
}, },
...@@ -129,32 +158,21 @@ export default { ...@@ -129,32 +158,21 @@ export default {
}; };
</script> </script>
<template> <template>
<monitor-single-stat-chart <div v-gl-resize-observer="onResize" class="prometheus-graph">
v-if="isPanelType('single-stat') && graphDataHasMetrics" <div class="prometheus-graph-header">
:graph-data="graphData" <h5
/> ref="graphTitle"
<monitor-heatmap-chart class="prometheus-graph-title gl-font-size-large font-weight-bold text-truncate append-right-8"
v-else-if="isPanelType('heatmap') && graphDataHasMetrics" >
:graph-data="graphData" {{ title }}
/> </h5>
<monitor-column-chart <gl-tooltip :target="() => $refs.graphTitle" :disabled="!showTitleTooltip">
v-else-if="isPanelType('column') && graphDataHasMetrics" {{ title }}
:graph-data="graphData" </gl-tooltip>
/> <div
<monitor-stacked-column-chart v-if="isContextualMenuShown"
v-else-if="isPanelType('stacked-column') && graphDataHasMetrics" class="prometheus-graph-widgets js-graph-widgets flex-fill"
:graph-data="graphData" data-qa-selector="prometheus_graph_widgets"
/>
<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"> <div class="d-flex align-items-center">
<alert-widget <alert-widget
...@@ -170,8 +188,8 @@ export default { ...@@ -170,8 +188,8 @@ export default {
class="ml-auto mx-3" class="ml-auto mx-3"
toggle-class="btn btn-transparent border-0" toggle-class="btn btn-transparent border-0"
data-qa-selector="prometheus_widgets_dropdown" data-qa-selector="prometheus_widgets_dropdown"
:right="true" right
:no-caret="true" no-caret
:title="__('More actions')" :title="__('More actions')"
> >
<template slot="button-content"> <template slot="button-content">
...@@ -187,7 +205,9 @@ export default { ...@@ -187,7 +205,9 @@ export default {
</gl-dropdown-item> </gl-dropdown-item>
<gl-dropdown-item <gl-dropdown-item
v-track-event="downloadCSVOptions(graphData.title)" v-if="csvText"
ref="downloadCsvLink"
v-track-event="downloadCSVOptions(title)"
:href="downloadCsv" :href="downloadCsv"
download="chart_metrics.csv" download="chart_metrics.csv"
> >
...@@ -211,6 +231,36 @@ export default { ...@@ -211,6 +231,36 @@ export default {
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
</div> </div>
</component> </div>
<monitor-empty-chart v-else :graph-title="graphData.title" /> </div>
<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> </template>
<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>
...@@ -91,10 +91,6 @@ ...@@ -91,10 +91,6 @@
margin-bottom: $gl-padding-8; margin-bottom: $gl-padding-8;
} }
.prometheus-graph-title {
font-size: $gl-font-size-large;
}
.alert-current-setting { .alert-current-setting {
max-width: 240px; max-width: 240px;
} }
... ...
......
# 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
...@@ -15,7 +15,7 @@ module Clusters ...@@ -15,7 +15,7 @@ module Clusters
def set_initial_status def set_initial_status
return unless not_installable? 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 end
def can_uninstall? def can_uninstall?
... ...
......
...@@ -23,7 +23,7 @@ module Clusters ...@@ -23,7 +23,7 @@ module Clusters
@files ||= begin @files ||= begin
files = { 'values.yaml': values } files = { 'values.yaml': values }
files.merge!(certificate_files) if cluster.application_helm.has_ssl? files.merge!(certificate_files) if use_tiller_ssl?
files files
end end
...@@ -31,6 +31,12 @@ module Clusters ...@@ -31,6 +31,12 @@ module Clusters
private private
def use_tiller_ssl?
return false if ::Gitlab::Kubernetes::Helm.local_tiller_enabled?
cluster.application_helm.has_ssl?
end
def certificate_files def certificate_files
{ {
'ca.pem': ca_cert, 'ca.pem': ca_cert,
... ...
......
...@@ -92,8 +92,11 @@ module Clusters ...@@ -92,8 +92,11 @@ module Clusters
# When installing any application we are also performing an update # When installing any application we are also performing an update
# of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so # of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so
# therefore we need to reflect that in the database. # therefore we need to reflect that in the database.
unless ::Gitlab::Kubernetes::Helm.local_tiller_enabled?
application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION) application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
end end
end
after_transition any => [:uninstalling], :use_transactions => false do |application, _| after_transition any => [:uninstalling], :use_transactions => false do |application, _|
application.prepare_uninstall application.prepare_uninstall
... ...
......
...@@ -95,6 +95,10 @@ class Environment < ApplicationRecord ...@@ -95,6 +95,10 @@ class Environment < ApplicationRecord
end end
end end
def self.for_id_and_slug(id, slug)
find_by(id: id, slug: slug)
end
def self.max_deployment_id_sql def self.max_deployment_id_sql
Deployment.select(Deployment.arel_table[:id].maximum) Deployment.select(Deployment.arel_table[:id].maximum)
.where(Deployment.arel_table[:environment_id].eq(arel_table[:id])) .where(Deployment.arel_table[:environment_id].eq(arel_table[:id]))
... ...
......
# 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
...@@ -16,11 +16,18 @@ module Serverless ...@@ -16,11 +16,18 @@ module Serverless
algorithm: 'aes-256-gcm' algorithm: 'aes-256-gcm'
validates :pages_domain, :knative, presence: true 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' } 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 :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
end end
#badge-settings{ data: { api_endpoint_url: @badge_api_endpoint, #badge-settings{ data: { api_endpoint_url: @badge_api_endpoint,
docs_url: help_page_path('user/project/badges')} } docs_url: help_page_path('user/project/badges')} }
.text-center.prepend-top-default
= icon('spinner spin 2x')
---
title: Remove unused loading spinner from badge_settings partial
merge_request: 25044
author: nuwe1
type: other
...@@ -13,7 +13,7 @@ The Packages feature allows GitLab to act as a repository for the following: ...@@ -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+ | | [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+ | | [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+ | | [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 ## Suggested contributions
... ...
......