Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
97b325a4
G
gitlab-foss
项目概览
李少辉-开发者
/
gitlab-foss
通知
15
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
gitlab-foss
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
97b325a4
编写于
7月 22, 2019
作者:
T
Tristan Read
提交者:
Fatih Acet
7月 22, 2019
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add ability to embed metrics
See
https://gitlab.com/gitlab-org/gitlab-ce/issues/30423
上级
86e00214
变更
15
隐藏空白更改
内联
并排
Showing
15 changed file
with
410 addition
and
50 deletion
+410
-50
app/assets/javascripts/behaviors/markdown/render_gfm.js
app/assets/javascripts/behaviors/markdown/render_gfm.js
+4
-0
app/assets/javascripts/behaviors/markdown/render_metrics.js
app/assets/javascripts/behaviors/markdown/render_metrics.js
+24
-0
app/assets/javascripts/monitoring/components/charts/area.vue
app/assets/javascripts/monitoring/components/charts/area.vue
+52
-44
app/assets/javascripts/monitoring/components/dashboard.vue
app/assets/javascripts/monitoring/components/dashboard.vue
+3
-4
app/assets/javascripts/monitoring/components/embed.vue
app/assets/javascripts/monitoring/components/embed.vue
+97
-0
app/assets/javascripts/monitoring/constants.js
app/assets/javascripts/monitoring/constants.js
+2
-0
app/assets/javascripts/monitoring/stores/actions.js
app/assets/javascripts/monitoring/stores/actions.js
+10
-2
app/assets/javascripts/monitoring/stores/mutation_types.js
app/assets/javascripts/monitoring/stores/mutation_types.js
+1
-0
app/assets/javascripts/monitoring/stores/mutations.js
app/assets/javascripts/monitoring/stores/mutations.js
+3
-0
app/assets/javascripts/monitoring/stores/state.js
app/assets/javascripts/monitoring/stores/state.js
+1
-0
app/assets/stylesheets/pages/prometheus.scss
app/assets/stylesheets/pages/prometheus.scss
+5
-0
spec/frontend/behaviors/markdown/render_metrics_spec.js
spec/frontend/behaviors/markdown/render_metrics_spec.js
+37
-0
spec/frontend/monitoring/embed/embed_spec.js
spec/frontend/monitoring/embed/embed_spec.js
+78
-0
spec/frontend/monitoring/embed/mock_data.js
spec/frontend/monitoring/embed/mock_data.js
+87
-0
spec/frontend/test_setup.js
spec/frontend/test_setup.js
+6
-0
未找到文件。
app/assets/javascripts/behaviors/markdown/render_gfm.js
浏览文件 @
97b325a4
...
...
@@ -2,6 +2,7 @@ import $ from 'jquery';
import
syntaxHighlight
from
'
~/syntax_highlight
'
;
import
renderMath
from
'
./render_math
'
;
import
renderMermaid
from
'
./render_mermaid
'
;
import
renderMetrics
from
'
./render_metrics
'
;
import
highlightCurrentUser
from
'
./highlight_current_user
'
;
import
initUserPopovers
from
'
../../user_popovers
'
;
import
initMRPopovers
from
'
../../mr_popover
'
;
...
...
@@ -17,6 +18,9 @@ $.fn.renderGFM = function renderGFM() {
highlightCurrentUser
(
this
.
find
(
'
.gfm-project_member
'
).
get
());
initUserPopovers
(
this
.
find
(
'
.gfm-project_member
'
).
get
());
initMRPopovers
(
this
.
find
(
'
.gfm-merge_request
'
).
get
());
if
(
gon
.
features
&&
gon
.
features
.
gfmEmbeddedMetrics
)
{
renderMetrics
(
this
.
find
(
'
.js-render-metrics
'
).
get
());
}
return
this
;
};
...
...
app/assets/javascripts/behaviors/markdown/render_metrics.js
0 → 100644
浏览文件 @
97b325a4
import
Vue
from
'
vue
'
;
import
Metrics
from
'
~/monitoring/components/embed.vue
'
;
import
{
createStore
}
from
'
~/monitoring/stores
'
;
// TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-ce/issues/64369.
export
default
function
renderMetrics
(
elements
)
{
if
(
!
elements
.
length
)
{
return
;
}
elements
.
forEach
(
element
=>
{
const
{
dashboardUrl
}
=
element
.
dataset
;
const
MetricsComponent
=
Vue
.
extend
(
Metrics
);
// eslint-disable-next-line no-new
new
MetricsComponent
({
el
:
element
,
store
:
createStore
(),
propsData
:
{
dashboardUrl
,
},
});
});
}
app/assets/javascripts/monitoring/components/charts/area.vue
浏览文件 @
97b325a4
...
...
@@ -37,7 +37,13 @@ export default {
},
projectPath
:
{
type
:
String
,
required
:
true
,
required
:
false
,
default
:
()
=>
''
,
},
showBorder
:
{
type
:
Boolean
,
required
:
false
,
default
:
()
=>
false
,
},
thresholds
:
{
type
:
Array
,
...
...
@@ -234,52 +240,54 @@ export default {
</
script
>
<
template
>
<div
class=
"prometheus-graph col-12 col-lg-6"
>
<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-area-chart
ref=
"areaChart"
v-bind=
"$attrs"
:data=
"chartData"
:option=
"chartOptions"
:format-tooltip-text=
"formatTooltipText"
:thresholds=
"thresholds"
:width=
"width"
:height=
"height"
@
updated=
"onChartUpdated"
>
<template
v-if=
"tooltip.isDeployment"
>
<template
slot=
"tooltipTitle"
>
{{
__
(
'
Deployed
'
)
}}
</
template
>
<div
slot=
"tooltipContent"
class=
"d-flex align-items-center"
>
<icon
name=
"commit"
class=
"mr-2"
/>
<gl-link
:href=
"tooltip.commitUrl"
>
{{ tooltip.sha }}
</gl-link>
</div>
</template>
<
template
v-else
>
<template
slot=
"tooltipTitle"
>
<div
class=
"text-nowrap"
>
{{
tooltip
.
title
}}
<div
class=
"col-12 col-lg-6"
:class=
"[showBorder ? 'p-2' : 'p-0']"
>
<div
class=
"prometheus-graph"
:class=
"
{ 'prometheus-graph-embed w-100 p-3': showBorder }">
<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-area-chart
ref=
"areaChart"
v-bind=
"$attrs"
:data=
"chartData"
:option=
"chartOptions"
:format-tooltip-text=
"formatTooltipText"
:thresholds=
"thresholds"
:width=
"width"
:height=
"height"
@
updated=
"onChartUpdated"
>
<template
v-if=
"tooltip.isDeployment"
>
<template
slot=
"tooltipTitle"
>
{{
__
(
'
Deployed
'
)
}}
</
template
>
<div
slot=
"tooltipContent"
class=
"d-flex align-items-center"
>
<icon
name=
"commit"
class=
"mr-2"
/>
<gl-link
:href=
"tooltip.commitUrl"
>
{{ tooltip.sha }}
</gl-link>
</div>
</template>
<
template
slot=
"tooltipContent"
>
<div
v-for=
"(content, key) in tooltip.content"
:key=
"key"
class=
"d-flex justify-content-between"
>
<gl-chart-series-label
:color=
"isMultiSeries ? content.color : ''"
>
{{
content
.
name
}}
</gl-chart-series-label>
<div
class=
"prepend-left-32"
>
{{
content
.
value
}}
<
template
v-else
>
<template
slot=
"tooltipTitle"
>
<div
class=
"text-nowrap"
>
{{
tooltip
.
title
}}
</div>
</div>
</
template
>
<
template
slot=
"tooltipContent"
>
<div
v-for=
"(content, key) in tooltip.content"
:key=
"key"
class=
"d-flex justify-content-between"
>
<gl-chart-series-label
:color=
"isMultiSeries ? content.color : ''"
>
{{
content
.
name
}}
</gl-chart-series-label>
<div
class=
"prepend-left-32"
>
{{
content
.
value
}}
</div>
</div>
</
template
>
</template>
</
template
>
</
gl-area-chart
>
</
gl-area-chart
>
</
div
>
</div>
</template>
app/assets/javascripts/monitoring/components/dashboard.vue
浏览文件 @
97b325a4
...
...
@@ -11,10 +11,9 @@ import MonitorSingleStatChart from './charts/single_stat.vue';
import
PanelType
from
'
./panel_type.vue
'
;
import
GraphGroup
from
'
./graph_group.vue
'
;
import
EmptyState
from
'
./empty_state.vue
'
;
import
{
timeWindows
,
timeWindowsKeyNames
}
from
'
../constants
'
;
import
{
sidebarAnimationDuration
,
timeWindows
,
timeWindowsKeyNames
}
from
'
../constants
'
;
import
{
getTimeDiff
}
from
'
../utils
'
;
const
sidebarAnimationDuration
=
150
;
let
sidebarMutationObserver
;
export
default
{
...
...
@@ -370,8 +369,8 @@ export default {
</div>
<div
v-if=
"!showEmptyState"
>
<graph-group
v-for=
"
(groupData, index)
in groupsWithData"
:key=
"
index
"
v-for=
"
groupData
in groupsWithData"
:key=
"
`$
{groupData.group}.${groupData.priority}`
"
:name="groupData.group"
:show-panels="showPanels"
>
...
...
app/assets/javascripts/monitoring/components/embed.vue
0 → 100644
浏览文件 @
97b325a4
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
GraphGroup
from
'
./graph_group.vue
'
;
import
MonitorAreaChart
from
'
./charts/area.vue
'
;
import
{
sidebarAnimationDuration
,
timeWindowsKeyNames
,
timeWindows
}
from
'
../constants
'
;
import
{
getTimeDiff
}
from
'
../utils
'
;
let
sidebarMutationObserver
;
export
default
{
components
:
{
GraphGroup
,
MonitorAreaChart
,
},
props
:
{
dashboardUrl
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
params
:
{
...
getTimeDiff
(
timeWindows
[
timeWindowsKeyNames
.
eightHours
]),
},
elWidth
:
0
,
};
},
computed
:
{
...
mapState
(
'
monitoringDashboard
'
,
[
'
groups
'
,
'
metricsWithData
'
]),
groupData
()
{
const
groupsWithData
=
this
.
groups
.
filter
(
group
=>
this
.
chartsWithData
(
group
.
metrics
).
length
);
if
(
groupsWithData
.
length
)
{
return
groupsWithData
[
0
];
}
return
null
;
},
},
mounted
()
{
this
.
setInitialState
();
this
.
fetchMetricsData
(
this
.
params
);
sidebarMutationObserver
=
new
MutationObserver
(
this
.
onSidebarMutation
);
sidebarMutationObserver
.
observe
(
document
.
querySelector
(
'
.layout-page
'
),
{
attributes
:
true
,
childList
:
false
,
subtree
:
false
,
});
},
beforeDestroy
()
{
if
(
sidebarMutationObserver
)
{
sidebarMutationObserver
.
disconnect
();
}
},
methods
:
{
...
mapActions
(
'
monitoringDashboard
'
,
[
'
fetchMetricsData
'
,
'
setEndpoints
'
,
'
setFeatureFlags
'
,
'
setShowErrorBanner
'
,
]),
chartsWithData
(
charts
)
{
return
charts
.
filter
(
chart
=>
chart
.
metrics
.
some
(
metric
=>
this
.
metricsWithData
.
includes
(
metric
.
metric_id
)),
);
},
onSidebarMutation
()
{
setTimeout
(()
=>
{
this
.
elWidth
=
this
.
$el
.
clientWidth
;
},
sidebarAnimationDuration
);
},
setInitialState
()
{
this
.
setFeatureFlags
({
prometheusEndpointEnabled
:
true
,
});
this
.
setEndpoints
({
dashboardEndpoint
:
this
.
dashboardUrl
,
});
this
.
setShowErrorBanner
(
false
);
},
},
};
</
script
>
<
template
>
<div
class=
"metrics-embed"
>
<div
v-if=
"groupData"
class=
"row w-100 m-n2 pb-4"
>
<monitor-area-chart
v-for=
"graphData in chartsWithData(groupData.metrics)"
:key=
"graphData.title"
:graph-data=
"graphData"
:container-width=
"elWidth"
group-id=
"monitor-area-chart"
:project-path=
"null"
:show-border=
"true"
/>
</div>
</div>
</
template
>
app/assets/javascripts/monitoring/constants.js
浏览文件 @
97b325a4
import
{
__
}
from
'
~/locale
'
;
export
const
sidebarAnimationDuration
=
300
;
// milliseconds.
export
const
chartHeight
=
300
;
export
const
graphTypes
=
{
...
...
app/assets/javascripts/monitoring/stores/actions.js
浏览文件 @
97b325a4
...
...
@@ -44,6 +44,10 @@ export const setFeatureFlags = (
commit
(
types
.
SET_ADDITIONAL_PANEL_TYPES_ENABLED
,
additionalPanelTypesEnabled
);
};
export
const
setShowErrorBanner
=
({
commit
},
enabled
)
=>
{
commit
(
types
.
SET_SHOW_ERROR_BANNER
,
enabled
);
};
export
const
requestMetricsDashboard
=
({
commit
})
=>
{
commit
(
types
.
REQUEST_METRICS_DATA
);
};
...
...
@@ -99,7 +103,9 @@ export const fetchMetricsData = ({ state, dispatch }, params) => {
})
.
catch
(
error
=>
{
dispatch
(
'
receiveMetricsDataFailure
'
,
error
);
createFlash
(
s__
(
'
Metrics|There was an error while retrieving metrics
'
));
if
(
state
.
setShowErrorBanner
)
{
createFlash
(
s__
(
'
Metrics|There was an error while retrieving metrics
'
));
}
});
};
...
...
@@ -119,7 +125,9 @@ export const fetchDashboard = ({ state, dispatch }, params) => {
})
.
catch
(
error
=>
{
dispatch
(
'
receiveMetricsDashboardFailure
'
,
error
);
createFlash
(
s__
(
'
Metrics|There was an error while retrieving metrics
'
));
if
(
state
.
setShowErrorBanner
)
{
createFlash
(
s__
(
'
Metrics|There was an error while retrieving metrics
'
));
}
});
};
...
...
app/assets/javascripts/monitoring/stores/mutation_types.js
浏览文件 @
97b325a4
...
...
@@ -16,3 +16,4 @@ 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
'
;
export
const
SET_NO_DATA_EMPTY_STATE
=
'
SET_NO_DATA_EMPTY_STATE
'
;
export
const
SET_SHOW_ERROR_BANNER
=
'
SET_SHOW_ERROR_BANNER
'
;
app/assets/javascripts/monitoring/stores/mutations.js
浏览文件 @
97b325a4
...
...
@@ -96,4 +96,7 @@ export default {
[
types
.
SET_ADDITIONAL_PANEL_TYPES_ENABLED
](
state
,
enabled
)
{
state
.
additionalPanelTypesEnabled
=
enabled
;
},
[
types
.
SET_SHOW_ERROR_BANNER
](
state
,
enabled
)
{
state
.
showErrorBanner
=
enabled
;
},
};
app/assets/javascripts/monitoring/stores/state.js
浏览文件 @
97b325a4
...
...
@@ -12,6 +12,7 @@ export default () => ({
additionalPanelTypesEnabled
:
false
,
emptyState
:
'
gettingStarted
'
,
showEmptyState
:
true
,
showErrorBanner
:
true
,
groups
:
[],
deploymentData
:
[],
environments
:
[],
...
...
app/assets/stylesheets/pages/prometheus.scss
浏览文件 @
97b325a4
...
...
@@ -29,6 +29,11 @@
padding
:
$gl-padding
/
2
;
}
.prometheus-graph-embed
{
border
:
1px
solid
$border-color
;
border-radius
:
$border-radius-default
;
}
.prometheus-graph-header
{
display
:
flex
;
align-items
:
center
;
...
...
spec/frontend/behaviors/markdown/render_metrics_spec.js
0 → 100644
浏览文件 @
97b325a4
import
Vue
from
'
vue
'
;
import
renderMetrics
from
'
~/behaviors/markdown/render_metrics
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
const
originalExtend
=
Vue
.
extend
;
describe
(
'
Render metrics for Gitlab Flavoured Markdown
'
,
()
=>
{
const
container
=
{
Metrics
()
{},
};
let
spyExtend
;
beforeEach
(()
=>
{
Vue
.
extend
=
()
=>
container
.
Metrics
;
spyExtend
=
jest
.
spyOn
(
Vue
,
'
extend
'
);
});
afterEach
(()
=>
{
Vue
.
extend
=
originalExtend
;
});
it
(
'
does nothing when no elements are found
'
,
()
=>
{
renderMetrics
([]);
expect
(
spyExtend
).
not
.
toHaveBeenCalled
();
});
it
(
'
renders a vue component when elements are found
'
,
()
=>
{
const
element
=
document
.
createElement
(
'
div
'
);
element
.
setAttribute
(
'
data-dashboard-url
'
,
TEST_HOST
);
renderMetrics
([
element
]);
expect
(
spyExtend
).
toHaveBeenCalled
();
});
});
spec/frontend/monitoring/embed/embed_spec.js
0 → 100644
浏览文件 @
97b325a4
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
Embed
from
'
~/monitoring/components/embed.vue
'
;
import
MonitorAreaChart
from
'
~/monitoring/components/charts/area.vue
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
{
groups
,
initialState
,
metricsData
,
metricsWithData
}
from
'
./mock_data
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
Embed
'
,
()
=>
{
let
wrapper
;
let
store
;
let
actions
;
function
mountComponent
()
{
wrapper
=
shallowMount
(
Embed
,
{
localVue
,
store
,
propsData
:
{
dashboardUrl
:
TEST_HOST
,
},
});
}
beforeEach
(()
=>
{
actions
=
{
setFeatureFlags
:
()
=>
{},
setShowErrorBanner
:
()
=>
{},
setEndpoints
:
()
=>
{},
fetchMetricsData
:
()
=>
{},
};
store
=
new
Vuex
.
Store
({
modules
:
{
monitoringDashboard
:
{
namespaced
:
true
,
actions
,
state
:
initialState
,
},
},
});
});
afterEach
(()
=>
{
if
(
wrapper
)
{
wrapper
.
destroy
();
}
});
describe
(
'
no metrics are available yet
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
();
});
it
(
'
shows an empty state when no metrics are present
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.metrics-embed
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
MonitorAreaChart
).
exists
()).
toBe
(
false
);
});
});
describe
(
'
metrics are available
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
monitoringDashboard
.
groups
=
groups
;
store
.
state
.
monitoringDashboard
.
groups
[
0
].
metrics
=
metricsData
;
store
.
state
.
monitoringDashboard
.
metricsWithData
=
metricsWithData
;
mountComponent
();
});
it
(
'
shows a chart when metrics are present
'
,
()
=>
{
wrapper
.
setProps
({});
expect
(
wrapper
.
find
(
'
.metrics-embed
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
MonitorAreaChart
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
findAll
(
MonitorAreaChart
).
length
).
toBe
(
2
);
});
});
});
spec/frontend/monitoring/embed/mock_data.js
0 → 100644
浏览文件 @
97b325a4
export
const
metricsWithData
=
[
15
,
16
];
export
const
groups
=
[
{
panels
:
[
{
title
:
'
Memory Usage (Total)
'
,
type
:
'
area-chart
'
,
y_label
:
'
Total Memory Used
'
,
weight
:
4
,
metrics
:
[
{
id
:
'
system_metrics_kubernetes_container_memory_total
'
,
metric_id
:
15
,
},
],
},
{
title
:
'
Core Usage (Total)
'
,
type
:
'
area-chart
'
,
y_label
:
'
Total Cores
'
,
weight
:
3
,
metrics
:
[
{
id
:
'
system_metrics_kubernetes_container_cores_total
'
,
metric_id
:
16
,
},
],
},
],
},
];
export
const
metrics
=
[
{
id
:
'
system_metrics_kubernetes_container_memory_total
'
,
metric_id
:
15
,
},
{
id
:
'
system_metrics_kubernetes_container_cores_total
'
,
metric_id
:
16
,
},
];
const
queries
=
[
{
result
:
[
{
values
:
[
[
'
Mon
'
,
1220
],
[
'
Tue
'
,
932
],
[
'
Wed
'
,
901
],
[
'
Thu
'
,
934
],
[
'
Fri
'
,
1290
],
[
'
Sat
'
,
1330
],
[
'
Sun
'
,
1320
],
],
},
],
},
];
export
const
metricsData
=
[
{
queries
,
metrics
:
[
{
metric_id
:
15
,
},
],
},
{
queries
,
metrics
:
[
{
metric_id
:
16
,
},
],
},
];
export
const
initialState
=
{
monitoringDashboard
:
{},
groups
:
[],
metricsWithData
:
[],
useDashboardEndpoint
:
true
,
};
spec/frontend/test_setup.js
浏览文件 @
97b325a4
...
...
@@ -69,3 +69,9 @@ Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => {
// Tech debt issue TBD
testUtilsConfig
.
logModifiedComponents
=
false
;
// Basic stub for MutationObserver
global
.
MutationObserver
=
()
=>
({
disconnect
:
()
=>
{},
observe
:
()
=>
{},
});
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录