2018-03-17 18:26:18 +05:30
< script >
2022-04-04 11:22:00 +05:30
import {
GlButton ,
GlModalDirective ,
GlTooltipDirective ,
GlIcon ,
GlAlert ,
GlSprintf ,
GlLink ,
} from '@gitlab/ui' ;
2021-03-11 19:13:27 +05:30
import Mousetrap from 'mousetrap' ;
import VueDraggable from 'vuedraggable' ;
import { mapActions , mapState , mapGetters } from 'vuex' ;
2021-09-04 01:27:46 +05:30
import createFlash from '~/flash' ;
2021-03-11 19:13:27 +05:30
import invalidUrl from '~/lib/utils/invalid_url' ;
2020-10-24 23:57:45 +05:30
import { ESC _KEY } from '~/lib/utils/keys' ;
2020-06-23 00:09:42 +05:30
import { mergeUrlParams , updateHistory } from '~/lib/utils/url_utility' ;
2021-03-11 19:13:27 +05:30
import { s _ _ } from '~/locale' ;
import { defaultTimeRange } from '~/vue_shared/constants' ;
2019-12-21 20:55:43 +05:30
import TrackEventDirective from '~/vue_shared/directives/track_event' ;
2021-03-11 19:13:27 +05:30
import { metricStates , keyboardShortcutKeys } from '../constants' ;
2020-05-24 23:13:21 +05:30
import {
timeRangeFromUrl ,
panelToUrl ,
expandedPanelPayloadFromUrl ,
convertVariablesForURL ,
} from '../utils' ;
2021-03-11 19:13:27 +05:30
import DashboardHeader from './dashboard_header.vue' ;
import DashboardPanel from './dashboard_panel.vue' ;
import EmptyState from './empty_state.vue' ;
import GraphGroup from './graph_group.vue' ;
import GroupEmptyState from './group_empty_state.vue' ;
import LinksSection from './links_section.vue' ;
import VariablesSection from './variables_section.vue' ;
2019-03-02 22:35:43 +05:30
2018-05-09 12:01:36 +05:30
export default {
components : {
2019-12-21 20:55:43 +05:30
VueDraggable ,
2020-06-23 00:09:42 +05:30
DashboardHeader ,
2020-05-24 23:13:21 +05:30
DashboardPanel ,
2020-11-24 15:15:51 +05:30
GlIcon ,
2020-05-24 23:13:21 +05:30
GlButton ,
2020-03-13 15:44:24 +05:30
GraphGroup ,
EmptyState ,
GroupEmptyState ,
2020-05-24 23:13:21 +05:30
VariablesSection ,
2020-06-23 00:09:42 +05:30
LinksSection ,
2022-04-04 11:22:00 +05:30
GlAlert ,
GlSprintf ,
GlLink ,
2019-07-31 22:56:46 +05:30
} ,
directives : {
2019-10-12 21:52:04 +05:30
GlModal : GlModalDirective ,
GlTooltip : GlTooltipDirective ,
2019-12-21 20:55:43 +05:30
TrackEvent : TrackEventDirective ,
2018-05-09 12:01:36 +05:30
} ,
props : {
hasMetrics : {
type : Boolean ,
required : false ,
default : true ,
2018-03-17 18:26:18 +05:30
} ,
2020-03-13 15:44:24 +05:30
showHeader : {
type : Boolean ,
required : false ,
default : true ,
} ,
2018-05-09 12:01:36 +05:30
showPanels : {
type : Boolean ,
required : false ,
default : true ,
2018-03-17 18:26:18 +05:30
} ,
2018-05-09 12:01:36 +05:30
documentationPath : {
type : String ,
required : true ,
2018-03-17 18:26:18 +05:30
} ,
2018-05-09 12:01:36 +05:30
settingsPath : {
type : String ,
required : true ,
} ,
clustersPath : {
type : String ,
required : true ,
} ,
tagsPath : {
type : String ,
required : true ,
} ,
2020-03-13 15:44:24 +05:30
defaultBranch : {
2018-05-09 12:01:36 +05:30
type : String ,
required : false ,
2020-06-23 00:09:42 +05:30
default : '' ,
2018-05-09 12:01:36 +05:30
} ,
emptyGettingStartedSvgPath : {
type : String ,
required : true ,
} ,
emptyLoadingSvgPath : {
type : String ,
required : true ,
} ,
emptyNoDataSvgPath : {
type : String ,
required : true ,
} ,
2020-01-01 13:55:28 +05:30
emptyNoDataSmallSvgPath : {
type : String ,
required : true ,
} ,
2018-05-09 12:01:36 +05:30
emptyUnableToConnectSvgPath : {
type : String ,
required : true ,
} ,
2019-07-31 22:56:46 +05:30
customMetricsAvailable : {
type : Boolean ,
required : false ,
default : false ,
} ,
customMetricsPath : {
type : String ,
2019-09-30 21:07:59 +05:30
required : false ,
default : invalidUrl ,
2019-07-31 22:56:46 +05:30
} ,
validateQueryPath : {
type : String ,
2019-09-30 21:07:59 +05:30
required : false ,
default : invalidUrl ,
2019-07-31 22:56:46 +05:30
} ,
2019-09-30 21:07:59 +05:30
smallEmptyState : {
type : Boolean ,
required : false ,
default : false ,
} ,
2019-12-04 20:38:33 +05:30
alertsEndpoint : {
type : String ,
required : false ,
default : null ,
} ,
prometheusAlertsAvailable : {
type : Boolean ,
required : false ,
default : false ,
} ,
2019-12-21 20:55:43 +05:30
rearrangePanelsAvailable : {
type : Boolean ,
required : false ,
default : false ,
} ,
2018-05-09 12:01:36 +05:30
} ,
data ( ) {
return {
2020-03-13 15:44:24 +05:30
selectedTimeRange : timeRangeFromUrl ( ) || defaultTimeRange ,
isRearrangingPanels : false ,
2020-06-23 00:09:42 +05:30
originalDocumentTitle : document . title ,
2020-07-28 23:09:34 +05:30
hoveredPanel : '' ,
2022-04-04 11:22:00 +05:30
isDeprecationNoticeDismissed : false ,
2018-05-09 12:01:36 +05:30
} ;
} ,
2019-07-31 22:56:46 +05:30
computed : {
2019-09-04 21:01:54 +05:30
... mapState ( 'monitoringDashboard' , [
2019-12-26 22:10:19 +05:30
'dashboard' ,
2019-09-04 21:01:54 +05:30
'emptyState' ,
2020-05-24 23:13:21 +05:30
'expandedPanel' ,
2020-06-23 00:09:42 +05:30
'variables' ,
'links' ,
'currentDashboard' ,
2020-07-28 23:09:34 +05:30
'hasDashboardValidationWarnings' ,
2020-05-24 23:13:21 +05:30
] ) ,
2020-06-23 00:09:42 +05:30
... mapGetters ( 'monitoringDashboard' , [ 'selectedDashboard' , 'getMetricStates' ] ) ,
2020-07-28 23:09:34 +05:30
shouldShowEmptyState ( ) {
return Boolean ( this . emptyState ) ;
} ,
2020-05-24 23:13:21 +05:30
shouldShowVariablesSection ( ) {
2020-07-28 23:09:34 +05:30
return Boolean ( this . variables . length ) ;
2020-06-23 00:09:42 +05:30
} ,
shouldShowLinksSection ( ) {
return Object . keys ( this . links ) . length > 0 ;
2020-05-24 23:13:21 +05:30
} ,
} ,
watch : {
dashboard ( newDashboard ) {
try {
const expandedPanel = expandedPanelPayloadFromUrl ( newDashboard ) ;
if ( expandedPanel ) {
this . setExpandedPanel ( expandedPanel ) ;
}
} catch {
2021-09-04 01:27:46 +05:30
createFlash ( {
message : s _ _ (
2020-05-24 23:13:21 +05:30
'Metrics|Link contains invalid chart information, please verify the link to see the expanded panel.' ,
) ,
2021-09-04 01:27:46 +05:30
} ) ;
2020-05-24 23:13:21 +05:30
}
} ,
expandedPanel : {
handler ( { group , panel } ) {
const dashboardPath = this . currentDashboard || this . selectedDashboard ? . path ;
updateHistory ( {
2020-06-23 00:09:42 +05:30
url : panelToUrl ( dashboardPath , convertVariablesForURL ( this . variables ) , group , panel ) ,
2020-05-24 23:13:21 +05:30
title : document . title ,
} ) ;
} ,
deep : true ,
} ,
2020-06-23 00:09:42 +05:30
selectedDashboard ( dashboard ) {
this . prependToDocumentTitle ( dashboard ? . display _name ) ;
} ,
2020-07-28 23:09:34 +05:30
hasDashboardValidationWarnings ( hasWarnings ) {
/ * *
* This watcher is set for future SPA behaviour of the dashboard
* /
if ( hasWarnings ) {
2021-09-04 01:27:46 +05:30
createFlash ( {
message : s _ _ (
2020-07-28 23:09:34 +05:30
'Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema.' ,
) ,
2021-09-04 01:27:46 +05:30
type : 'warning' ,
} ) ;
2020-07-28 23:09:34 +05:30
}
} ,
2019-07-31 22:56:46 +05:30
} ,
2018-05-09 12:01:36 +05:30
created ( ) {
2020-05-24 23:13:21 +05:30
window . addEventListener ( 'keyup' , this . onKeyup ) ;
2020-07-28 23:09:34 +05:30
Mousetrap . bind ( Object . values ( keyboardShortcutKeys ) , this . runShortcut ) ;
2020-05-24 23:13:21 +05:30
} ,
destroyed ( ) {
window . removeEventListener ( 'keyup' , this . onKeyup ) ;
2020-07-28 23:09:34 +05:30
Mousetrap . unbind ( Object . values ( keyboardShortcutKeys ) ) ;
2018-05-09 12:01:36 +05:30
} ,
mounted ( ) {
if ( ! this . hasMetrics ) {
2019-09-04 21:01:54 +05:30
this . setGettingStartedEmptyState ( ) ;
2018-05-09 12:01:36 +05:30
} else {
2020-03-13 15:44:24 +05:30
this . setTimeRange ( this . selectedTimeRange ) ;
this . fetchData ( ) ;
2018-05-09 12:01:36 +05:30
}
} ,
methods : {
2019-09-04 21:01:54 +05:30
... mapActions ( 'monitoringDashboard' , [
2020-03-13 15:44:24 +05:30
'setTimeRange' ,
2019-09-04 21:01:54 +05:30
'fetchData' ,
'setGettingStartedEmptyState' ,
2019-12-26 22:10:19 +05:30
'setPanelGroupMetrics' ,
2020-05-24 23:13:21 +05:30
'setExpandedPanel' ,
'clearExpandedPanel' ,
2019-09-04 21:01:54 +05:30
] ) ,
2020-01-01 13:55:28 +05:30
updatePanels ( key , panels ) {
2019-12-26 22:10:19 +05:30
this . setPanelGroupMetrics ( {
2020-01-01 13:55:28 +05:30
panels ,
2019-12-26 22:10:19 +05:30
key ,
} ) ;
} ,
2020-01-01 13:55:28 +05:30
removePanel ( key , panels , graphIndex ) {
2019-12-26 22:10:19 +05:30
this . setPanelGroupMetrics ( {
2020-01-01 13:55:28 +05:30
panels : panels . filter ( ( v , i ) => i !== graphIndex ) ,
2019-12-26 22:10:19 +05:30
key ,
} ) ;
} ,
2020-05-24 23:13:21 +05:30
generatePanelUrl ( groupKey , panel ) {
const dashboardPath = this . currentDashboard || this . selectedDashboard ? . path ;
2020-06-23 00:09:42 +05:30
return panelToUrl ( dashboardPath , convertVariablesForURL ( this . variables ) , groupKey , panel ) ;
2019-07-31 22:56:46 +05:30
} ,
2020-01-01 13:55:28 +05:30
/ * *
* Return a single empty state for a group .
*
* If all states are the same a single state is returned to be displayed
* Except if the state is OK , in which case the group is displayed .
*
* @ param { String } groupKey - Identifier for group
* @ returns { String } state code from ` metricStates `
* /
groupSingleEmptyState ( groupKey ) {
const states = this . getMetricStates ( groupKey ) ;
if ( states . length === 1 && states [ 0 ] !== metricStates . OK ) {
return states [ 0 ] ;
}
return null ;
} ,
2020-07-28 23:09:34 +05:30
/ * *
* Return true if the entire group is loading .
* @ param { String } groupKey - Identifier for group
* @ returns { boolean }
* /
isGroupLoading ( groupKey ) {
return this . groupSingleEmptyState ( groupKey ) === metricStates . LOADING ;
} ,
2020-01-01 13:55:28 +05:30
/ * *
* A group should be not collapsed if any metric is loaded ( OK )
*
* @ param { String } groupKey - Identifier for group
* @ returns { Boolean } If the group should be collapsed
* /
collapseGroup ( groupKey ) {
// Collapse group if no data is available
return ! this . getMetricStates ( groupKey ) . includes ( metricStates . OK ) ;
} ,
2020-06-23 00:09:42 +05:30
prependToDocumentTitle ( text ) {
if ( text ) {
document . title = ` ${ text } · ${ this . originalDocumentTitle } ` ;
}
2020-04-22 19:07:51 +05:30
} ,
onTimeRangeZoom ( { start , end } ) {
updateHistory ( {
url : mergeUrlParams ( { start , end } , window . location . href ) ,
title : document . title ,
} ) ;
this . selectedTimeRange = { start , end } ;
2020-06-23 00:09:42 +05:30
// keep the current dashboard time range
// in sync with the Vuex store
this . setTimeRange ( this . selectedTimeRange ) ;
2020-04-08 14:13:33 +05:30
} ,
2020-05-24 23:13:21 +05:30
onExpandPanel ( group , panel ) {
this . setExpandedPanel ( { group , panel } ) ;
} ,
onGoBack ( ) {
this . clearExpandedPanel ( ) ;
} ,
onKeyup ( event ) {
const { key } = event ;
2020-10-24 23:57:45 +05:30
if ( key === ESC _KEY ) {
2020-05-24 23:13:21 +05:30
this . clearExpandedPanel ( ) ;
}
} ,
2020-06-23 00:09:42 +05:30
onSetRearrangingPanels ( isRearrangingPanels ) {
this . isRearrangingPanels = isRearrangingPanels ;
} ,
onDateTimePickerInvalid ( ) {
2021-09-04 01:27:46 +05:30
createFlash ( {
message : s _ _ (
2020-06-23 00:09:42 +05:30
'Metrics|Link contains an invalid time window, please verify the link to see the requested time range.' ,
) ,
2021-09-04 01:27:46 +05:30
} ) ;
2020-06-23 00:09:42 +05:30
// As a fallback, switch to default time range instead
this . selectedTimeRange = defaultTimeRange ;
} ,
2020-07-28 23:09:34 +05:30
isPanelHalfWidth ( panelIndex , totalPanels ) {
/ * *
* A single panel on a row should take the full width of its parent .
* All others should have half the width their parent .
* /
const isNumberOfPanelsEven = totalPanels % 2 === 0 ;
const isLastPanel = panelIndex === totalPanels - 1 ;
return isNumberOfPanelsEven || ! isLastPanel ;
} ,
/ * *
* TODO : Investigate this to utilize the eventBus from Vue
* The intentation behind this cleanup is to allow for better tests
* as well as use the correct eventBus facilities that are compatible
* with Vue 3
* https : //gitlab.com/gitlab-org/gitlab/-/issues/225583
* /
//
runShortcut ( e ) {
const panel = this . $refs [ this . hoveredPanel ] ;
if ( ! panel ) return ;
const [ panelInstance ] = panel ;
let actionToRun = '' ;
switch ( e . key ) {
case keyboardShortcutKeys . EXPAND :
actionToRun = 'onExpandFromKeyboardShortcut' ;
break ;
case keyboardShortcutKeys . VISIT _LOGS :
actionToRun = 'visitLogsPageFromKeyboardShortcut' ;
break ;
case keyboardShortcutKeys . SHOW _ALERT :
actionToRun = 'showAlertModalFromKeyboardShortcut' ;
break ;
case keyboardShortcutKeys . DOWNLOAD _CSV :
actionToRun = 'downloadCsvFromKeyboardShortcut' ;
break ;
case keyboardShortcutKeys . CHART _COPY :
actionToRun = 'copyChartLinkFromKeyboardShotcut' ;
break ;
default :
actionToRun = 'onExpandFromKeyboardShortcut' ;
break ;
}
panelInstance [ actionToRun ] ( ) ;
} ,
setHoveredPanel ( groupKey , graphIndex ) {
this . hoveredPanel = ` dashboard-panel- ${ groupKey } - ${ graphIndex } ` ;
} ,
clearHoveredPanel ( ) {
this . hoveredPanel = '' ;
} ,
2018-05-09 12:01:36 +05:30
} ,
2020-05-24 23:13:21 +05:30
i18n : {
2020-10-24 23:57:45 +05:30
collapsePanelLabel : s _ _ ( 'Metrics|Collapse panel' ) ,
collapsePanelTooltip : s _ _ ( 'Metrics|Collapse panel (Esc)' ) ,
2020-05-24 23:13:21 +05:30
} ,
2018-05-09 12:01:36 +05:30
} ;
2018-03-17 18:26:18 +05:30
< / script >
< template >
2020-03-13 15:44:24 +05:30
< div class = "prometheus-graphs" data -qa -selector = " prometheus_graphs " >
2022-04-04 11:22:00 +05:30
< div >
< gl-alert
v - if = "!isDeprecationNoticeDismissed"
: title = "__('Feature deprecation and removal')"
class = "mb-3"
variant = "danger"
@ dismiss = "isDeprecationNoticeDismissed = true"
>
< gl-sprintf
: message = "
s _ _ (
'Deprecations|The metrics, logs and tracing features were deprecated in GitLab 14.7 and are %{epicStart} scheduled for removal %{epicEnd} in GitLab 15.0.' ,
)
"
>
< template # epic = "{ content }" >
< gl-link href = "https://gitlab.com/groups/gitlab-org/-/epics/7188" target = "_blank" > { {
content
} } < / gl-link >
< / template >
< / gl-sprintf >
< gl-sprintf
: message = "
s _ _ (
'Deprecations|For information on a possible replacement %{epicStart} learn more about Opstrace %{epicEnd}.' ,
)
"
>
< template # epic = "{ content }" >
< gl-link href = "https://gitlab.com/groups/gitlab-org/-/epics/6976" target = "_blank" > { {
content
} } < / gl-link >
< / template >
< / gl-sprintf >
< / gl-alert >
< / div >
2020-06-23 00:09:42 +05:30
< dashboard-header
2020-03-13 15:44:24 +05:30
v - if = "showHeader"
ref = "prometheusGraphsHeader"
2020-04-22 19:07:51 +05:30
class = "prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
2020-06-23 00:09:42 +05:30
: default - branch = "defaultBranch"
: rearrange - panels - available = "rearrangePanelsAvailable"
: custom - metrics - available = "customMetricsAvailable"
: custom - metrics - path = "customMetricsPath"
: validate - query - path = "validateQueryPath"
: is - rearranging - panels = "isRearrangingPanels"
: selected - time - range = "selectedTimeRange"
@ dateTimePickerInvalid = "onDateTimePickerInvalid"
@ setRearrangingPanels = "onSetRearrangingPanels"
/ >
2020-07-28 23:09:34 +05:30
< template v-if = "!shouldShowEmptyState" >
< variables-section v-if = "shouldShowVariablesSection" / >
< links-section v-if = "shouldShowLinksSection" / >
2020-05-24 23:13:21 +05:30
< dashboard-panel
v - show = "expandedPanel.panel"
ref = "expandedPanel"
: settings - path = "settingsPath"
: clipboard - text = "generatePanelUrl(expandedPanel.group, expandedPanel.panel)"
: graph - data = "expandedPanel.panel"
: alerts - endpoint = "alertsEndpoint"
: height = "600"
: prometheus - alerts - available = "prometheusAlertsAvailable"
@ timerangezoom = "onTimeRangeZoom"
2019-07-07 11:18:12 +05:30
>
2021-01-29 00:20:46 +05:30
< template # top -left >
2020-05-24 23:13:21 +05:30
< gl-button
ref = "goBackBtn"
v - gl - tooltip
class = "mr-3 my-3"
2020-10-24 23:57:45 +05:30
: title = "$options.i18n.collapsePanelTooltip"
2020-05-24 23:13:21 +05:30
@ click = "onGoBack"
>
2020-10-24 23:57:45 +05:30
{ { $options . i18n . collapsePanelLabel } }
2020-05-24 23:13:21 +05:30
< / gl-button >
< / template >
< / dashboard-panel >
< div v-show = "!expandedPanel.panel" >
< graph-group
v - for = "groupData in dashboard.panelGroups"
: key = "`${groupData.group}.${groupData.priority}`"
: name = "groupData.group"
: show - panels = "showPanels"
2020-07-28 23:09:34 +05:30
: is - loading = "isGroupLoading(groupData.key)"
2020-05-24 23:13:21 +05:30
: collapse - group = "collapseGroup(groupData.key)"
2020-03-13 15:44:24 +05:30
>
2020-05-24 23:13:21 +05:30
< vue-draggable
v - if = "!groupSingleEmptyState(groupData.key)"
: value = "groupData.panels"
group = "metrics-dashboard"
: component - data = "{ attrs: { class: 'row mx-0 w-100' } }"
: disabled = "!isRearrangingPanels"
@ input = "updatePanels(groupData.key, $event)"
2019-12-21 20:55:43 +05:30
>
2020-05-24 23:13:21 +05:30
< div
v - for = "(graphData, graphIndex) in groupData.panels"
: key = "`dashboard-panel-${graphIndex}`"
2020-07-28 23:09:34 +05:30
data - testid = "dashboard-panel-layout-wrapper"
class = "col-12 px-2 mb-2 draggable"
: class = " {
'draggable-enabled' : isRearrangingPanels ,
'col-lg-6' : isPanelHalfWidth ( graphIndex , groupData . panels . length ) ,
} "
@ mouseover = "setHoveredPanel(groupData.key, graphIndex)"
@ mouseout = "clearHoveredPanel"
2020-05-24 23:13:21 +05:30
>
< div class = "position-relative draggable-panel js-draggable-panel" >
< div
v - if = "isRearrangingPanels"
class = "draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end"
@ click = "removePanel(groupData.key, groupData.panels, graphIndex)"
>
< a class = "mx-2 p-2 draggable-remove-link" :aria-label = "__('Remove')" >
2020-11-24 15:15:51 +05:30
< gl-icon name = "close" / >
2020-05-24 23:13:21 +05:30
< / a >
< / div >
2020-03-13 15:44:24 +05:30
2020-05-24 23:13:21 +05:30
< dashboard-panel
2020-07-28 23:09:34 +05:30
: ref = "`dashboard-panel-${groupData.key}-${graphIndex}`"
2020-05-24 23:13:21 +05:30
: settings - path = "settingsPath"
: clipboard - text = "generatePanelUrl(groupData.group, graphData)"
: graph - data = "graphData"
: alerts - endpoint = "alertsEndpoint"
: prometheus - alerts - available = "prometheusAlertsAvailable"
@ timerangezoom = "onTimeRangeZoom"
@ expand = "onExpandPanel(groupData.group, graphData)"
/ >
< / div >
2019-10-12 21:52:04 +05:30
< / div >
2020-05-24 23:13:21 +05:30
< / vue-draggable >
< div v -else class = "py-5 col col-sm-10 col-md-8 col-lg-7 col-xl-6" >
< group-empty-state
ref = "empty-group"
: documentation - path = "documentationPath"
: settings - path = "settingsPath"
: selected - state = "groupSingleEmptyState(groupData.key)"
: svg - path = "emptyNoDataSmallSvgPath"
/ >
2020-03-13 15:44:24 +05:30
< / div >
2020-05-24 23:13:21 +05:30
< / graph-group >
< / div >
2020-07-28 23:09:34 +05:30
< / template >
2019-09-30 21:07:59 +05:30
< empty-state
v - else
: selected - state = "emptyState"
: documentation - path = "documentationPath"
: settings - path = "settingsPath"
: clusters - path = "clustersPath"
: empty - getting - started - svg - path = "emptyGettingStartedSvgPath"
: empty - loading - svg - path = "emptyLoadingSvgPath"
: empty - no - data - svg - path = "emptyNoDataSvgPath"
2020-01-01 13:55:28 +05:30
: empty - no - data - small - svg - path = "emptyNoDataSmallSvgPath"
2019-09-30 21:07:59 +05:30
: empty - unable - to - connect - svg - path = "emptyUnableToConnectSvgPath"
: compact = "smallEmptyState"
/ >
2018-03-17 18:26:18 +05:30
< / div >
< / template >