Skip to content
Snippets Groups Projects
Commit 211a8c33 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 456a7247
No related branches found
No related tags found
No related merge requests found
Showing
with 206 additions and 95 deletions
Loading
Loading
@@ -102,13 +102,13 @@ rspec migration pg9:
extends:
- .rspec-base-pg9
- .rspec-base-migration
parallel: 4
parallel: 5
 
rspec migration pg9-foss:
extends:
- .rspec-base-pg9-foss
- .rspec-base-migration
parallel: 4
parallel: 5
 
rspec unit pg9:
extends: .rspec-base-pg9
Loading
Loading
@@ -120,11 +120,11 @@ rspec unit pg9-foss:
 
rspec integration pg9:
extends: .rspec-base-pg9
parallel: 6
parallel: 8
 
rspec integration pg9-foss:
extends: .rspec-base-pg9-foss
parallel: 6
parallel: 8
 
rspec system pg9:
extends: .rspec-base-pg9
Loading
Loading
@@ -140,7 +140,7 @@ rspec unit pg10:
 
rspec integration pg10:
extends: .rspec-base-pg10
parallel: 6
parallel: 8
 
rspec system pg10:
extends: .rspec-base-pg10
Loading
Loading
@@ -170,11 +170,11 @@ rspec-ee unit pg9:
 
rspec-ee integration pg9:
extends: .rspec-ee-base-pg9
parallel: 3
parallel: 4
 
rspec-ee system pg9:
extends: .rspec-ee-base-pg9
parallel: 5
parallel: 6
 
