2021-03-08 18:12:59 +05:30
import { TEST _HOST } from 'helpers/test_constants' ;
2020-05-24 23:13:21 +05:30
import * as urlUtils from '~/lib/utils/url_utility' ;
2021-03-11 19:13:27 +05:30
import * as monitoringUtils from '~/monitoring/utils' ;
2020-05-24 23:13:21 +05:30
import { metricsDashboardViewModel , graphData } from './fixture_data' ;
2021-03-11 19:13:27 +05:30
import { singleStatGraphData , anomalyGraphData } from './graph_data' ;
import { mockProjectDir , barMockData } from './mock_data' ;
2020-03-13 15:44:24 +05:30
2020-04-22 19:07:51 +05:30
const mockPath = ` ${ TEST _HOST } ${ mockProjectDir } /-/environments/29/metrics ` ;
2020-03-13 15:44:24 +05:30
const generatedLink = 'http://chart.link.com' ;
const chartTitle = 'Some metric chart' ;
const range = {
start : '2019-01-01T00:00:00.000Z' ,
end : '2019-01-10T00:00:00.000Z' ,
} ;
const rollingRange = {
duration : { seconds : 120 } ,
} ;
2020-01-01 13:55:28 +05:30
describe ( 'monitoring/utils' , ( ) => {
2019-12-21 20:55:43 +05:30
describe ( 'trackGenerateLinkToChartEventOptions' , ( ) => {
it ( 'should return Cluster Monitoring options if located on Cluster Health Dashboard' , ( ) => {
document . body . dataset . page = 'groups:clusters:show' ;
expect ( monitoringUtils . generateLinkToChartOptions ( generatedLink ) ) . toEqual ( {
category : 'Cluster Monitoring' ,
action : 'generate_link_to_cluster_metric_chart' ,
label : 'Chart link' ,
property : generatedLink ,
} ) ;
} ) ;
it ( 'should return Incident Management event options if located on Metrics Dashboard' , ( ) => {
document . body . dataset . page = 'metrics:show' ;
expect ( monitoringUtils . generateLinkToChartOptions ( generatedLink ) ) . toEqual ( {
category : 'Incident Management::Embedded metrics' ,
action : 'generate_link_to_metrics_chart' ,
label : 'Chart link' ,
property : generatedLink ,
} ) ;
} ) ;
} ) ;
describe ( 'trackDownloadCSVEvent' , ( ) => {
it ( 'should return Cluster Monitoring options if located on Cluster Health Dashboard' , ( ) => {
document . body . dataset . page = 'groups:clusters:show' ;
expect ( monitoringUtils . downloadCSVOptions ( chartTitle ) ) . toEqual ( {
category : 'Cluster Monitoring' ,
action : 'download_csv_of_cluster_metric_chart' ,
label : 'Chart title' ,
property : chartTitle ,
} ) ;
} ) ;
it ( 'should return Incident Management event options if located on Metrics Dashboard' , ( ) => {
document . body . dataset . page = 'metriss:show' ;
expect ( monitoringUtils . downloadCSVOptions ( chartTitle ) ) . toEqual ( {
category : 'Incident Management::Embedded metrics' ,
action : 'download_csv_of_metrics_dashboard_chart' ,
label : 'Chart title' ,
property : chartTitle ,
} ) ;
} ) ;
} ) ;
2020-01-01 13:55:28 +05:30
describe ( 'graphDataValidatorForValues' , ( ) => {
/ *
* When dealing with a metric using the query format , e . g .
* query : 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024'
* the validator will look for the ` value ` key instead of ` values `
* /
it ( 'validates data with the query format' , ( ) => {
const validGraphData = monitoringUtils . graphDataValidatorForValues (
true ,
2020-07-28 23:09:34 +05:30
singleStatGraphData ( ) ,
2020-01-01 13:55:28 +05:30
) ;
expect ( validGraphData ) . toBe ( true ) ;
} ) ;
/ *
* When dealing with a metric using the query ? range format , e . g .
* query _range : 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024' ,
* the validator will look for the ` values ` key instead of ` value `
* /
it ( 'validates data with the query_range format' , ( ) => {
2020-04-22 19:07:51 +05:30
const validGraphData = monitoringUtils . graphDataValidatorForValues ( false , graphData ) ;
2020-01-01 13:55:28 +05:30
expect ( validGraphData ) . toBe ( true ) ;
} ) ;
} ) ;
describe ( 'graphDataValidatorForAnomalyValues' , ( ) => {
let oneMetric ;
let threeMetrics ;
let fourMetrics ;
beforeEach ( ( ) => {
2020-07-28 23:09:34 +05:30
oneMetric = singleStatGraphData ( ) ;
threeMetrics = anomalyGraphData ( ) ;
2020-01-01 13:55:28 +05:30
const metrics = [ ... threeMetrics . metrics ] ;
metrics . push ( threeMetrics . metrics [ 0 ] ) ;
fourMetrics = {
2020-07-28 23:09:34 +05:30
... anomalyGraphData ( ) ,
2020-01-01 13:55:28 +05:30
metrics ,
} ;
} ) ;
/ *
* Anomaly charts can accept results for exactly 3 metrics ,
* /
it ( 'validates passes with the right query format' , ( ) => {
expect ( monitoringUtils . graphDataValidatorForAnomalyValues ( threeMetrics ) ) . toBe ( true ) ;
} ) ;
it ( 'validation fails for wrong format, 1 metric' , ( ) => {
expect ( monitoringUtils . graphDataValidatorForAnomalyValues ( oneMetric ) ) . toBe ( false ) ;
} ) ;
it ( 'validation fails for wrong format, more than 3 metrics' , ( ) => {
expect ( monitoringUtils . graphDataValidatorForAnomalyValues ( fourMetrics ) ) . toBe ( false ) ;
} ) ;
} ) ;
2020-03-13 15:44:24 +05:30
describe ( 'timeRangeFromUrl' , ( ) => {
2020-05-24 23:13:21 +05:30
beforeEach ( ( ) => {
jest . spyOn ( urlUtils , 'queryToObject' ) ;
} ) ;
afterEach ( ( ) => {
urlUtils . queryToObject . mockRestore ( ) ;
} ) ;
2020-03-13 15:44:24 +05:30
2020-05-24 23:13:21 +05:30
const { timeRangeFromUrl } = monitoringUtils ;
2020-03-13 15:44:24 +05:30
2020-05-24 23:13:21 +05:30
it ( 'returns a fixed range when query contains `start` and `end` parameters are given' , ( ) => {
urlUtils . queryToObject . mockReturnValueOnce ( range ) ;
2020-03-13 15:44:24 +05:30
expect ( timeRangeFromUrl ( ) ) . toEqual ( range ) ;
} ) ;
2020-05-24 23:13:21 +05:30
it ( 'returns a rolling range when query contains `duration_seconds` parameters are given' , ( ) => {
2020-03-13 15:44:24 +05:30
const { seconds } = rollingRange . duration ;
2020-05-24 23:13:21 +05:30
urlUtils . queryToObject . mockReturnValueOnce ( {
2020-03-13 15:44:24 +05:30
dashboard : '.gitlab/dashboard/my_dashboard.yml' ,
duration _seconds : ` ${ seconds } ` ,
} ) ;
expect ( timeRangeFromUrl ( ) ) . toEqual ( rollingRange ) ;
} ) ;
2020-05-24 23:13:21 +05:30
it ( 'returns null when no time range parameters are given' , ( ) => {
urlUtils . queryToObject . mockReturnValueOnce ( {
2020-03-13 15:44:24 +05:30
dashboard : '.gitlab/dashboards/custom_dashboard.yml' ,
param1 : 'value1' ,
param2 : 'value2' ,
2020-05-24 23:13:21 +05:30
} ) ;
2020-03-13 15:44:24 +05:30
2020-05-24 23:13:21 +05:30
expect ( timeRangeFromUrl ( ) ) . toBe ( null ) ;
} ) ;
} ) ;
2020-07-01 16:08:20 +05:30
describe ( 'templatingVariablesFromUrl' , ( ) => {
const { templatingVariablesFromUrl } = monitoringUtils ;
2020-05-24 23:13:21 +05:30
beforeEach ( ( ) => {
jest . spyOn ( urlUtils , 'queryToObject' ) ;
} ) ;
afterEach ( ( ) => {
urlUtils . queryToObject . mockRestore ( ) ;
} ) ;
it ( 'returns an object with only the custom variables' , ( ) => {
urlUtils . queryToObject . mockReturnValueOnce ( {
dashboard : '.gitlab/dashboards/custom_dashboard.yml' ,
y _label : 'memory usage' ,
group : 'kubernetes' ,
title : 'Kubernetes memory total' ,
start : '2020-05-06' ,
end : '2020-05-07' ,
duration _seconds : '86400' ,
direction : 'left' ,
anchor : 'top' ,
pod : 'POD' ,
'var-pod' : 'POD' ,
} ) ;
2020-07-01 16:08:20 +05:30
expect ( templatingVariablesFromUrl ( ) ) . toEqual ( expect . objectContaining ( { pod : 'POD' } ) ) ;
2020-05-24 23:13:21 +05:30
} ) ;
it ( 'returns an empty object when no custom variables are present' , ( ) => {
urlUtils . queryToObject . mockReturnValueOnce ( {
dashboard : '.gitlab/dashboards/custom_dashboard.yml' ,
} ) ;
2020-07-01 16:08:20 +05:30
expect ( templatingVariablesFromUrl ( ) ) . toStrictEqual ( { } ) ;
2020-03-13 15:44:24 +05:30
} ) ;
} ) ;
describe ( 'removeTimeRangeParams' , ( ) => {
const { removeTimeRangeParams } = monitoringUtils ;
2020-05-24 23:13:21 +05:30
it ( 'returns when query contains `start` and `end` parameters are given' , ( ) => {
2020-03-13 15:44:24 +05:30
expect ( removeTimeRangeParams ( ` ${ mockPath } ?start= ${ range . start } &end= ${ range . end } ` ) ) . toEqual (
mockPath ,
) ;
} ) ;
} ) ;
describe ( 'timeRangeToUrl' , ( ) => {
const { timeRangeToUrl } = monitoringUtils ;
2020-05-24 23:13:21 +05:30
beforeEach ( ( ) => {
jest . spyOn ( urlUtils , 'mergeUrlParams' ) ;
jest . spyOn ( urlUtils , 'removeParams' ) ;
} ) ;
afterEach ( ( ) => {
urlUtils . mergeUrlParams . mockRestore ( ) ;
urlUtils . removeParams . mockRestore ( ) ;
} ) ;
it ( 'returns a fixed range when query contains `start` and `end` parameters are given' , ( ) => {
2020-03-13 15:44:24 +05:30
const toUrl = ` ${ mockPath } ?start= ${ range . start } &end= ${ range . end } ` ;
const fromUrl = mockPath ;
2020-05-24 23:13:21 +05:30
urlUtils . removeParams . mockReturnValueOnce ( fromUrl ) ;
urlUtils . mergeUrlParams . mockReturnValueOnce ( toUrl ) ;
2020-03-13 15:44:24 +05:30
expect ( timeRangeToUrl ( range ) ) . toEqual ( toUrl ) ;
2020-05-24 23:13:21 +05:30
expect ( urlUtils . mergeUrlParams ) . toHaveBeenCalledWith ( range , fromUrl ) ;
2020-03-13 15:44:24 +05:30
} ) ;
2020-05-24 23:13:21 +05:30
it ( 'returns a rolling range when query contains `duration_seconds` parameters are given' , ( ) => {
2020-03-13 15:44:24 +05:30
const { seconds } = rollingRange . duration ;
const toUrl = ` ${ mockPath } ?duration_seconds= ${ seconds } ` ;
const fromUrl = mockPath ;
2020-05-24 23:13:21 +05:30
urlUtils . removeParams . mockReturnValueOnce ( fromUrl ) ;
urlUtils . mergeUrlParams . mockReturnValueOnce ( toUrl ) ;
2020-03-13 15:44:24 +05:30
expect ( timeRangeToUrl ( rollingRange ) ) . toEqual ( toUrl ) ;
2020-05-24 23:13:21 +05:30
expect ( urlUtils . mergeUrlParams ) . toHaveBeenCalledWith (
{ duration _seconds : ` ${ seconds } ` } ,
fromUrl ,
) ;
} ) ;
} ) ;
describe ( 'expandedPanelPayloadFromUrl' , ( ) => {
const { expandedPanelPayloadFromUrl } = monitoringUtils ;
const [ panelGroup ] = metricsDashboardViewModel . panelGroups ;
const [ panel ] = panelGroup . panels ;
const { group } = panelGroup ;
const { title , y _label : yLabel } = panel ;
it ( 'returns payload for a panel when query parameters are given' , ( ) => {
const search = ` ?group= ${ group } &title= ${ title } &y_label= ${ yLabel } ` ;
expect ( expandedPanelPayloadFromUrl ( metricsDashboardViewModel , search ) ) . toEqual ( {
group : panelGroup . group ,
panel ,
} ) ;
} ) ;
it ( 'returns null when no parameters are given' , ( ) => {
expect ( expandedPanelPayloadFromUrl ( metricsDashboardViewModel , '' ) ) . toBe ( null ) ;
} ) ;
it ( 'throws an error when no group is provided' , ( ) => {
const search = ` ?title= ${ panel . title } &y_label= ${ yLabel } ` ;
expect ( ( ) => expandedPanelPayloadFromUrl ( metricsDashboardViewModel , search ) ) . toThrow ( ) ;
} ) ;
it ( 'throws an error when no title is provided' , ( ) => {
const search = ` ?title= ${ title } &y_label= ${ yLabel } ` ;
expect ( ( ) => expandedPanelPayloadFromUrl ( metricsDashboardViewModel , search ) ) . toThrow ( ) ;
} ) ;
it ( 'throws an error when no y_label group is provided' , ( ) => {
const search = ` ?group= ${ group } &title= ${ title } ` ;
expect ( ( ) => expandedPanelPayloadFromUrl ( metricsDashboardViewModel , search ) ) . toThrow ( ) ;
} ) ;
2022-11-25 23:54:43 +05:30
it . each `
2020-05-24 23:13:21 +05:30
group | title | yLabel | missingField
$ { 'NOT_A_GROUP' } | $ { title } | $ { yLabel } | $ { 'group' }
$ { group } | $ { 'NOT_A_TITLE' } | $ { yLabel } | $ { 'title' }
$ { group } | $ { title } | $ { 'NOT_A_Y_LABEL' } | $ { 'y_label' }
2021-03-08 18:12:59 +05:30
` ('throws an error when $ missingField is incorrect', (params) => {
2020-05-24 23:13:21 +05:30
const search = ` ?group= ${ params . group } &title= ${ params . title } &y_label= ${ params . yLabel } ` ;
expect ( ( ) => expandedPanelPayloadFromUrl ( metricsDashboardViewModel , search ) ) . toThrow ( ) ;
} ) ;
} ) ;
describe ( 'panelToUrl' , ( ) => {
const { panelToUrl } = monitoringUtils ;
const dashboard = 'metrics.yml' ;
const [ panelGroup ] = metricsDashboardViewModel . panelGroups ;
const [ panel ] = panelGroup . panels ;
2021-03-08 18:12:59 +05:30
const getUrlParams = ( url ) => urlUtils . queryToObject ( url . split ( '?' ) [ 1 ] ) ;
2020-05-24 23:13:21 +05:30
it ( 'returns URL for a panel when query parameters are given' , ( ) => {
const params = getUrlParams ( panelToUrl ( dashboard , { } , panelGroup . group , panel ) ) ;
expect ( params ) . toEqual (
expect . objectContaining ( {
dashboard ,
group : panelGroup . group ,
title : panel . title ,
y _label : panel . y _label ,
} ) ,
) ;
} ) ;
it ( 'returns a dashboard only URL if group is missing' , ( ) => {
const params = getUrlParams ( panelToUrl ( dashboard , { } , null , panel ) ) ;
expect ( params ) . toEqual ( expect . objectContaining ( { dashboard : 'metrics.yml' } ) ) ;
} ) ;
it ( 'returns a dashboard only URL if panel is missing' , ( ) => {
const params = getUrlParams ( panelToUrl ( dashboard , { } , panelGroup . group , null ) ) ;
expect ( params ) . toEqual ( expect . objectContaining ( { dashboard : 'metrics.yml' } ) ) ;
} ) ;
it ( 'returns URL for a panel when query paramters are given including custom variables' , ( ) => {
const params = getUrlParams ( panelToUrl ( dashboard , { pod : 'pod' } , panelGroup . group , null ) ) ;
expect ( params ) . toEqual ( expect . objectContaining ( { dashboard : 'metrics.yml' , pod : 'pod' } ) ) ;
2020-03-13 15:44:24 +05:30
} ) ;
} ) ;
2020-04-22 19:07:51 +05:30
describe ( 'barChartsDataParser' , ( ) => {
const singleMetricExpected = {
SLA : [
[ '0.9935198135198128' , 'api' ] ,
[ '0.9975296513504401' , 'git' ] ,
[ '0.9994716394716395' , 'registry' ] ,
[ '0.9948251748251747' , 'sidekiq' ] ,
[ '0.9535664335664336' , 'web' ] ,
[ '0.9335664335664336' , 'postgresql_database' ] ,
] ,
} ;
const multipleMetricExpected = {
... singleMetricExpected ,
SLA _2 : Object . values ( singleMetricExpected ) [ 0 ] ,
} ;
const barMockDataWithMultipleMetrics = {
... barMockData ,
metrics : [
barMockData . metrics [ 0 ] ,
{
... barMockData . metrics [ 0 ] ,
label : 'SLA_2' ,
} ,
] ,
} ;
2022-11-25 23:54:43 +05:30
it . each ( [
2020-04-22 19:07:51 +05:30
{
input : { metrics : undefined } ,
output : { } ,
testCase : 'barChartsDataParser returns {} with undefined' ,
} ,
{
input : { metrics : null } ,
output : { } ,
testCase : 'barChartsDataParser returns {} with null' ,
} ,
{
input : { metrics : [ ] } ,
output : { } ,
testCase : 'barChartsDataParser returns {} with []' ,
} ,
{
input : barMockData ,
output : singleMetricExpected ,
testCase : 'barChartsDataParser returns single series object with single metrics' ,
} ,
{
input : barMockDataWithMultipleMetrics ,
output : multipleMetricExpected ,
testCase : 'barChartsDataParser returns multiple series object with multiple metrics' ,
} ,
2022-11-25 23:54:43 +05:30
] ) ( '$testCase' , ( { input , output } ) => {
expect ( monitoringUtils . barChartsDataParser ( input . metrics ) ) . toEqual (
expect . objectContaining ( output ) ,
) ;
2020-04-22 19:07:51 +05:30
} ) ;
} ) ;
2020-05-24 23:13:21 +05:30
describe ( 'removePrefixFromLabel' , ( ) => {
it . each `
input | expected
$ { undefined } | $ { '' }
$ { null } | $ { '' }
$ { '' } | $ { '' }
$ { ' ' } | $ { ' ' }
$ { 'pod-1' } | $ { 'pod-1' }
$ { 'pod-var-1' } | $ { 'pod-var-1' }
$ { 'pod-1-var' } | $ { 'pod-1-var' }
$ { 'podvar--1' } | $ { 'podvar--1' }
$ { 'povar-d-1' } | $ { 'povar-d-1' }
$ { 'var-pod-1' } | $ { 'pod-1' }
$ { 'var-var-pod-1' } | $ { 'var-pod-1' }
$ { 'varvar-pod-1' } | $ { 'varvar-pod-1' }
$ { 'var-pod-1-var-' } | $ { 'pod-1-var-' }
` ('removePrefixFromLabel returns $ expected with input $ input', ({ input, expected }) => {
expect ( monitoringUtils . removePrefixFromLabel ( input ) ) . toEqual ( expected ) ;
} ) ;
} ) ;
describe ( 'convertVariablesForURL' , ( ) => {
it . each `
2020-07-28 23:09:34 +05:30
input | expected
$ { [ ] } | $ { { } }
$ { [ { name : 'env' , value : 'prod' } ] } | $ { { 'var-env' : 'prod' } }
$ { [ { name : 'env1' , value : 'prod' } , { name : 'env2' , value : null } ] } | $ { { 'var-env1' : 'prod' } }
$ { [ { name : 'var-env' , value : 'prod' } ] } | $ { { 'var-var-env' : 'prod' } }
2020-05-24 23:13:21 +05:30
` ('convertVariablesForURL returns $ expected with input $ input', ({ input, expected }) => {
expect ( monitoringUtils . convertVariablesForURL ( input ) ) . toEqual ( expected ) ;
} ) ;
} ) ;
2020-07-28 23:09:34 +05:30
describe ( 'setCustomVariablesFromUrl' , ( ) => {
beforeEach ( ( ) => {
2023-03-04 22:38:38 +05:30
window . history . pushState = jest . fn ( ) ;
2020-07-28 23:09:34 +05:30
jest . spyOn ( urlUtils , 'updateHistory' ) ;
} ) ;
afterEach ( ( ) => {
urlUtils . updateHistory . mockRestore ( ) ;
} ) ;
it . each `
input | urlParams
$ { [ ] } | $ { '' }
$ { [ { name : 'env' , value : 'prod' } ] } | $ { '?var-env=prod' }
2021-10-27 15:23:28 +05:30
$ { [ { name : 'env1' , value : 'prod' } , { name : 'env2' , value : null } ] } | $ { '?var-env1=prod' }
2020-07-28 23:09:34 +05:30
` (
'setCustomVariablesFromUrl updates history with query "$urlParams" with input $input' ,
( { input , urlParams } ) => {
monitoringUtils . setCustomVariablesFromUrl ( input ) ;
expect ( urlUtils . updateHistory ) . toHaveBeenCalledTimes ( 1 ) ;
expect ( urlUtils . updateHistory ) . toHaveBeenCalledWith ( {
url : ` ${ TEST _HOST } / ${ urlParams } ` ,
title : '' ,
} ) ;
} ,
) ;
} ) ;
2019-12-21 20:55:43 +05:30
} ) ;