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

Add latest changes from gitlab-org/gitlab@master

parent 17b91a3c
No related branches found
No related tags found
No related merge requests found
Showing
with 233 additions and 49 deletions
Loading
Loading
@@ -490,6 +490,8 @@ export const historyPushState = newUrl => {
*/
export const parseBoolean = value => (value && value.toString()) === 'true';
 
export const BACKOFF_TIMEOUT = 'BACKOFF_TIMEOUT';
/**
* @callback backOffCallback
* @param {Function} next
Loading
Loading
@@ -541,7 +543,7 @@ export const backOff = (fn, timeout = 60000) => {
timeElapsed += nextInterval;
nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
} else {
reject(new Error('BACKOFF_TIMEOUT'));
reject(new Error(BACKOFF_TIMEOUT));
}
};
 
Loading
Loading
Loading
Loading
@@ -21,6 +21,7 @@ const httpStatusCodes = {
NOT_FOUND: 404,
GONE: 410,
UNPROCESSABLE_ENTITY: 422,
SERVICE_UNAVAILABLE: 503,
};
 
export const successCodes = [
Loading
Loading
Loading
Loading
@@ -245,3 +245,38 @@ export function objectToQuery(obj) {
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`)
.join('&');
}
/**
* Sets query params for a given URL
* It adds new query params, updates existing params with a new value and removes params with value null/undefined
*
* @param {Object} params The query params to be set/updated
* @param {String} url The url to be operated on
* @param {Boolean} clearParams Indicates whether existing query params should be removed or not
* @returns {String} A copy of the original with the updated query params
*/
export const setUrlParams = (params, url = window.location.href, clearParams = false) => {
const urlObj = new URL(url);
const queryString = urlObj.search;
const searchParams = clearParams ? new URLSearchParams('') : new URLSearchParams(queryString);
Object.keys(params).forEach(key => {
if (params[key] === null || params[key] === undefined) {
searchParams.delete(key);
} else if (Array.isArray(params[key])) {
params[key].forEach((val, idx) => {
if (idx === 0) {
searchParams.set(key, val);
} else {
searchParams.append(key, val);
}
});
} else {
searchParams.set(key, params[key]);
}
});
urlObj.search = searchParams.toString();
return urlObj.toString();
};
import { __ } from '~/locale';
 
export const PROMETHEUS_TIMEOUT = 120000; // TWO_MINUTES
/**
* Errors in Prometheus Queries (PromQL) for metrics
*/
export const metricsErrors = {
/**
* Connection timed out to prometheus server
* the timeout is set to PROMETHEUS_TIMEOUT
*
*/
TIMEOUT: 'TIMEOUT',
/**
* The prometheus server replies with an empty data set
*/
NO_DATA: 'NO_DATA',
/**
* The prometheus server cannot be reached
*/
CONNECTION_FAILED: 'CONNECTION_FAILED',
/**
* The prometheus server was reach but it cannot process
* the query. This can happen for several reasons:
* - PromQL syntax is incorrect
* - An operator is not supported
*/
BAD_DATA: 'BAD_DATA',
/**
* No specific reason found for error
*/
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
};
export const sidebarAnimationDuration = 300; // milliseconds.
 
export const chartHeight = 300;
Loading
Loading
Loading
Loading
@@ -6,7 +6,7 @@ import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale';
 
const TWO_MINUTES = 120000;
import { PROMETHEUS_TIMEOUT } from '../constants';
 
function backOffRequest(makeRequestCallback) {
return backOff((next, stop) => {
Loading
Loading
@@ -19,7 +19,7 @@ function backOffRequest(makeRequestCallback) {
}
})
.catch(stop);
}, TWO_MINUTES);
}, PROMETHEUS_TIMEOUT);
}
 
export const setGettingStartedEmptyState = ({ commit }) => {
Loading
Loading
@@ -125,9 +125,17 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
step,
};
 
return fetchPrometheusResult(metric.prometheus_endpoint_path, queryParams).then(result => {
commit(types.SET_QUERY_RESULT, { metricId: metric.metric_id, result });
});
commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metric_id });
return fetchPrometheusResult(metric.prometheus_endpoint_path, queryParams)
.then(result => {
commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metric_id, result });
})
.catch(error => {
commit(types.RECEIVE_METRIC_RESULT_ERROR, { metricId: metric.metric_id, error });
// Continue to throw error so the dashboard can notify using createFlash
throw error;
});
};
 
export const fetchPrometheusMetrics = ({ state, commit, dispatch, getters }, params) => {
Loading
Loading
@@ -159,7 +167,8 @@ export const fetchDeploymentsData = ({ state, dispatch }) => {
if (!state.deploymentsEndpoint) {
return Promise.resolve([]);
}
return backOffRequest(() => axios.get(state.deploymentsEndpoint))
return axios
.get(state.deploymentsEndpoint)
.then(resp => resp.data)
.then(response => {
if (!response || !response.deployments) {
Loading
Loading
export const REQUEST_METRICS_DATA = 'REQUEST_METRICS_DATA';
export const RECEIVE_METRICS_DATA_SUCCESS = 'RECEIVE_METRICS_DATA_SUCCESS';
export const RECEIVE_METRICS_DATA_FAILURE = 'RECEIVE_METRICS_DATA_FAILURE';
export const REQUEST_DEPLOYMENTS_DATA = 'REQUEST_DEPLOYMENTS_DATA';
export const RECEIVE_DEPLOYMENTS_DATA_SUCCESS = 'RECEIVE_DEPLOYMENTS_DATA_SUCCESS';
export const RECEIVE_DEPLOYMENTS_DATA_FAILURE = 'RECEIVE_DEPLOYMENTS_DATA_FAILURE';
export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA';
export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS';
export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAILURE';
export const SET_QUERY_RESULT = 'SET_QUERY_RESULT';
export const REQUEST_METRIC_RESULT = 'REQUEST_METRIC_RESULT';
export const RECEIVE_METRIC_RESULT_SUCCESS = 'RECEIVE_METRIC_RESULT_SUCCESS';
export const RECEIVE_METRIC_RESULT_ERROR = 'RECEIVE_METRIC_RESULT_ERROR';
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
Loading
Loading
Loading
Loading
@@ -2,6 +2,9 @@ import Vue from 'vue';
import { slugify } from '~/lib/utils/text_utility';
import * as types from './mutation_types';
import { normalizeMetric, normalizeQueryResult } from './utils';
import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils';
import { metricsErrors } from '../constants';
import httpStatusCodes from '~/lib/utils/http_status';
 
const normalizePanelMetrics = (metrics, defaultLabel) =>
metrics.map(metric => ({
Loading
Loading
@@ -9,7 +12,74 @@ const normalizePanelMetrics = (metrics, defaultLabel) =>
label: metric.label || defaultLabel,
}));
 
/**
* Locate and return a metric in the dashboard by its id
* as generated by `uniqMetricsId()`.
* @param {String} metricId Unique id in the dashboard
* @param {Object} dashboard Full dashboard object
*/
const findMetricInDashboard = (metricId, dashboard) => {
let res = null;
dashboard.panel_groups.forEach(group => {
group.panels.forEach(panel => {
panel.metrics.forEach(metric => {
if (metric.metric_id === metricId) {
res = metric;
}
});
});
});
return res;
};
/**
* Set a new state for a metric.
*
* Initally metric data is not populated, so `Vue.set` is
* used to add new properties to the metric.
*
* @param {Object} metric - Metric object as defined in the dashboard
* @param {Object} state - New state
* @param {Array|null} state.result - Array of results
* @param {String} state.error - Error code from metricsErrors
* @param {Boolean} state.loading - True if the metric is loading
*/
const setMetricState = (metric, { result = null, error = null, loading = false }) => {
Vue.set(metric, 'result', result);
Vue.set(metric, 'error', error);
Vue.set(metric, 'loading', loading);
};
/**
* Maps a backened error state to a `metricsErrors` constant
* @param {Object} error - Error from backend response
*/
const getMetricError = error => {
if (!error) {
return metricsErrors.UNKNOWN_ERROR;
}
// Special error responses
if (error.message === BACKOFF_TIMEOUT) {
return metricsErrors.TIMEOUT;
}
// Axios error responses
const { response } = error;
if (response && response.status === httpStatusCodes.SERVICE_UNAVAILABLE) {
return metricsErrors.CONNECTION_FAILED;
} else if (response && response.status === httpStatusCodes.BAD_REQUEST) {
// Note: "error.response.data.error" may contain Prometheus error information
return metricsErrors.BAD_DATA;
}
return metricsErrors.UNKNOWN_ERROR;
};
export default {
/**
* Dashboard panels structure and global state
*/
[types.REQUEST_METRICS_DATA](state) {
state.emptyState = 'loading';
state.showEmptyState = true;
Loading
Loading
@@ -40,6 +110,10 @@ export default {
state.emptyState = error ? 'unableToConnect' : 'noData';
state.showEmptyState = true;
},
/**
* Deployments and environments
*/
[types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](state, deployments) {
state.deploymentData = deployments;
},
Loading
Loading
@@ -53,28 +127,46 @@ export default {
state.environments = [];
},
 
[types.SET_QUERY_RESULT](state, { metricId, result }) {
if (!metricId || !result || result.length === 0) {
/**
* Individual panel/metric results
*/
[types.REQUEST_METRIC_RESULT](state, { metricId }) {
const metric = findMetricInDashboard(metricId, state.dashboard);
setMetricState(metric, {
loading: true,
});
},
[types.RECEIVE_METRIC_RESULT_SUCCESS](state, { metricId, result }) {
if (!metricId) {
return;
}
 
state.showEmptyState = false;
 
/**
* Search the dashboard state for a matching id
*/
state.dashboard.panel_groups.forEach(group => {
group.panels.forEach(panel => {
panel.metrics.forEach(metric => {
if (metric.metric_id === metricId) {
// ensure dates/numbers are correctly formatted for charts
const normalizedResults = result.map(normalizeQueryResult);
Vue.set(metric, 'result', Object.freeze(normalizedResults));
}
});
const metric = findMetricInDashboard(metricId, state.dashboard);
if (!result || result.length === 0) {
// If no data is return we still consider it an error and set it to undefined
setMetricState(metric, {
error: metricsErrors.NO_DATA,
});
} else {
const normalizedResults = result.map(normalizeQueryResult);
setMetricState(metric, {
result: Object.freeze(normalizedResults),
});
}
},
[types.RECEIVE_METRIC_RESULT_ERROR](state, { metricId, error }) {
if (!metricId) {
return;
}
const metric = findMetricInDashboard(metricId, state.dashboard);
setMetricState(metric, {
error: getMetricError(error),
});
},
[types.SET_ENDPOINTS](state, endpoints) {
state.metricsEndpoint = endpoints.metricsEndpoint;
state.environmentsEndpoint = endpoints.environmentsEndpoint;
Loading
Loading
Loading
Loading
@@ -8,9 +8,11 @@ export default () => ({
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
dashboard: {
panel_groups: [],
},
deploymentData: [],
environments: [],
allDashboards: [],
Loading
Loading
Loading
Loading
@@ -23,7 +23,7 @@ class ApplicationController < ActionController::Base
before_action :validate_user_service_ticket!
before_action :check_password_expiration, if: :html_request?
before_action :ldap_security_check
before_action :sentry_context
around_action :sentry_context
before_action :default_headers
before_action :add_gon_variables, if: :html_request?
before_action :configure_permitted_parameters, if: :devise_controller?
Loading
Loading
@@ -165,7 +165,7 @@ class ApplicationController < ActionController::Base
end
 
def log_exception(exception)
Gitlab::Sentry.track_acceptable_exception(exception)
Gitlab::Sentry.track_exception(exception)
 
backtrace_cleaner = request.env["action_dispatch.backtrace_cleaner"]
application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace
Loading
Loading
@@ -532,8 +532,8 @@ class ApplicationController < ActionController::Base
@impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
end
 
def sentry_context
Gitlab::Sentry.context(current_user)
def sentry_context(&block)
Gitlab::Sentry.with_context(current_user, &block)
end
 
def allow_gitaly_ref_name_caching
Loading
Loading
Loading
Loading
@@ -98,13 +98,11 @@ module IssuableActions
 
error_message = "Destroy confirmation not provided for #{issuable.human_class_name}"
exception = RuntimeError.new(error_message)
Gitlab::Sentry.track_acceptable_exception(
Gitlab::Sentry.track_exception(
exception,
extra: {
project_path: issuable.project.full_path,
issuable_type: issuable.class.name,
issuable_id: issuable.id
}
project_path: issuable.project.full_path,
issuable_type: issuable.class.name,
issuable_id: issuable.id
)
 
index_path = polymorphic_path([parent, issuable.class])
Loading
Loading
Loading
Loading
@@ -42,11 +42,9 @@ module IconsHelper
end
 
def sprite_icon(icon_name, size: nil, css_class: nil)
if Gitlab::Sentry.should_raise_for_dev?
unless known_sprites.include?(icon_name)
exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg")
raise exception
end
unless known_sprites.include?(icon_name)
exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg")
Gitlab::Sentry.track_and_raise_for_dev_exception(exception)
end
 
css_classes = []
Loading
Loading
Loading
Loading
@@ -57,7 +57,7 @@ module UsersHelper
 
unless user.association(:status).loaded?
exception = RuntimeError.new("Status was not preloaded")
Gitlab::Sentry.track_exception(exception, extra: { user: user.inspect })
Gitlab::Sentry.track_and_raise_for_dev_exception(exception, user: user.inspect)
end
 
return unless user.status
Loading
Loading
Loading
Loading
@@ -289,7 +289,7 @@ module Ci
begin
build.deployment.drop!
rescue => e
Gitlab::Sentry.track_exception(e, extra: { build_id: build.id })
Gitlab::Sentry.track_and_raise_for_dev_exception(e, build_id: build.id)
end
 
true
Loading
Loading
Loading
Loading
@@ -27,7 +27,7 @@ module Ci
create_ref(sha, path)
rescue => e
Gitlab::Sentry
.track_acceptable_exception(e, extra: { pipeline_id: pipeline.id })
.track_exception(e, pipeline_id: pipeline.id)
end
 
def delete
Loading
Loading
@@ -38,7 +38,7 @@ module Ci
# no-op
rescue => e
Gitlab::Sentry
.track_acceptable_exception(e, extra: { pipeline_id: pipeline.id })
.track_exception(e, pipeline_id: pipeline.id)
end
 
def path
Loading
Loading
Loading
Loading
@@ -71,6 +71,8 @@ module Clusters
# `proxy_url` could raise an exception because gitlab can not communicate with the cluster.
# We check for a nil client in downstream use and behaviour is equivalent to an empty state
log_exception(error, :failed_to_create_elasticsearch_client)
nil
end
end
 
Loading
Loading
Loading
Loading
@@ -335,7 +335,7 @@ module Clusters
rescue Kubeclient::HttpError => e
kubeclient_error_status(e.message)
rescue => e
Gitlab::Sentry.track_acceptable_exception(e, extra: { cluster_id: id })
Gitlab::Sentry.track_exception(e, cluster_id: id)
 
:unknown_failure
else
Loading
Loading
Loading
Loading
@@ -76,7 +76,7 @@ module Clusters
message: error.message
})
 
Gitlab::Sentry.track_acceptable_exception(error, extra: { cluster_id: cluster&.id, application_id: id })
Gitlab::Sentry.track_exception(error, cluster_id: cluster&.id, application_id: id)
end
end
end
Loading
Loading
Loading
Loading
@@ -48,11 +48,11 @@ module GroupDescendant
extras = {
parent: parent.inspect,
child: child.inspect,
preloaded: preloaded.map(&:full_path)
preloaded: preloaded.map(&:full_path),
issue_url: 'https://gitlab.com/gitlab-org/gitlab-foss/issues/49404'
}
issue_url = 'https://gitlab.com/gitlab-org/gitlab-foss/issues/49404'
 
Gitlab::Sentry.track_exception(exception, issue_url: issue_url, extra: extras)
Gitlab::Sentry.track_and_raise_for_dev_exception(exception, extras)
end
 
if parent.nil? && hierarchy_top.present?
Loading
Loading
Loading
Loading
@@ -37,8 +37,10 @@ module Storage
send_update_instructions
write_projects_repository_config
rescue => e
# Raise if development/test environment, else just notify Sentry
Gitlab::Sentry.track_exception(e, extra: { full_path_before_last_save: full_path_before_last_save, full_path: full_path, action: 'move_dir' })
Gitlab::Sentry.track_and_raise_for_dev_exception(e,
full_path_before_last_save: full_path_before_last_save,
full_path: full_path,
action: 'move_dir')
end
 
true # false would cancel later callbacks but not rollback
Loading
Loading
Loading
Loading
@@ -1514,7 +1514,7 @@ class MergeRequest < ApplicationRecord
end
end
rescue ActiveRecord::LockWaitTimeout => e
Gitlab::Sentry.track_acceptable_exception(e)
Gitlab::Sentry.track_exception(e)
raise RebaseLockTimeout, REBASE_LOCK_MESSAGE
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