rspec-ee migration pg10:
extends:
Loading
Loading
<script>
import { omit } from 'lodash';
import { omit, throttle } from 'lodash';
import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui';
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
Loading
Loading
@@ -18,6 +18,13 @@ import {
import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils';
 
const THROTTLED_DATAZOOM_WAIT = 1000; // miliseconds
const timestampToISODate = timestamp => new Date(timestamp).toISOString();
const events = {
datazoom: 'datazoom',
};
export default {
components: {
GlAreaChart,
Loading
Loading
@@ -98,6 +105,7 @@ export default {
height: chartHeight,
svgs: {},
primaryColor: null,
throttledDatazoom: null,
};
},
computed: {
Loading
Loading
@@ -245,6 +253,11 @@ export default {
this.setSvg('rocket');
this.setSvg('scroll-handle');
},
destroyed() {
if (this.throttledDatazoom) {
this.throttledDatazoom.cancel();
}
},
methods: {
formatLegendLabel(query) {
return `${query.label}`;
Loading
Loading
@@ -287,8 +300,39 @@ export default {
console.error('SVG could not be rendered correctly: ', e);
});
},
onChartUpdated(chart) {
[this.primaryColor] = chart.getOption().color;
onChartUpdated(eChart) {
[this.primaryColor] = eChart.getOption().color;
},
onChartCreated(eChart) {
// Emit a datazoom event that corresponds to the eChart
// `datazoom` event.
if (this.throttledDatazoom) {
// Chart can be created multiple times in this component's
// lifetime, remove previous handlers every time
// chart is created.
this.throttledDatazoom.cancel();
}
// Emitting is throttled to avoid flurries of calls when
// the user changes or scrolls the zoom bar.
this.throttledDatazoom = throttle(
() => {
const { startValue, endValue } = eChart.getOption().dataZoom[0];
this.$emit(events.datazoom, {
start: timestampToISODate(startValue),
end: timestampToISODate(endValue),
});
},
THROTTLED_DATAZOOM_WAIT,
{
leading: false,
},
);
eChart.off('datazoom');
eChart.on('datazoom', this.throttledDatazoom);
},
onResize() {
if (!this.$refs.chart) return;
Loading
Loading
@@ -331,6 +375,7 @@ export default {
:height="height"
:average-text="legendAverageText"
:max-text="legendMaxText"
@created="onChartCreated"
@updated="onChartUpdated"
>
<template v-if="tooltip.isDeployment">
Loading
Loading
Loading
Loading
@@ -21,7 +21,6 @@ import createFlash from '~/flash';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import Icon from '~/vue_shared/components/icon.vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
 
Loading
Loading
@@ -102,6 +101,11 @@ export default {
type: String,
required: true,
},
logsPath: {
type: String,
required: false,
default: invalidUrl,
},
defaultBranch: {
type: String,
required: true,
Loading
Loading
@@ -247,22 +251,20 @@ export default {
dashboardsEndpoint: this.dashboardsEndpoint,
currentDashboard: this.currentDashboard,
projectPath: this.projectPath,
logsPath: this.logsPath,
});
},
mounted() {
if (!this.hasMetrics) {
this.setGettingStartedEmptyState();
} else {
const { start, end } = convertToFixedRange(this.selectedTimeRange);
this.fetchData({
start,
end,
});
this.setTimeRange(this.selectedTimeRange);
this.fetchData();
}
},
methods: {
...mapActions('monitoringDashboard', [
'setTimeRange',
'fetchData',
'setGettingStartedEmptyState',
'setEndpoints',
Loading
Loading
Loading
Loading
@@ -19,14 +19,8 @@ export default {
},
data() {
const timeRange = timeRangeFromUrl(this.dashboardUrl) || defaultTimeRange;
const { start, end } = convertToFixedRange(timeRange);
const params = {
start,
end,
};
return {
params,
timeRange: convertToFixedRange(timeRange),
elWidth: 0,
};
},
Loading
Loading
@@ -49,7 +43,9 @@ export default {
},
mounted() {
this.setInitialState();
this.fetchMetricsData(this.params);
this.setTimeRange(this.timeRange);
this.fetchDashboard();
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
attributes: true,
Loading
Loading
@@ -64,7 +60,8 @@ export default {
},
methods: {
...mapActions('monitoringDashboard', [
'fetchMetricsData',
'setTimeRange',
'fetchDashboard',
'setEndpoints',
'setFeatureFlags',
'setShowErrorBanner',
Loading
Loading
<script>
import { mapState } from 'vuex';
import { pickBy } from 'lodash';
import invalidUrl from '~/lib/utils/invalid_url';
import {
GlDropdown,
GlDropdownItem,
Loading
Loading
@@ -18,7 +19,7 @@ import MonitorColumnChart from './charts/column.vue';
import MonitorStackedColumnChart from './charts/stacked_column.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import { downloadCSVOptions, generateLinkToChartOptions } from '../utils';
import { timeRangeToUrl, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
 
export default {
components: {
Loading
Loading
@@ -58,8 +59,13 @@ export default {
default: 'panel-type-chart',
},
},
data() {
return {
zoomedTimeRange: null,
};
},
computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath']),
...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
},
Loading
Loading
@@ -70,6 +76,14 @@ export default {
this.graphData.metrics[0].result.length > 0
);
},
logsPathWithTimeRange() {
const timeRange = this.zoomedTimeRange || this.timeRange;
if (this.logsPath && this.logsPath !== invalidUrl && timeRange) {
return timeRangeToUrl(timeRange, this.logsPath);
}
return null;
},
csvText() {
const chartData = this.graphData.metrics[0].result[0].values;
const yLabel = this.graphData.y_label;
Loading
Loading
@@ -107,6 +121,10 @@ export default {
},
downloadCSVOptions,
generateLinkToChartOptions,
onDatazoom({ start, end }) {
this.zoomedTimeRange = { start, end };
},
},
};
</script>
Loading
Loading
@@ -130,11 +148,13 @@ export default {
<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
Loading
Loading
@@ -157,6 +177,15 @@ export default {
<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-track-event="downloadCSVOptions(graphData.title)"
:href="downloadCsv"
Loading
Loading
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { gqClient, parseEnvironmentsResponse, removeLeadingSlash } from './utils';
import trackDashboardLoad from '../monitoring_tracking_helper';
import getEnvironments from '../queries/getEnvironments.query.graphql';
Loading
Loading
@@ -32,6 +33,10 @@ export const setEndpoints = ({ commit }, endpoints) => {
commit(types.SET_ENDPOINTS, endpoints);
};
 
export const setTimeRange = ({ commit }, timeRange) => {
commit(types.SET_TIME_RANGE, timeRange);
};
export const filterEnvironments = ({ commit, dispatch }, searchTerm) => {
commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
dispatch('fetchEnvironmentsData');
Loading
Loading
@@ -63,19 +68,24 @@ export const receiveEnvironmentsDataSuccess = ({ commit }, data) =>
export const receiveEnvironmentsDataFailure = ({ commit }) =>
commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE);
 
export const fetchData = ({ dispatch }, params) => {
dispatch('fetchMetricsData', params);
export const fetchData = ({ dispatch }) => {
dispatch('fetchDashboard');
dispatch('fetchDeploymentsData');
dispatch('fetchEnvironmentsData');
};
 
export const fetchMetricsData = ({ dispatch }, params) => dispatch('fetchDashboard', params);
export const fetchDashboard = ({ state, dispatch }, params) => {
export const fetchDashboard = ({ state, dispatch }) => {
dispatch('requestMetricsDashboard');
 
const params = {};
if (state.timeRange) {
const { start, end } = convertToFixedRange(state.timeRange);
params.start = start;
params.end = end;
}
if (state.currentDashboard) {
// eslint-disable-next-line no-param-reassign
params.dashboard = state.currentDashboard;
}
 
Loading
Loading
Loading
Loading
@@ -14,7 +14,7 @@ export const REQUEST_METRIC_RESULT = 'REQUEST_METRIC_RESULT';
export const RECEIVE_METRIC_RESULT_SUCCESS = 'RECEIVE_METRIC_RESULT_SUCCESS';
export const RECEIVE_METRIC_RESULT_FAILURE = 'RECEIVE_METRIC_RESULT_FAILURE';
 
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
Loading
Loading
Loading
Loading
@@ -182,6 +182,10 @@ export default {
state.dashboardsEndpoint = endpoints.dashboardsEndpoint;
state.currentDashboard = endpoints.currentDashboard;
state.projectPath = endpoints.projectPath;
state.logsPath = endpoints.logsPath || state.logsPath;
},
[types.SET_TIME_RANGE](state, timeRange) {
state.timeRange = timeRange;
},
[types.SET_GETTING_STARTED_EMPTY_STATE](state) {
state.emptyState = 'gettingStarted';
Loading
Loading
import invalidUrl from '~/lib/utils/invalid_url';
 
export default () => ({
// API endpoints
metricsEndpoint: null,
deploymentsEndpoint: null,
dashboardEndpoint: invalidUrl,
// Dashboard request parameters
timeRange: null,
currentDashboard: null,
// Dashboard data
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
dashboard: {
panel_groups: [],
},
allDashboards: [],
 
// Other project data
deploymentData: [],
environments: [],
environmentsSearchTerm: '',
environmentsLoading: false,
allDashboards: [],
currentDashboard: null,
// GitLab paths to other pages
projectPath: null,
logsPath: invalidUrl,
});
Loading
Loading
@@ -103,8 +103,9 @@ export const graphDataValidatorForAnomalyValues = graphData => {
/**
* Returns a time range from the current URL params
*
* @returns {Object} The time range defined by the
* current URL, reading from `window.location.search`
* @returns {Object|null} The time range defined by the
* current URL, reading from search query or `window.location.search`.
* Returns `null` if no parameters form a time range.
*/
export const timeRangeFromUrl = (search = window.location.search) => {
const params = queryToObject(search);
Loading
Loading
Loading
Loading
@@ -102,6 +102,7 @@
padding-bottom: 0.3em;
border-bottom: 1px solid $white-dark;
color: $gl-text-color;
overflow: hidden;
 
&:first-child {
margin-top: 0;
Loading
Loading
@@ -115,6 +116,7 @@
padding-bottom: 0.3em;
border-bottom: 1px solid $white-dark;
color: $gl-text-color;
overflow: hidden;
}
 
h3 {
Loading
Loading
Loading
Loading
@@ -7,7 +7,7 @@ class Admin::ServicesController < Admin::ApplicationController
before_action :service, only: [:edit, :update]
 
def index
@services = services_templates
@services = instance_level_services
end
 
def edit
Loading
Loading
@@ -19,7 +19,7 @@ class Admin::ServicesController < Admin::ApplicationController
 
def update
if service.update(service_params[:service])
PropagateServiceTemplateWorker.perform_async(service.id) if service.active?
PropagateInstanceLevelServiceWorker.perform_async(service.id) if service.active?
 
redirect_to admin_application_settings_services_path,
notice: 'Application settings saved successfully'
Loading
Loading
@@ -31,17 +31,17 @@ class Admin::ServicesController < Admin::ApplicationController
private
 
# rubocop: disable CodeReuse/ActiveRecord
def services_templates
def instance_level_services
Service.available_services_names.map do |service_name|
service_template = "#{service_name}_service".camelize.constantize
service_template.where(template: true).first_or_create
service = "#{service_name}_service".camelize.constantize
service.where(instance: true).first_or_create
end
end
# rubocop: enable CodeReuse/ActiveRecord
 
# rubocop: disable CodeReuse/ActiveRecord
def service
@service ||= Service.where(id: params[:id], template: true).first
@service ||= Service.where(id: params[:id], instance: true).first
end
# rubocop: enable CodeReuse/ActiveRecord
 
Loading
Loading
Loading
Loading
@@ -112,10 +112,6 @@ module LfsRequest
has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
end
 
def storage_project
@storage_project ||= project.lfs_storage_project
end
def objects
@objects ||= (params[:objects] || []).to_a
end
Loading
Loading
Loading
Loading
@@ -80,12 +80,13 @@ module Repositories
LfsObject.create!(oid: oid, size: size, file: uploaded_file)
end
 
# rubocop: disable CodeReuse/ActiveRecord
def link_to_project!(object)
if object && !object.projects.exists?(storage_project.id)
object.lfs_objects_projects.create!(project: storage_project)
end
return unless object
LfsObjectsProject.safe_find_or_create_by!(
project: project,
lfs_object: object
)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
Loading
Loading
@@ -8,13 +8,13 @@ module Sortable
extend ActiveSupport::Concern
 
included do
scope :with_order_id_desc, -> { order(id: :desc) }
scope :order_id_desc, -> { reorder(id: :desc) }
scope :order_id_asc, -> { reorder(id: :asc) }
scope :order_created_desc, -> { reorder(created_at: :desc) }
scope :order_created_asc, -> { reorder(created_at: :asc) }
scope :order_updated_desc, -> { reorder(updated_at: :desc) }
scope :order_updated_asc, -> { reorder(updated_at: :asc) }
scope :with_order_id_desc, -> { order(self.arel_table['id'].desc) }
scope :order_id_desc, -> { reorder(self.arel_table['id'].desc) }
scope :order_id_asc, -> { reorder(self.arel_table['id'].asc) }
scope :order_created_desc, -> { reorder(self.arel_table['created_at'].desc) }
scope :order_created_asc, -> { reorder(self.arel_table['created_at'].asc) }
scope :order_updated_desc, -> { reorder(self.arel_table['updated_at'].desc) }
scope :order_updated_asc, -> { reorder(self.arel_table['updated_at'].asc) }
scope :order_name_asc, -> { reorder(Arel::Nodes::Ascending.new(arel_table[:name].lower)) }
scope :order_name_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:name].lower)) }
end
Loading
Loading
Loading
Loading
@@ -358,6 +358,8 @@ class Project < ApplicationRecord
project_path: true,
length: { maximum: 255 }
 
validates :project_feature, presence: true
validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id }
validates :import_url, public_url: { schemes: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
Loading
Loading
@@ -395,11 +397,11 @@ class Project < ApplicationRecord
 
# last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push
scope :sorted_by_activity, -> { reorder(Arel.sql("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC")) }
scope :sorted_by_stars_desc, -> { reorder(star_count: :desc) }
scope :sorted_by_stars_asc, -> { reorder(star_count: :asc) }
scope :sorted_by_stars_desc, -> { reorder(self.arel_table['star_count'].desc) }
scope :sorted_by_stars_asc, -> { reorder(self.arel_table['star_count'].asc) }
scope :sorted_by_name_asc_limited, ->(limit) { reorder(name: :asc).limit(limit) }
# Sometimes queries (e.g. using CTEs) require explicit disambiguation with table name
scope :projects_order_id_desc, -> { reorder("#{table_name}.id DESC") }
scope :projects_order_id_desc, -> { reorder(self.arel_table['id'].desc) }
 
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
Loading
Loading
@@ -595,9 +597,9 @@ class Project < ApplicationRecord
# pass a string to avoid AR adding the table name
reorder('project_statistics.storage_size DESC, projects.id DESC')
when 'latest_activity_desc'
reorder(last_activity_at: :desc)
reorder(self.arel_table['last_activity_at'].desc)
when 'latest_activity_asc'
reorder(last_activity_at: :asc)
reorder(self.arel_table['last_activity_at'].asc)
when 'stars_desc'
sorted_by_stars_desc
when 'stars_asc'
Loading
Loading
@@ -1219,13 +1221,13 @@ class Project < ApplicationRecord
service = find_service(services, name)
return service if service
 
# We should check if template for the service exists
template = find_service(services_templates, name)
# We should check if an instance-level service exists
instance_level_service = find_service(instance_level_services, name)
 
if template
Service.build_from_template(id, template)
if instance_level_service
Service.build_from_instance(id, instance_level_service)
else
# If no template, we should create an instance. Ex `build_gitlab_ci_service`
# If no instance-level service exists, we should create a new service. Ex `build_gitlab_ci_service`
public_send("build_#{name}_service") # rubocop:disable GitlabSecurity/PublicSend
end
end
Loading
Loading
@@ -1357,6 +1359,10 @@ class Project < ApplicationRecord
forked_from_project || fork_network&.root_project
end
 
# TODO: Remove this method once all LfsObjectsProject records are backfilled
# for forks.
#
# See https://gitlab.com/gitlab-org/gitlab/issues/122002 for more info.
def lfs_storage_project
@lfs_storage_project ||= begin
result = self
Loading
Loading
@@ -1369,14 +1375,27 @@ class Project < ApplicationRecord
end
end
 
# This will return all `lfs_objects` that are accessible to the project.
# So this might be `self.lfs_objects` if the project is not part of a fork
# network, or it is the base of the fork network.
# This will return all `lfs_objects` that are accessible to the project and
# the fork source. This is needed since older forks won't have access to some
# LFS objects directly and have to get it from the fork source.
#
# TODO: Remove this method once all LfsObjectsProject records are backfilled
# for forks. At that point, projects can look at their own `lfs_objects`.
#
# TODO: refactor this to get the correct lfs objects when implementing
# https://gitlab.com/gitlab-org/gitlab-foss/issues/39769
# See https://gitlab.com/gitlab-org/gitlab/issues/122002 for more info.
def all_lfs_objects
lfs_storage_project.lfs_objects
LfsObject
.distinct
.joins(:lfs_objects_projects)
.where(lfs_objects_projects: { project_id: [self, lfs_storage_project] })
end
# TODO: Call `#lfs_objects` instead once all LfsObjectsProject records are
# backfilled. At that point, projects can look at their own `lfs_objects`.
#
# See https://gitlab.com/gitlab-org/gitlab/issues/122002 for more info.
def lfs_objects_oids
all_lfs_objects.pluck(:oid)
end
 
def personal?
Loading
Loading
@@ -2438,8 +2457,8 @@ class Project < ApplicationRecord
end
end
 
def services_templates
@services_templates ||= Service.where(template: true)
def instance_level_services
@instance_level_services ||= Service.where(instance: true)
end
 
def ensure_pages_metadatum
Loading
Loading
Loading
Loading
@@ -164,7 +164,7 @@ class IssueTrackerService < Service
end
 
def one_issue_tracker
return if template?
return if instance?
return if project.blank?
 
if project.services.external_issue_trackers.where.not(id: id).any?
Loading
Loading
Loading
Loading
@@ -85,7 +85,7 @@ class PrometheusService < MonitoringService
end
 
def prometheus_available?
return false if template?
return false if instance?
return false unless project
 
project.all_clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
Loading
Loading
Loading
Loading
@@ -32,7 +32,7 @@ class Service < ApplicationRecord
belongs_to :project, inverse_of: :services
has_one :service_hook
 
validates :project_id, presence: true, unless: proc { |service| service.template? }
validates :project_id, presence: true, unless: proc { |service| service.instance? }
validates :type, presence: true
 
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
Loading
Loading
@@ -70,8 +70,8 @@ class Service < ApplicationRecord
true
end
 
def template?
template
def instance?
instance
end
 
def category
Loading
Loading
@@ -299,15 +299,15 @@ class Service < ApplicationRecord
service_names.sort_by(&:downcase)
end
 
def self.build_from_template(project_id, template)
service = template.dup
def self.build_from_instance(project_id, instance_level_service)
service = instance_level_service.dup
 
if template.supports_data_fields?
data_fields = template.data_fields.dup
if instance_level_service.supports_data_fields?
data_fields = instance_level_service.data_fields.dup
data_fields.service = service
end
 
service.template = false
service.instance = false
service.project_id = project_id
service.active = false if service.active? && !service.valid?
service
Loading
Loading
@@ -321,10 +321,6 @@ class Service < ApplicationRecord
nil
end
 
def self.find_by_template
find_by(template: true)
end
# override if needed
def supports_data_fields?
false
Loading
Loading
Loading
Loading
@@ -302,7 +302,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
cluster_link = clusters.count == 1 ? project_cluster_path(project, clusters.first) : project_clusters_path(project)
 
AnchorData.new(false,
_('Kubernetes configured'),
_('Kubernetes'),
cluster_link,
'default')
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment