Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
238c8c3e
Commit
238c8c3e
authored
Jul 26, 2018
by
Clement Ho
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into ce-to-ee-2018-07-26
parents
0030c4c3
8cb9f02e
Changes
107
Show whitespace changes
Inline
Side-by-side
Showing
107 changed files
with
3515 additions
and
558 deletions
+3515
-558
app/assets/javascripts/boards/index.js
app/assets/javascripts/boards/index.js
+84
-79
app/assets/javascripts/monitoring/components/dashboard.vue
app/assets/javascripts/monitoring/components/dashboard.vue
+15
-2
app/assets/javascripts/vue_shared/components/reports/constants.js
...ts/javascripts/vue_shared/components/reports/constants.js
+3
-0
app/assets/javascripts/vue_shared/components/reports/issue_body.js
...s/javascripts/vue_shared/components/reports/issue_body.js
+3
-0
app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue
...ripts/vue_shared/components/reports/issue_status_icon.vue
+58
-0
app/assets/javascripts/vue_shared/components/reports/issues_list.vue
...javascripts/vue_shared/components/reports/issues_list.vue
+22
-19
app/assets/javascripts/vue_shared/components/reports/report_issues.vue
...vascripts/vue_shared/components/reports/report_issues.vue
+15
-108
app/assets/javascripts/vue_shared/components/reports/report_section.vue
...ascripts/vue_shared/components/reports/report_section.vue
+3
-2
app/assets/stylesheets/pages/environments.scss
app/assets/stylesheets/pages/environments.scss
+64
-3
app/helpers/environments_helper.rb
app/helpers/environments_helper.rb
+20
-0
app/models/clusters/applications/prometheus.rb
app/models/clusters/applications/prometheus.rb
+10
-0
app/models/clusters/concerns/application_status.rb
app/models/clusters/concerns/application_status.rb
+2
-0
app/models/concerns/prometheus_adapter.rb
app/models/concerns/prometheus_adapter.rb
+12
-10
app/services/clusters/applications/base_helm_service.rb
app/services/clusters/applications/base_helm_service.rb
+2
-0
app/services/clusters/applications/check_installation_progress_service.rb
...sters/applications/check_installation_progress_service.rb
+3
-3
app/services/prometheus/adapter_service.rb
app/services/prometheus/adapter_service.rb
+1
-1
app/views/projects/environments/metrics.html.haml
app/views/projects/environments/metrics.html.haml
+8
-14
app/workers/all_queues.yml
app/workers/all_queues.yml
+2
-0
config/routes/project.rb
config/routes/project.rb
+6
-0
db/schema.rb
db/schema.rb
+17
-0
ee/app/assets/javascripts/geo_nodes/components/app.vue
ee/app/assets/javascripts/geo_nodes/components/app.vue
+9
-22
ee/app/assets/javascripts/monitoring/components/alert_widget.vue
...assets/javascripts/monitoring/components/alert_widget.vue
+245
-0
ee/app/assets/javascripts/monitoring/components/alert_widget_form.vue
...s/javascripts/monitoring/components/alert_widget_form.vue
+165
-0
ee/app/assets/javascripts/monitoring/components/dashboard_mixin.js
...sets/javascripts/monitoring/components/dashboard_mixin.js
+24
-0
ee/app/assets/javascripts/monitoring/services/alerts_service.js
.../assets/javascripts/monitoring/services/alerts_service.js
+32
-0
ee/app/assets/javascripts/vue_merge_request_widget/components/codequality_issue_body.vue
...erge_request_widget/components/codequality_issue_body.vue
+8
-3
ee/app/assets/javascripts/vue_merge_request_widget/components/license_issue_body.vue
...ue_merge_request_widget/components/license_issue_body.vue
+1
-0
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
...avascripts/vue_merge_request_widget/mr_widget_options.vue
+6
-4
ee/app/assets/javascripts/vue_shared/components/reports/issue_body.js
...s/javascripts/vue_shared/components/reports/issue_body.js
+30
-0
ee/app/assets/javascripts/vue_shared/security_reports/components/dast_issue_body.vue
...ue_shared/security_reports/components/dast_issue_body.vue
+1
-6
ee/app/assets/javascripts/vue_shared/security_reports/grouped_security_reports_app.vue
..._shared/security_reports/grouped_security_reports_app.vue
+6
-8
ee/app/assets/javascripts/vue_shared/security_reports/split_security_reports_app.vue
...ue_shared/security_reports/split_security_reports_app.vue
+6
-8
ee/app/controllers/ee/projects/prometheus/metrics_controller.rb
.../controllers/ee/projects/prometheus/metrics_controller.rb
+10
-2
ee/app/controllers/projects/prometheus/alerts_controller.rb
ee/app/controllers/projects/prometheus/alerts_controller.rb
+93
-0
ee/app/helpers/ee/environments_helper.rb
ee/app/helpers/ee/environments_helper.rb
+12
-0
ee/app/mailers/emails/ee/projects.rb
ee/app/mailers/emails/ee/projects.rb
+19
-0
ee/app/models/concerns/ee/clusters/application_status.rb
ee/app/models/concerns/ee/clusters/application_status.rb
+36
-0
ee/app/models/concerns/ee/prometheus_adapter.rb
ee/app/models/concerns/ee/prometheus_adapter.rb
+21
-0
ee/app/models/ee/clusters/applications/prometheus.rb
ee/app/models/ee/clusters/applications/prometheus.rb
+48
-0
ee/app/models/ee/environment.rb
ee/app/models/ee/environment.rb
+14
-0
ee/app/models/ee/project.rb
ee/app/models/ee/project.rb
+8
-0
ee/app/models/license.rb
ee/app/models/license.rb
+1
-0
ee/app/models/prometheus_alert.rb
ee/app/models/prometheus_alert.rb
+48
-0
ee/app/models/prometheus_metric.rb
ee/app/models/prometheus_metric.rb
+4
-1
ee/app/policies/ee/project_policy.rb
ee/app/policies/ee/project_policy.rb
+5
-0
ee/app/serializers/prometheus_alert_entity.rb
ee/app/serializers/prometheus_alert_entity.rb
+24
-0
ee/app/serializers/prometheus_alert_serializer.rb
ee/app/serializers/prometheus_alert_serializer.rb
+3
-0
ee/app/services/clusters/applications/check_upgrade_progress_service.rb
...s/clusters/applications/check_upgrade_progress_service.rb
+65
-0
ee/app/services/clusters/applications/prometheus_update_service.rb
...rvices/clusters/applications/prometheus_update_service.rb
+144
-0
ee/app/services/clusters/applications/schedule_update_service.rb
...services/clusters/applications/schedule_update_service.rb
+36
-0
ee/app/services/ee/clusters/applications/base_helm_service.rb
...pp/services/ee/clusters/applications/base_helm_service.rb
+13
-0
ee/app/services/ee/notification_service.rb
ee/app/services/ee/notification_service.rb
+12
-0
ee/app/services/projects/prometheus/metrics/base_service.rb
ee/app/services/projects/prometheus/metrics/base_service.rb
+29
-0
ee/app/services/projects/prometheus/metrics/destroy_service.rb
...p/services/projects/prometheus/metrics/destroy_service.rb
+12
-0
ee/app/services/projects/prometheus/metrics/update_service.rb
...pp/services/projects/prometheus/metrics/update_service.rb
+27
-0
ee/app/views/notify/prometheus_alert_fired_email.html.haml
ee/app/views/notify/prometheus_alert_fired_email.html.haml
+14
-0
ee/app/views/notify/prometheus_alert_fired_email.text.erb
ee/app/views/notify/prometheus_alert_fired_email.text.erb
+7
-0
ee/app/workers/cluster_update_app_worker.rb
ee/app/workers/cluster_update_app_worker.rb
+21
-0
ee/app/workers/cluster_wait_for_app_update_worker.rb
ee/app/workers/cluster_wait_for_app_update_worker.rb
+14
-0
ee/changelogs/unreleased/5158-backend-metrics-alerting.yml
ee/changelogs/unreleased/5158-backend-metrics-alerting.yml
+5
-0
ee/db/migrate/20180320142552_create_prometheus_alerts.rb
ee/db/migrate/20180320142552_create_prometheus_alerts.rb
+20
-0
ee/db/migrate/20180524115107_add_last_update_started_at_to_applications_prometheus.rb
..._add_last_update_started_at_to_applications_prometheus.rb
+7
-0
ee/lib/ee/gitlab/kubernetes/helm/api.rb
ee/lib/ee/gitlab/kubernetes/helm/api.rb
+31
-0
ee/lib/ee/gitlab/prometheus/queries/query_additional_metrics.rb
.../ee/gitlab/prometheus/queries/query_additional_metrics.rb
+46
-0
ee/lib/gitlab/kubernetes/helm/get_command.rb
ee/lib/gitlab/kubernetes/helm/get_command.rb
+17
-0
ee/lib/gitlab/kubernetes/helm/upgrade_command.rb
ee/lib/gitlab/kubernetes/helm/upgrade_command.rb
+59
-0
ee/spec/controllers/projects/prometheus/alerts_controller_spec.rb
...controllers/projects/prometheus/alerts_controller_spec.rb
+211
-0
ee/spec/factories/prometheus_alert.rb
ee/spec/factories/prometheus_alert.rb
+9
-0
ee/spec/javascripts/geo_nodes/components/app_spec.js
ee/spec/javascripts/geo_nodes/components/app_spec.js
+175
-110
ee/spec/javascripts/vue_mr_widget/components/codequality_issue_body_spec.js
...s/vue_mr_widget/components/codequality_issue_body_spec.js
+13
-12
ee/spec/javascripts/vue_mr_widget/components/performance_issue_body_spec.js
...s/vue_mr_widget/components/performance_issue_body_spec.js
+1
-1
ee/spec/javascripts/vue_shared/components/reports/report_issues_spec.js
...ripts/vue_shared/components/reports/report_issues_spec.js
+17
-17
ee/spec/javascripts/vue_shared/components/reports/report_section_mock_data.js
...vue_shared/components/reports/report_section_mock_data.js
+49
-0
ee/spec/javascripts/vue_shared/components/reports/report_section_spec.js
...ipts/vue_shared/components/reports/report_section_spec.js
+51
-0
ee/spec/lib/ee/gitlab/kubernetes/helm/api_spec.rb
ee/spec/lib/ee/gitlab/kubernetes/helm/api_spec.rb
+61
-0
ee/spec/lib/gitlab/kubernetes/helm/get_command_spec.rb
ee/spec/lib/gitlab/kubernetes/helm/get_command_spec.rb
+19
-0
ee/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb
ee/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb
+72
-0
ee/spec/models/ee/clusters/applications/prometheus_spec.rb
ee/spec/models/ee/clusters/applications/prometheus_spec.rb
+124
-0
ee/spec/models/project_spec.rb
ee/spec/models/project_spec.rb
+18
-0
ee/spec/models/prometheus_alert_spec.rb
ee/spec/models/prometheus_alert_spec.rb
+40
-0
ee/spec/serializers/prometheus_alert_entity_spec.rb
ee/spec/serializers/prometheus_alert_entity_spec.rb
+25
-0
ee/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb
...sters/applications/check_upgrade_progress_service_spec.rb
+92
-0
ee/spec/services/clusters/applications/prometheus_update_service_spec.rb
...s/clusters/applications/prometheus_update_service_spec.rb
+87
-0
ee/spec/services/clusters/applications/schedule_update_service_spec.rb
...ces/clusters/applications/schedule_update_service_spec.rb
+35
-0
ee/spec/services/ee/notification_service_spec.rb
ee/spec/services/ee/notification_service_spec.rb
+17
-0
ee/spec/services/projects/prometheus/metrics/destroy_service_spec.rb
...vices/projects/prometheus/metrics/destroy_service_spec.rb
+26
-0
ee/spec/services/projects/prometheus/metrics/update_service_spec.rb
...rvices/projects/prometheus/metrics/update_service_spec.rb
+42
-0
ee/spec/workers/cluster_update_app_worker_spec.rb
ee/spec/workers/cluster_update_app_worker_spec.rb
+44
-0
ee/spec/workers/cluster_wait_for_app_update_worker_spec.rb
ee/spec/workers/cluster_wait_for_app_update_worker_spec.rb
+25
-0
lib/gitlab/kubernetes/config_map.rb
lib/gitlab/kubernetes/config_map.rb
+5
-5
lib/gitlab/kubernetes/helm/api.rb
lib/gitlab/kubernetes/helm/api.rb
+13
-9
lib/gitlab/prometheus/metric.rb
lib/gitlab/prometheus/metric.rb
+1
-1
lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
...prometheus/queries/additional_metrics_deployment_query.rb
+1
-0
lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
...rometheus/queries/additional_metrics_environment_query.rb
+1
-0
lib/gitlab/prometheus/queries/query_additional_metrics.rb
lib/gitlab/prometheus/queries/query_additional_metrics.rb
+8
-2
locale/gitlab.pot
locale/gitlab.pot
+31
-1
spec/factories/clusters/applications/helm.rb
spec/factories/clusters/applications/helm.rb
+13
-0
spec/javascripts/monitoring/alert_widget_form_spec.js
spec/javascripts/monitoring/alert_widget_form_spec.js
+90
-0
spec/javascripts/monitoring/alert_widget_spec.js
spec/javascripts/monitoring/alert_widget_spec.js
+203
-0
spec/javascripts/test_bundle.js
spec/javascripts/test_bundle.js
+13
-0
spec/javascripts/vue_shared/components/reports/report_section_spec.js
...ipts/vue_shared/components/reports/report_section_spec.js
+4
-87
spec/javascripts/vue_shared/security_reports/components/sast_issue_body_spec.js
...hared/security_reports/components/sast_issue_body_spec.js
+3
-2
spec/lib/gitlab/import_export/all_models.yml
spec/lib/gitlab/import_export/all_models.yml
+2
-0
spec/lib/gitlab/kubernetes/config_map_spec.rb
spec/lib/gitlab/kubernetes/config_map_spec.rb
+6
-0
spec/lib/gitlab/kubernetes/helm/api_spec.rb
spec/lib/gitlab/kubernetes/helm/api_spec.rb
+6
-6
spec/models/clusters/applications/prometheus_spec.rb
spec/models/clusters/applications/prometheus_spec.rb
+50
-7
spec/support/prometheus/additional_metrics_shared_examples.rb
.../support/prometheus/additional_metrics_shared_examples.rb
+4
-3
No files found.
app/assets/javascripts/boards/index.js
View file @
238c8c3e
/* eslint-disable quote-props, comma-dangle */
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
...
@@ -56,7 +54,7 @@ export default () => {
...
@@ -56,7 +54,7 @@ export default () => {
gl
.
IssueBoardsApp
=
new
Vue
({
gl
.
IssueBoardsApp
=
new
Vue
({
el
:
$boardApp
,
el
:
$boardApp
,
components
:
{
components
:
{
'
board
'
:
gl
.
issueBoards
.
Board
,
board
:
gl
.
issueBoards
.
Board
,
'
board-sidebar
'
:
gl
.
issueBoards
.
BoardSidebar
,
'
board-sidebar
'
:
gl
.
issueBoards
.
BoardSidebar
,
BoardAddIssuesModal
,
BoardAddIssuesModal
,
},
},
...
@@ -74,11 +72,11 @@ export default () => {
...
@@ -74,11 +72,11 @@ export default () => {
defaultAvatar
:
$boardApp
.
dataset
.
defaultAvatar
,
defaultAvatar
:
$boardApp
.
dataset
.
defaultAvatar
,
},
},
computed
:
{
computed
:
{
detailIssueVisible
()
{
detailIssueVisible
()
{
return
Object
.
keys
(
this
.
detailIssue
.
issue
).
length
;
return
Object
.
keys
(
this
.
detailIssue
.
issue
).
length
;
},
},
},
},
created
()
{
created
()
{
gl
.
boardService
=
new
BoardService
({
gl
.
boardService
=
new
BoardService
({
boardsEndpoint
:
this
.
boardsEndpoint
,
boardsEndpoint
:
this
.
boardsEndpoint
,
listsEndpoint
:
this
.
listsEndpoint
,
listsEndpoint
:
this
.
listsEndpoint
,
...
@@ -100,15 +98,16 @@ export default () => {
...
@@ -100,15 +98,16 @@ export default () => {
sidebarEventHub
.
$off
(
'
toggleSubscription
'
,
this
.
toggleSubscription
);
sidebarEventHub
.
$off
(
'
toggleSubscription
'
,
this
.
toggleSubscription
);
sidebarEventHub
.
$off
(
'
updateWeight
'
,
this
.
updateWeight
);
sidebarEventHub
.
$off
(
'
updateWeight
'
,
this
.
updateWeight
);
},
},
mounted
()
{
mounted
()
{
this
.
filterManager
=
new
FilteredSearchBoards
(
Store
.
filter
,
true
,
Store
.
cantEdit
);
this
.
filterManager
=
new
FilteredSearchBoards
(
Store
.
filter
,
true
,
Store
.
cantEdit
);
this
.
filterManager
.
setup
();
this
.
filterManager
.
setup
();
Store
.
disabled
=
this
.
disabled
;
Store
.
disabled
=
this
.
disabled
;
gl
.
boardService
.
all
()
gl
.
boardService
.
all
()
.
then
(
res
=>
res
.
data
)
.
then
(
res
=>
res
.
data
)
.
then
(
(
data
)
=>
{
.
then
(
data
=>
{
data
.
forEach
(
(
board
)
=>
{
data
.
forEach
(
board
=>
{
const
list
=
Store
.
addList
(
board
,
this
.
defaultAvatar
);
const
list
=
Store
.
addList
(
board
,
this
.
defaultAvatar
);
if
(
list
.
type
===
'
closed
'
)
{
if
(
list
.
type
===
'
closed
'
)
{
...
@@ -140,7 +139,7 @@ export default () => {
...
@@ -140,7 +139,7 @@ export default () => {
newIssue
.
setFetchingState
(
'
epic
'
,
true
);
newIssue
.
setFetchingState
(
'
epic
'
,
true
);
BoardService
.
getIssueInfo
(
sidebarInfoEndpoint
)
BoardService
.
getIssueInfo
(
sidebarInfoEndpoint
)
.
then
(
res
=>
res
.
data
)
.
then
(
res
=>
res
.
data
)
.
then
(
(
data
)
=>
{
.
then
(
data
=>
{
newIssue
.
setFetchingState
(
'
subscriptions
'
,
false
);
newIssue
.
setFetchingState
(
'
subscriptions
'
,
false
);
newIssue
.
setFetchingState
(
'
weight
'
,
false
);
newIssue
.
setFetchingState
(
'
weight
'
,
false
);
newIssue
.
setFetchingState
(
'
epic
'
,
false
);
newIssue
.
setFetchingState
(
'
epic
'
,
false
);
...
@@ -185,7 +184,7 @@ export default () => {
...
@@ -185,7 +184,7 @@ export default () => {
issue
.
setLoadingState
(
'
weight
'
,
true
);
issue
.
setLoadingState
(
'
weight
'
,
true
);
BoardService
.
updateWeight
(
issue
.
sidebarInfoEndpoint
,
newWeight
)
BoardService
.
updateWeight
(
issue
.
sidebarInfoEndpoint
,
newWeight
)
.
then
(
res
=>
res
.
data
)
.
then
(
res
=>
res
.
data
)
.
then
(
(
data
)
=>
{
.
then
(
data
=>
{
issue
.
setLoadingState
(
'
weight
'
,
false
);
issue
.
setLoadingState
(
'
weight
'
,
false
);
issue
.
updateData
({
issue
.
updateData
({
weight
:
data
.
weight
,
weight
:
data
.
weight
,
...
@@ -196,7 +195,7 @@ export default () => {
...
@@ -196,7 +195,7 @@ export default () => {
Flash
(
__
(
'
An error occurred when updating the issue weight
'
));
Flash
(
__
(
'
An error occurred when updating the issue weight
'
));
});
});
}
}
}
}
,
},
},
});
});
...
@@ -206,7 +205,7 @@ export default () => {
...
@@ -206,7 +205,7 @@ export default () => {
filters
:
Store
.
state
.
filters
,
filters
:
Store
.
state
.
filters
,
milestoneTitle
:
$boardApp
.
dataset
.
boardMilestoneTitle
,
milestoneTitle
:
$boardApp
.
dataset
.
boardMilestoneTitle
,
},
},
mounted
()
{
mounted
()
{
gl
.
issueBoards
.
newListDropdownInit
();
gl
.
issueBoards
.
newListDropdownInit
();
},
},
});
});
...
@@ -231,8 +230,8 @@ export default () => {
...
@@ -231,8 +230,8 @@ export default () => {
return
this
.
canAdminList
?
'
Edit board
'
:
'
View scope
'
;
return
this
.
canAdminList
?
'
Edit board
'
:
'
View scope
'
;
},
},
tooltipTitle
()
{
tooltipTitle
()
{
return
this
.
hasScope
?
__
(
'
This board
\'
s scope is reduced
'
)
:
''
;
return
this
.
hasScope
?
__
(
"
This board's scope is reduced
"
)
:
''
;
}
}
,
},
},
methods
:
{
methods
:
{
showPage
:
page
=>
gl
.
issueBoards
.
BoardsStore
.
showPage
(
page
),
showPage
:
page
=>
gl
.
issueBoards
.
BoardsStore
.
showPage
(
page
),
...
@@ -254,8 +253,11 @@ export default () => {
...
@@ -254,8 +253,11 @@ export default () => {
});
});
}
}
const
issueBoardsModal
=
document
.
getElementById
(
'
js-add-issues-btn
'
);
if
(
issueBoardsModal
)
{
gl
.
IssueBoardsModalAddBtn
=
new
Vue
({
gl
.
IssueBoardsModalAddBtn
=
new
Vue
({
el
:
document
.
getElementById
(
'
js-add-issues-btn
'
)
,
el
:
issueBoardsModal
,
mixins
:
[
modalMixin
],
mixins
:
[
modalMixin
],
data
()
{
data
()
{
return
{
return
{
...
@@ -324,6 +326,7 @@ export default () => {
...
@@ -324,6 +326,7 @@ export default () => {
</div>
</div>
`
,
`
,
});
});
}
gl
.
IssueBoardsToggleFocusBtn
=
new
Vue
({
gl
.
IssueBoardsToggleFocusBtn
=
new
Vue
({
el
:
document
.
getElementById
(
'
js-toggle-focus-btn
'
),
el
:
document
.
getElementById
(
'
js-toggle-focus-btn
'
),
...
@@ -335,7 +338,9 @@ export default () => {
...
@@ -335,7 +338,9 @@ export default () => {
},
},
methods
:
{
methods
:
{
toggleFocusMode
()
{
toggleFocusMode
()
{
if
(
!
this
.
focusModeAvailable
)
{
return
;
}
if
(
!
this
.
focusModeAvailable
)
{
return
;
}
$
(
this
.
$refs
.
toggleFocusModeButton
).
tooltip
(
'
hide
'
);
$
(
this
.
$refs
.
toggleFocusModeButton
).
tooltip
(
'
hide
'
);
issueBoardsContent
.
classList
.
toggle
(
'
is-focused
'
);
issueBoardsContent
.
classList
.
toggle
(
'
is-focused
'
);
...
@@ -369,6 +374,6 @@ export default () => {
...
@@ -369,6 +374,6 @@ export default () => {
el
:
'
#js-multiple-boards-switcher
'
,
el
:
'
#js-multiple-boards-switcher
'
,
components
:
{
components
:
{
'
boards-selector
'
:
gl
.
issueBoards
.
BoardsSelector
,
'
boards-selector
'
:
gl
.
issueBoards
.
BoardsSelector
,
}
}
,
});
});
};
};
app/assets/javascripts/monitoring/components/dashboard.vue
View file @
238c8c3e
<
script
>
<
script
>
// ee-only
import
DashboardMixin
from
'
ee/monitoring/components/dashboard_mixin
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
s__
}
from
'
~/locale
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
...
@@ -17,6 +20,10 @@ export default {
...
@@ -17,6 +20,10 @@ export default {
EmptyState
,
EmptyState
,
Icon
,
Icon
,
},
},
// ee-only
mixins
:
[
DashboardMixin
],
props
:
{
props
:
{
hasMetrics
:
{
hasMetrics
:
{
type
:
Boolean
,
type
:
Boolean
,
...
@@ -137,7 +144,7 @@ export default {
...
@@ -137,7 +144,7 @@ export default {
.
catch
(()
=>
Flash
(
s__
(
'
Metrics|There was an error getting deployment information.
'
))),
.
catch
(()
=>
Flash
(
s__
(
'
Metrics|There was an error getting deployment information.
'
))),
this
.
service
this
.
service
.
getEnvironmentsData
()
.
getEnvironmentsData
()
.
then
(
(
data
)
=>
this
.
store
.
storeEnvironmentsData
(
data
))
.
then
(
data
=>
this
.
store
.
storeEnvironmentsData
(
data
))
.
catch
(()
=>
Flash
(
s__
(
'
Metrics|There was an error getting environments information.
'
))),
.
catch
(()
=>
Flash
(
s__
(
'
Metrics|There was an error getting environments information.
'
))),
])
])
.
then
(()
=>
{
.
then
(()
=>
{
...
@@ -225,7 +232,13 @@ export default {
...
@@ -225,7 +232,13 @@ export default {
:small-graph=
"forceSmallGraph"
:small-graph=
"forceSmallGraph"
>
>
<!-- EE content -->
<!-- EE content -->
{{
null
}}
<alert-widget
v-if=
"alertsEndpoint && graphData.id"
:alerts-endpoint=
"alertsEndpoint"
:label=
"getGraphLabel(graphData)"
:current-alerts=
"getQueryAlerts(graphData)"
:custom-metric-id=
"graphData.id"
/>
</graph>
</graph>
</graph-group>
</graph-group>
</div>
</div>
...
...
app/assets/javascripts/vue_shared/components/reports/constants.js
0 → 100644
View file @
238c8c3e
export
const
STATUS_FAILED
=
'
failed
'
;
export
const
STATUS_SUCCESS
=
'
success
'
;
export
const
STATUS_NEUTRAL
=
'
neutral
'
;
app/assets/javascripts/vue_shared/components/reports/issue_body.js
0 → 100644
View file @
238c8c3e
export
const
components
=
{};
export
const
componentNames
=
{};
app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue
0 → 100644
View file @
238c8c3e
<
script
>
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
{
STATUS_FAILED
,
STATUS_NEUTRAL
,
STATUS_SUCCESS
,
}
from
'
~/vue_shared/components/reports/constants
'
;
export
default
{
name
:
'
IssueStatusIcon
'
,
components
:
{
Icon
,
},
props
:
{
// failed || success
status
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
iconName
()
{
if
(
this
.
isStatusFailed
)
{
return
'
status_failed_borderless
'
;
}
else
if
(
this
.
isStatusSuccess
)
{
return
'
status_success_borderless
'
;
}
return
'
status_created_borderless
'
;
},
isStatusFailed
()
{
return
this
.
status
===
STATUS_FAILED
;
},
isStatusSuccess
()
{
return
this
.
status
===
STATUS_SUCCESS
;
},
isStatusNeutral
()
{
return
this
.
status
===
STATUS_NEUTRAL
;
},
},
};
</
script
>
<
template
>
<div
:class=
"
{
failed: isStatusFailed,
success: isStatusSuccess,
neutral: isStatusNeutral,
}"
class="report-block-list-icon"
>
<icon
:name=
"iconName"
:size=
"32"
/>
</div>
</
template
>
app/assets/javascripts/vue_shared/components/reports/issues_list.vue
View file @
238c8c3e
<
script
>
<
script
>
import
IssuesBlock
from
'
~/vue_shared/components/reports/report_issues.vue
'
;
import
IssuesBlock
from
'
~/vue_shared/components/reports/report_issues.vue
'
;
import
{
STATUS_SUCCESS
,
STATUS_FAILED
,
STATUS_NEUTRAL
,
}
from
'
~/vue_shared/components/reports/constants
'
;
import
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
SastContainerInfo
from
'
ee/vue_shared/security_reports/components/sast_container_info.vue
'
;
import
SastContainerInfo
from
'
ee/vue_shared/security_reports/components/sast_container_info.vue
'
;
import
{
SAST_CONTAINER
}
from
'
ee/vue_shared/security_reports/store/constants
'
;
/**
/**
* Renders block of issues
* Renders block of issues
...
@@ -13,7 +18,10 @@ export default {
...
@@ -13,7 +18,10 @@ export default {
IssuesBlock
,
IssuesBlock
,
SastContainerInfo
,
SastContainerInfo
,
},
},
sastContainer
:
SAST_CONTAINER
,
componentNames
,
success
:
STATUS_SUCCESS
,
failed
:
STATUS_FAILED
,
neutral
:
STATUS_NEUTRAL
,
props
:
{
props
:
{
unresolvedIssues
:
{
unresolvedIssues
:
{
type
:
Array
,
type
:
Array
,
...
@@ -35,9 +43,10 @@ export default {
...
@@ -35,9 +43,10 @@ export default {
required
:
false
,
required
:
false
,
default
:
()
=>
[],
default
:
()
=>
[],
},
},
type
:
{
component
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
false
,
default
:
''
,
},
},
},
},
data
()
{
data
()
{
...
@@ -45,11 +54,6 @@ export default {
...
@@ -45,11 +54,6 @@ export default {
isFullReportVisible
:
false
,
isFullReportVisible
:
false
,
};
};
},
},
computed
:
{
unresolvedIssuesStatus
()
{
return
this
.
type
===
'
license
'
?
'
neutral
'
:
'
failed
'
;
},
},
methods
:
{
methods
:
{
openFullReport
()
{
openFullReport
()
{
this
.
isFullReportVisible
=
true
;
this
.
isFullReportVisible
=
true
;
...
@@ -59,38 +63,37 @@ export default {
...
@@ -59,38 +63,37 @@ export default {
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"report-block-container"
>
<div
class=
"report-block-container"
>
<sast-container-info
v-if=
"type === $options.sastContainer"
/>
<sast-container-info
v-if=
"component === $options.componentNames.SastContainerIssueBody"
/>
<issues-block
<issues-block
v-if=
"unresolvedIssues.length"
v-if=
"unresolvedIssues.length"
:type=
"type"
:component=
"component"
:status=
"unresolvedIssuesStatus"
:issues=
"unresolvedIssues"
:issues=
"unresolvedIssues"
:status=
"$options.failed"
class=
"js-mr-code-new-issues"
class=
"js-mr-code-new-issues"
/>
/>
<issues-block
<issues-block
v-if=
"isFullReportVisible"
v-if=
"isFullReportVisible"
:
type=
"type
"
:
component=
"component
"
:issues=
"allIssues"
:issues=
"allIssues"
:status=
"$options.failed"
class=
"js-mr-code-all-issues"
class=
"js-mr-code-all-issues"
status=
"failed"
/>
/>
<issues-block
<issues-block
v-if=
"neutralIssues.length"
v-if=
"neutralIssues.length"
:
type=
"type
"
:
component=
"component
"
:issues=
"neutralIssues"
:issues=
"neutralIssues"
:status=
"$options.neutral"
class=
"js-mr-code-non-issues"
class=
"js-mr-code-non-issues"
status=
"neutral"
/>
/>
<issues-block
<issues-block
v-if=
"resolvedIssues.length"
v-if=
"resolvedIssues.length"
:
type=
"type
"
:
component=
"component
"
:issues=
"resolvedIssues"
:issues=
"resolvedIssues"
:status=
"$options.success"
class=
"js-mr-code-resolved-issues"
class=
"js-mr-code-resolved-issues"
status=
"success"
/>
/>
<button
<button
...
...
app/assets/javascripts/vue_shared/components/reports/report_issues.vue
View file @
238c8c3e
<
script
>
<
script
>
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
IssueStatusIcon
from
'
~/vue_shared/components/reports/issue_status_icon.vue
'
;
import
{
components
,
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
PerformanceIssue
from
'
ee/vue_merge_request_widget/components/performance_issue_body.vue
'
;
import
CodequalityIssue
from
'
ee/vue_merge_request_widget/components/codequality_issue_body.vue
'
;
import
LicenseIssue
from
'
ee/vue_merge_request_widget/components/license_issue_body.vue
'
;
import
SastIssue
from
'
ee/vue_shared/security_reports/components/sast_issue_body.vue
'
;
import
SastContainerIssue
from
'
ee/vue_shared/security_reports/components/sast_container_issue_body.vue
'
;
import
DastIssue
from
'
ee/vue_shared/security_reports/components/dast_issue_body.vue
'
;
import
{
SAST
,
DAST
,
SAST_CONTAINER
}
from
'
ee/vue_shared/security_reports/store/constants
'
;
export
default
{
export
default
{
name
:
'
ReportIssues
'
,
name
:
'
ReportIssues
'
,
components
:
{
components
:
{
Icon
,
IssueStatusIcon
,
SastIssue
,
...
components
,
SastContainerIssue
,
DastIssue
,
PerformanceIssue
,
CodequalityIssue
,
LicenseIssue
,
},
},
props
:
{
props
:
{
issues
:
{
issues
:
{
type
:
Array
,
type
:
Array
,
required
:
true
,
required
:
true
,
},
},
// security || codequality || performance || docker || dast || license
component
:
{
type
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
false
,
default
:
''
,
validator
:
value
=>
value
===
''
||
Object
.
values
(
componentNames
).
includes
(
value
),
},
},
// failed || success
// failed || success
status
:
{
status
:
{
...
@@ -36,44 +25,6 @@ export default {
...
@@ -36,44 +25,6 @@ export default {
required
:
true
,
required
:
true
,
},
},
},
},
computed
:
{
iconName
()
{
if
(
this
.
isStatusFailed
)
{
return
'
status_failed_borderless
'
;
}
else
if
(
this
.
isStatusSuccess
)
{
return
'
status_success_borderless
'
;
}
return
'
status_created_borderless
'
;
},
isStatusFailed
()
{
return
this
.
status
===
'
failed
'
;
},
isStatusSuccess
()
{
return
this
.
status
===
'
success
'
;
},
isStatusNeutral
()
{
return
this
.
status
===
'
neutral
'
;
},
isTypeCodequality
()
{
return
this
.
type
===
'
codequality
'
;
},
isTypePerformance
()
{
return
this
.
type
===
'
performance
'
;
},
isTypeLicense
()
{
return
this
.
type
===
'
license
'
;
},
isTypeSast
()
{
return
this
.
type
===
SAST
;
},
isTypeSastContainer
()
{
return
this
.
type
===
SAST_CONTAINER
;
},
isTypeDast
()
{
return
this
.
type
===
DAST
;
},
},
};
};
</
script
>
</
script
>
<
template
>
<
template
>
...
@@ -85,60 +36,16 @@ export default {
...
@@ -85,60 +36,16 @@ export default {
:key="index"
:key="index"
class="report-block-list-issue"
class="report-block-list-issue"
>
>
<div
<issue-status-icon
:class=
"
{
:status=
"issue.status || status"
failed: isStatusFailed,
class=
"append-right-5"
success: isStatusSuccess,
neutral: isStatusNeutral,
}"
class="report-block-list-icon append-right-5"
>
<icon
v-if=
"isTypeLicense"
:size=
"24"
name=
"status_created_borderless"
css-classes=
"prepend-left-4"
/>
<icon
v-else
:name=
"iconName"
:size=
"32"
/>
</div>
<sast-issue
v-if=
"isTypeSast"
:issue=
"issue"
:status=
"status"
/>
<dast-issue
v-else-if=
"isTypeDast"
:issue=
"issue"
:issue-index=
"index"
:status=
"status"
/>
<sast-container-issue
v-else-if=
"isTypeSastContainer"
:issue=
"issue"
:status=
"status"
/>
<codequality-issue
v-else-if=
"isTypeCodequality"
:is-status-success=
"isStatusSuccess"
:issue=
"issue"
/>
<performance-issue
v-else-if=
"isTypePerformance"
:issue=
"issue"
/>
/>
<license-issue
<component
v-else-if=
"isTypeLicense"
v-if=
"component"
:is=
"component"
:issue=
"issue"
:issue=
"issue"
:status=
"issue.status || status"
/>
/>
</li>
</li>
</ul>
</ul>
...
...
app/assets/javascripts/vue_shared/components/reports/report_section.vue
View file @
238c8c3e
...
@@ -21,7 +21,7 @@ export default {
...
@@ -21,7 +21,7 @@ export default {
required
:
false
,
required
:
false
,
default
:
false
,
default
:
false
,
},
},
type
:
{
component
:
{
type
:
String
,
type
:
String
,
required
:
false
,
required
:
false
,
default
:
''
,
default
:
''
,
...
@@ -183,8 +183,9 @@ export default {
...
@@ -183,8 +183,9 @@ export default {
<issues-list
<issues-list
:unresolved-issues=
"unresolvedIssues"
:unresolved-issues=
"unresolvedIssues"
:resolved-issues=
"resolvedIssues"
:resolved-issues=
"resolvedIssues"
:neutral-issues=
"neutralIssues"
:all-issues=
"allIssues"
:all-issues=
"allIssues"
:
type=
"type
"
:
component=
"component
"
/>
/>
</slot>
</slot>
</div>
</div>
...
...
app/assets/stylesheets/pages/environments.scss
View file @
238c8c3e
...
@@ -267,7 +267,7 @@
...
@@ -267,7 +267,7 @@
border
:
1px
solid
$white-light
;
border
:
1px
solid
$white-light
;
background-color
:
$orange-300
;
background-color
:
$orange-300
;
border-radius
:
50%
;
border-radius
:
50%
;
content
:
""
;
content
:
''
;
}
}
}
}
}
}
...
@@ -287,8 +287,6 @@
...
@@ -287,8 +287,6 @@
}
}
}
}
.gl-responsive-table-row
{
.gl-responsive-table-row
{
.branch-commit
{
.branch-commit
{
max-width
:
100%
;
max-width
:
100%
;
...
@@ -679,3 +677,66 @@
...
@@ -679,3 +677,66 @@
}
}
}
}
}
}
.alert-dropdown-button
{
margin-left
:
$btn-side-margin
;
.dropdown.open
&
{
background
:
$white-normal
;
outline
:
0
;
}
svg
{
margin
:
0
;
+
svg
{
margin-left
:
-
$gl-padding-4
;
}
&
.chevron
{
color
:
$gl-text-color-secondary
;
}
}
}
.alert-dropdown-menu
{
right
:
0
;
left
:
auto
;
z-index
:
$zindex-popover
+
5
;
// must be higher than graph flag popover
.dropdown-title
{
margin
:
0
;
}
}
.alert-error-message
{
color
:
$gl-danger
;
vertical-align
:
middle
;
}
.alert-current-setting
{
color
:
$gl-text-color-disabled
;
vertical-align
:
middle
;
}
.alert-form
{
padding
:
$gl-padding
$gl-padding
$gl-padding-8
;
label
{
font-weight
:
normal
;
}
.btn-group
,
.action-group
{
display
:
flex
;
.btn
{
flex
:
1
auto
;
box-shadow
:
none
;
}
}
.action-group
.btn
+
.btn
{
margin-left
:
$gl-padding-8
;
}
}
app/helpers/environments_helper.rb
View file @
238c8c3e
module
EnvironmentsHelper
module
EnvironmentsHelper
prepend
::
EE
::
EnvironmentsHelper
def
environments_list_data
def
environments_list_data
{
{
endpoint:
project_environments_path
(
@project
,
format: :json
)
endpoint:
project_environments_path
(
@project
,
format: :json
)
}
}
end
end
def
metrics_data
(
project
,
environment
)
{
"settings-path"
=>
edit_project_service_path
(
project
,
'prometheus'
),
"clusters-path"
=>
project_clusters_path
(
project
),
"documentation-path"
=>
help_page_path
(
'administration/monitoring/prometheus/index.md'
),
"empty-getting-started-svg-path"
=>
image_path
(
'illustrations/monitoring/getting_started.svg'
),
"empty-loading-svg-path"
=>
image_path
(
'illustrations/monitoring/loading.svg'
),
"empty-no-data-svg-path"
=>
image_path
(
'illustrations/monitoring/no_data.svg'
),
"empty-unable-to-connect-svg-path"
=>
image_path
(
'illustrations/monitoring/unable_to_connect.svg'
),
"metrics-endpoint"
=>
additional_metrics_project_environment_path
(
project
,
environment
,
format: :json
),
"deployment-endpoint"
=>
project_environment_deployments_path
(
project
,
environment
,
format: :json
),
"environments-endpoint"
:
project_environments_path
(
project
,
format: :json
),
"project-path"
=>
project_path
(
project
),
"tags-path"
=>
project_tags_path
(
project
),
"has-metrics"
=>
"
#{
environment
.
has_metrics?
}
"
}
end
end
end
app/models/clusters/applications/prometheus.rb
View file @
238c8c3e
...
@@ -11,6 +11,8 @@ module Clusters
...
@@ -11,6 +11,8 @@ module Clusters
include
::
Clusters
::
Concerns
::
ApplicationStatus
include
::
Clusters
::
Concerns
::
ApplicationStatus
include
::
Clusters
::
Concerns
::
ApplicationData
include
::
Clusters
::
Concerns
::
ApplicationData
prepend
EE
::
Clusters
::
Applications
::
Prometheus
default_value_for
:version
,
VERSION
default_value_for
:version
,
VERSION
state_machine
:status
do
state_machine
:status
do
...
@@ -21,6 +23,14 @@ module Clusters
...
@@ -21,6 +23,14 @@ module Clusters
end
end
end
end
def
ready_status
[
:installed
]
end
def
ready?
ready_status
.
include?
(
status_name
)
end
def
chart
def
chart
'stable/prometheus'
'stable/prometheus'
end
end
...
...
app/models/clusters/concerns/application_status.rb
View file @
238c8c3e
...
@@ -4,6 +4,8 @@ module Clusters
...
@@ -4,6 +4,8 @@ module Clusters
extend
ActiveSupport
::
Concern
extend
ActiveSupport
::
Concern
included
do
included
do
prepend
::
EE
::
Clusters
::
ApplicationStatus
scope
:installed
,
->
{
where
(
status:
self
.
state_machines
[
:status
].
states
[
:installed
].
value
)
}
scope
:installed
,
->
{
where
(
status:
self
.
state_machines
[
:status
].
states
[
:installed
].
value
)
}
state_machine
:status
,
initial: :not_installable
do
state_machine
:status
,
initial: :not_installable
do
...
...
app/models/concerns/prometheus_adapter.rb
View file @
238c8c3e
...
@@ -3,6 +3,7 @@ module PrometheusAdapter
...
@@ -3,6 +3,7 @@ module PrometheusAdapter
included
do
included
do
include
ReactiveCaching
include
ReactiveCaching
prepend
EE
::
PrometheusAdapter
self
.
reactive_cache_key
=
->
(
adapter
)
{
[
adapter
.
class
.
model_name
.
singular
,
adapter
.
id
]
}
self
.
reactive_cache_key
=
->
(
adapter
)
{
[
adapter
.
class
.
model_name
.
singular
,
adapter
.
id
]
}
self
.
reactive_cache_lease_timeout
=
30
.
seconds
self
.
reactive_cache_lease_timeout
=
30
.
seconds
...
@@ -24,17 +25,10 @@ module PrometheusAdapter
...
@@ -24,17 +25,10 @@ module PrometheusAdapter
def
query
(
query_name
,
*
args
)
def
query
(
query_name
,
*
args
)
return
unless
can_query?
return
unless
can_query?
query_class
=
Gitlab
::
Prometheus
::
Queries
.
const_get
(
"
#{
query_name
.
to_s
.
classify
}
Query"
)
query_class
=
query_klass_for
(
query_name
)
query_args
=
build_query_args
(
*
args
)
args
.
map!
do
|
arg
|
with_reactive_cache
(
query_class
.
name
,
*
query_args
,
&
query_class
.
method
(
:transform_reactive_result
))
if
arg
.
respond_to?
(
:id
)
arg
.
id
else
arg
end
end
with_reactive_cache
(
query_class
.
name
,
*
args
,
&
query_class
.
method
(
:transform_reactive_result
))
end
end
# Cache metrics for specific environment
# Cache metrics for specific environment
...
@@ -50,5 +44,13 @@ module PrometheusAdapter
...
@@ -50,5 +44,13 @@ module PrometheusAdapter
rescue
Gitlab
::
PrometheusClient
::
Error
=>
err
rescue
Gitlab
::
PrometheusClient
::
Error
=>
err
{
success:
false
,
result:
err
.
message
}
{
success:
false
,
result:
err
.
message
}
end
end
def
query_klass_for
(
query_name
)
Gitlab
::
Prometheus
::
Queries
.
const_get
(
"
#{
query_name
.
to_s
.
classify
}
Query"
)
end
def
build_query_args
(
*
args
)
args
.
map
(
&
:id
)
end
end
end
end
end
app/services/clusters/applications/base_helm_service.rb
View file @
238c8c3e
...
@@ -5,6 +5,8 @@ module Clusters
...
@@ -5,6 +5,8 @@ module Clusters
class
BaseHelmService
class
BaseHelmService
attr_accessor
:app
attr_accessor
:app
prepend
EE
::
Clusters
::
Applications
::
BaseHelmService
def
initialize
(
app
)
def
initialize
(
app
)
@app
=
app
@app
=
app
end
end
...
...
app/services/clusters/applications/check_installation_progress_service.rb
View file @
238c8c3e
...
@@ -50,17 +50,17 @@ module Clusters
...
@@ -50,17 +50,17 @@ module Clusters
end
end
def
remove_installation_pod
def
remove_installation_pod
helm_api
.
delete_
installation_
pod!
(
install_command
.
pod_name
)
helm_api
.
delete_pod!
(
install_command
.
pod_name
)
rescue
rescue
# no-op
# no-op
end
end
def
installation_phase
def
installation_phase
helm_api
.
installation_
status
(
install_command
.
pod_name
)
helm_api
.
status
(
install_command
.
pod_name
)
end
end
def
installation_errors
def
installation_errors
helm_api
.
installation_
log
(
install_command
.
pod_name
)
helm_api
.
log
(
install_command
.
pod_name
)
end
end
end
end
end
end
...
...
app/services/prometheus/adapter_service.rb
View file @
238c8c3e
...
@@ -30,7 +30,7 @@ module Prometheus
...
@@ -30,7 +30,7 @@ module Prometheus
return
unless
deployment_platform
.
respond_to?
(
:cluster
)
return
unless
deployment_platform
.
respond_to?
(
:cluster
)
cluster
=
deployment_platform
.
cluster
cluster
=
deployment_platform
.
cluster
return
unless
cluster
.
application_prometheus
&
.
installed
?
return
unless
cluster
.
application_prometheus
&
.
ready
?
cluster
.
application_prometheus
cluster
.
application_prometheus
end
end
...
...
app/views/projects/environments/metrics.html.haml
View file @
238c8c3e
...
@@ -2,17 +2,11 @@
...
@@ -2,17 +2,11 @@
-
page_title
"Metrics for environment"
,
@environment
.
name
-
page_title
"Metrics for environment"
,
@environment
.
name
.prometheus-container
{
class:
container_class
}
.prometheus-container
{
class:
container_class
}
#prometheus-graphs
{
data:
{
"settings-path"
:
edit_project_service_path
(
@project
,
'prometheus'
),
.top-area
"clusters-path"
:
project_clusters_path
(
@project
),
.row
"current-environment-name"
:
@environment
.
name
,
.col-sm-6
"documentation-path"
:
help_page_path
(
'administration/monitoring/prometheus/index.md'
),
%h3
"empty-getting-started-svg-path"
:
image_path
(
'illustrations/monitoring/getting_started.svg'
),
Environment:
"empty-loading-svg-path"
:
image_path
(
'illustrations/monitoring/loading.svg'
),
=
link_to
@environment
.
name
,
environment_path
(
@environment
)
"empty-no-data-svg-path"
:
image_path
(
'illustrations/monitoring/no_data.svg'
),
"empty-unable-to-connect-svg-path"
:
image_path
(
'illustrations/monitoring/unable_to_connect.svg'
),
#prometheus-graphs
{
data:
metrics_data
(
@project
,
@environment
)
}
"metrics-endpoint"
:
additional_metrics_project_environment_path
(
@project
,
@environment
,
format: :json
),
"deployment-endpoint"
:
project_environment_deployments_path
(
@project
,
@environment
,
format: :json
),
"environments-endpoint"
:
project_environments_path
(
@project
,
format: :json
),
"project-path"
:
project_path
(
@project
),
"tags-path"
:
project_tags_path
(
@project
),
"has-metrics"
:
"#{@environment.has_metrics?}"
}
}
app/workers/all_queues.yml
View file @
238c8c3e
...
@@ -22,8 +22,10 @@
...
@@ -22,8 +22,10 @@
-
cronjob:prune_web_hook_logs
-
cronjob:prune_web_hook_logs
-
gcp_cluster:cluster_install_app
-
gcp_cluster:cluster_install_app
-
gcp_cluster:cluster_update_app
-
gcp_cluster:cluster_provision
-
gcp_cluster:cluster_provision
-
gcp_cluster:cluster_wait_for_app_installation
-
gcp_cluster:cluster_wait_for_app_installation
-
gcp_cluster:cluster_wait_for_app_update
-
gcp_cluster:wait_for_cluster_creation
-
gcp_cluster:wait_for_cluster_creation
-
gcp_cluster:cluster_wait_for_ingress_ip_address
-
gcp_cluster:cluster_wait_for_ingress_ip_address
...
...
config/routes/project.rb
View file @
238c8c3e
...
@@ -80,6 +80,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
...
@@ -80,6 +80,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post
:validate_query
,
on: :collection
post
:validate_query
,
on: :collection
get
:active_common
,
on: :collection
get
:active_common
,
on: :collection
end
end
# EE-specific
resources
:alerts
,
constraints:
{
id:
/\d+/
},
only:
[
:index
,
:create
,
:show
,
:update
,
:destroy
]
do
post
:notify
,
on: :collection
end
# EE-specific
end
end
resources
:deploy_keys
,
constraints:
{
id:
/\d+/
},
only:
[
:index
,
:new
,
:create
,
:edit
,
:update
]
do
resources
:deploy_keys
,
constraints:
{
id:
/\d+/
},
only:
[
:index
,
:new
,
:create
,
:edit
,
:update
]
do
...
...
db/schema.rb
View file @
238c8c3e
...
@@ -778,6 +778,7 @@ ActiveRecord::Schema.define(version: 20180722103201) do
...
@@ -778,6 +778,7 @@ ActiveRecord::Schema.define(version: 20180722103201) do
t
.
text
"status_reason"
t
.
text
"status_reason"
t
.
datetime_with_timezone
"created_at"
,
null:
false
t
.
datetime_with_timezone
"created_at"
,
null:
false
t
.
datetime_with_timezone
"updated_at"
,
null:
false
t
.
datetime_with_timezone
"updated_at"
,
null:
false
t
.
datetime_with_timezone
"last_update_started_at"
end
end
create_table
"clusters_applications_runners"
,
force: :cascade
do
|
t
|
create_table
"clusters_applications_runners"
,
force: :cascade
do
|
t
|
...
@@ -2180,6 +2181,19 @@ ActiveRecord::Schema.define(version: 20180722103201) do
...
@@ -2180,6 +2181,19 @@ ActiveRecord::Schema.define(version: 20180722103201) do
add_index
"projects"
,
[
"star_count"
],
name:
"index_projects_on_star_count"
,
using: :btree
add_index
"projects"
,
[
"star_count"
],
name:
"index_projects_on_star_count"
,
using: :btree
add_index
"projects"
,
[
"visibility_level"
],
name:
"index_projects_on_visibility_level"
,
using: :btree
add_index
"projects"
,
[
"visibility_level"
],
name:
"index_projects_on_visibility_level"
,
using: :btree
create_table
"prometheus_alerts"
,
force: :cascade
do
|
t
|
t
.
datetime_with_timezone
"created_at"
,
null:
false
t
.
datetime_with_timezone
"updated_at"
,
null:
false
t
.
float
"threshold"
,
null:
false
t
.
integer
"operator"
,
null:
false
t
.
integer
"environment_id"
,
null:
false
t
.
integer
"project_id"
,
null:
false
t
.
integer
"prometheus_metric_id"
,
null:
false
end
add_index
"prometheus_alerts"
,
[
"environment_id"
],
name:
"index_prometheus_alerts_on_environment_id"
,
using: :btree
add_index
"prometheus_alerts"
,
[
"prometheus_metric_id"
],
name:
"index_prometheus_alerts_on_prometheus_metric_id"
,
unique:
true
,
using: :btree
create_table
"prometheus_metrics"
,
force: :cascade
do
|
t
|
create_table
"prometheus_metrics"
,
force: :cascade
do
|
t
|
t
.
integer
"project_id"
t
.
integer
"project_id"
t
.
string
"title"
,
null:
false
t
.
string
"title"
,
null:
false
...
@@ -2980,6 +2994,9 @@ ActiveRecord::Schema.define(version: 20180722103201) do
...
@@ -2980,6 +2994,9 @@ ActiveRecord::Schema.define(version: 20180722103201) do
add_foreign_key
"project_mirror_data"
,
"projects"
,
name:
"fk_d1aad367d7"
,
on_delete: :cascade
add_foreign_key
"project_mirror_data"
,
"projects"
,
name:
"fk_d1aad367d7"
,
on_delete: :cascade
add_foreign_key
"project_repository_states"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"project_repository_states"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"project_statistics"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"project_statistics"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"prometheus_alerts"
,
"environments"
,
on_delete: :cascade
add_foreign_key
"prometheus_alerts"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"prometheus_alerts"
,
"prometheus_metrics"
,
on_delete: :cascade
add_foreign_key
"prometheus_metrics"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"prometheus_metrics"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"protected_branch_merge_access_levels"
,
"namespaces"
,
column:
"group_id"
,
name:
"fk_98f3d044fe"
,
on_delete: :cascade
add_foreign_key
"protected_branch_merge_access_levels"
,
"namespaces"
,
column:
"group_id"
,
name:
"fk_98f3d044fe"
,
on_delete: :cascade
add_foreign_key
"protected_branch_merge_access_levels"
,
"protected_branches"
,
name:
"fk_8a3072ccb3"
,
on_delete: :cascade
add_foreign_key
"protected_branch_merge_access_levels"
,
"protected_branches"
,
name:
"fk_8a3072ccb3"
,
on_delete: :cascade
...
...
ee/app/assets/javascripts/geo_nodes/components/app.vue
View file @
238c8c3e
...
@@ -83,7 +83,7 @@ export default {
...
@@ -83,7 +83,7 @@ export default {
});
});
},
},
fetchGeoNodes
()
{
fetchGeoNodes
()
{
this
.
service
return
this
.
service
.
getGeoNodes
()
.
getGeoNodes
()
.
then
(
res
=>
res
.
data
)
.
then
(
res
=>
res
.
data
)
.
then
(
nodes
=>
{
.
then
(
nodes
=>
{
...
@@ -92,9 +92,7 @@ export default {
...
@@ -92,9 +92,7 @@ export default {
})
})
.
catch
(()
=>
{
.
catch
(()
=>
{
this
.
isLoading
=
false
;
this
.
isLoading
=
false
;
Flash
(
Flash
(
s__
(
'
GeoNodes|Something went wrong while fetching nodes
'
));
s__
(
'
GeoNodes|Something went wrong while fetching nodes
'
),
);
});
});
},
},
fetchNodeDetails
(
node
)
{
fetchNodeDetails
(
node
)
{
...
@@ -109,10 +107,7 @@ export default {
...
@@ -109,10 +107,7 @@ export default {
primaryRevision
:
primaryNodeVersion
.
revision
,
primaryRevision
:
primaryNodeVersion
.
revision
,
});
});
this
.
store
.
setNodeDetails
(
nodeId
,
updatedNodeDetails
);
this
.
store
.
setNodeDetails
(
nodeId
,
updatedNodeDetails
);
eventHub
.
$emit
(
eventHub
.
$emit
(
'
nodeDetailsLoaded
'
,
this
.
store
.
getNodeDetails
(
nodeId
));
'
nodeDetailsLoaded
'
,
this
.
store
.
getNodeDetails
(
nodeId
),
);
})
})
.
catch
(
err
=>
{
.
catch
(
err
=>
{
if
(
err
.
response
&&
err
.
response
.
data
)
{
if
(
err
.
response
&&
err
.
response
.
data
)
{
...
@@ -124,10 +119,7 @@ export default {
...
@@ -124,10 +119,7 @@ export default {
sync_status_unavailable
:
true
,
sync_status_unavailable
:
true
,
storage_shards_match
:
null
,
storage_shards_match
:
null
,
});
});
eventHub
.
$emit
(
eventHub
.
$emit
(
'
nodeDetailsLoaded
'
,
this
.
store
.
getNodeDetails
(
nodeId
));
'
nodeDetailsLoaded
'
,
this
.
store
.
getNodeDetails
(
nodeId
),
);
}
else
{
}
else
{
eventHub
.
$emit
(
'
nodeDetailsLoadFailed
'
,
nodeId
,
err
);
eventHub
.
$emit
(
'
nodeDetailsLoadFailed
'
,
nodeId
,
err
);
}
}
...
@@ -135,14 +127,11 @@ export default {
...
@@ -135,14 +127,11 @@ export default {
},
},
repairNode
(
targetNode
)
{
repairNode
(
targetNode
)
{
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
service
return
this
.
service
.
repairNode
(
targetNode
)
.
repairNode
(
targetNode
)
.
then
(()
=>
{
.
then
(()
=>
{
this
.
setNodeActionStatus
(
targetNode
,
false
);
this
.
setNodeActionStatus
(
targetNode
,
false
);
Flash
(
Flash
(
s__
(
'
GeoNodes|Node Authentication was successfully repaired.
'
),
'
notice
'
);
s__
(
'
GeoNodes|Node Authentication was successfully repaired.
'
),
'
notice
'
,
);
})
})
.
catch
(()
=>
{
.
catch
(()
=>
{
this
.
setNodeActionStatus
(
targetNode
,
false
);
this
.
setNodeActionStatus
(
targetNode
,
false
);
...
@@ -151,7 +140,7 @@ export default {
...
@@ -151,7 +140,7 @@ export default {
},
},
toggleNode
(
targetNode
)
{
toggleNode
(
targetNode
)
{
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
service
return
this
.
service
.
toggleNode
(
targetNode
)
.
toggleNode
(
targetNode
)
.
then
(
res
=>
res
.
data
)
.
then
(
res
=>
res
.
data
)
.
then
(
node
=>
{
.
then
(
node
=>
{
...
@@ -162,14 +151,12 @@ export default {
...
@@ -162,14 +151,12 @@ export default {
})
})
.
catch
(()
=>
{
.
catch
(()
=>
{
this
.
setNodeActionStatus
(
targetNode
,
false
);
this
.
setNodeActionStatus
(
targetNode
,
false
);
Flash
(
Flash
(
s__
(
'
GeoNodes|Something went wrong while changing node status
'
));
s__
(
'
GeoNodes|Something went wrong while changing node status
'
),
);
});
});
},
},
removeNode
(
targetNode
)
{
removeNode
(
targetNode
)
{
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
service
return
this
.
service
.
removeNode
(
targetNode
)
.
removeNode
(
targetNode
)
.
then
(()
=>
{
.
then
(()
=>
{
this
.
store
.
removeNode
(
targetNode
);
this
.
store
.
removeNode
(
targetNode
);
...
...
ee/app/assets/javascripts/monitoring/components/alert_widget.vue
0 → 100644
View file @
238c8c3e
<
script
>
import
{
s__
}
from
'
~/locale
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
LoadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
AlertWidgetForm
from
'
./alert_widget_form.vue
'
;
import
AlertsService
from
'
../services/alerts_service
'
;
export
default
{
components
:
{
Icon
,
LoadingIcon
,
AlertWidgetForm
,
},
props
:
{
alertsEndpoint
:
{
type
:
String
,
required
:
true
,
},
label
:
{
type
:
String
,
required
:
true
,
},
currentAlerts
:
{
type
:
Array
,
require
:
false
,
default
:
()
=>
[],
},
customMetricId
:
{
type
:
Number
,
require
:
false
,
default
:
null
,
},
},
data
()
{
return
{
service
:
null
,
errorMessage
:
null
,
isLoading
:
false
,
isOpen
:
false
,
alerts
:
this
.
currentAlerts
,
alertData
:
{},
};
},
computed
:
{
alertSummary
()
{
const
data
=
this
.
firstAlertData
;
if
(
!
data
)
return
null
;
return
`
${
this
.
label
}
${
data
.
operator
}
${
data
.
threshold
}
`
;
},
alertIcon
()
{
return
this
.
hasAlerts
?
'
notifications
'
:
'
notifications-off
'
;
},
alertStatus
()
{
return
this
.
hasAlerts
?
s__
(
'
PrometheusAlerts|Alert set
'
)
:
s__
(
'
PrometheusAlerts|No alert set
'
);
},
dropdownTitle
()
{
return
this
.
hasAlerts
?
s__
(
'
PrometheusAlerts|Edit alert
'
)
:
s__
(
'
PrometheusAlerts|Add alert
'
);
},
hasAlerts
()
{
return
this
.
alerts
.
length
>
0
;
},
firstAlert
()
{
return
this
.
hasAlerts
?
this
.
alerts
[
0
]
:
undefined
;
},
firstAlertData
()
{
return
this
.
hasAlerts
?
this
.
alertData
[
this
.
alerts
[
0
]]
:
undefined
;
},
formDisabled
()
{
return
!!
(
this
.
errorMessage
||
this
.
isLoading
);
},
},
watch
:
{
isOpen
(
open
)
{
if
(
open
)
{
document
.
addEventListener
(
'
click
'
,
this
.
handleOutsideClick
);
}
else
{
document
.
removeEventListener
(
'
click
'
,
this
.
handleOutsideClick
);
}
},
},
created
()
{
this
.
service
=
new
AlertsService
({
alertsEndpoint
:
this
.
alertsEndpoint
});
this
.
fetchAlertData
();
},
beforeDestroy
()
{
// clean up external event listeners
document
.
removeEventListener
(
'
click
'
,
this
.
handleOutsideClick
);
},
methods
:
{
fetchAlertData
()
{
this
.
isLoading
=
true
;
return
Promise
.
all
(
this
.
alerts
.
map
(
alertPath
=>
this
.
service
.
readAlert
(
alertPath
)
.
then
(
alertData
=>
this
.
$set
(
this
.
alertData
,
alertPath
,
alertData
)),
),
)
.
then
(()
=>
{
this
.
isLoading
=
false
;
})
.
catch
(()
=>
{
this
.
errorMessage
=
s__
(
'
PrometheusAlerts|Error fetching alert
'
);
this
.
isLoading
=
false
;
});
},
handleDropdownToggle
()
{
this
.
isOpen
=
!
this
.
isOpen
;
},
handleDropdownClose
()
{
this
.
isOpen
=
false
;
},
handleOutsideClick
(
event
)
{
if
(
!
this
.
$refs
.
dropdownMenu
.
contains
(
event
.
target
))
{
this
.
isOpen
=
false
;
}
},
handleCreate
({
operator
,
threshold
})
{
const
newAlert
=
{
operator
,
threshold
,
prometheus_metric_id
:
this
.
customMetricId
};
this
.
isLoading
=
true
;
this
.
service
.
createAlert
(
newAlert
)
.
then
(
response
=>
{
const
alertPath
=
response
.
alert_path
;
this
.
alerts
.
unshift
(
alertPath
);
this
.
$set
(
this
.
alertData
,
alertPath
,
newAlert
);
this
.
isLoading
=
false
;
this
.
handleDropdownClose
();
})
.
catch
(()
=>
{
this
.
errorMessage
=
s__
(
'
PrometheusAlerts|Error creating alert
'
);
this
.
isLoading
=
false
;
});
},
handleUpdate
({
alert
,
operator
,
threshold
})
{
const
updatedAlert
=
{
operator
,
threshold
};
this
.
isLoading
=
true
;
this
.
service
.
updateAlert
(
alert
,
updatedAlert
)
.
then
(()
=>
{
this
.
$set
(
this
.
alertData
,
alert
,
updatedAlert
);
this
.
isLoading
=
false
;
this
.
handleDropdownClose
();
})
.
catch
(()
=>
{
this
.
errorMessage
=
s__
(
'
PrometheusAlerts|Error saving alert
'
);
this
.
isLoading
=
false
;
});
},
handleDelete
({
alert
})
{
this
.
isLoading
=
true
;
this
.
service
.
deleteAlert
(
alert
)
.
then
(()
=>
{
this
.
$delete
(
this
.
alertData
,
alert
);
this
.
alerts
=
this
.
alerts
.
filter
(
alertPath
=>
alert
!==
alertPath
);
this
.
isLoading
=
false
;
this
.
handleDropdownClose
();
})
.
catch
(()
=>
{
this
.
errorMessage
=
s__
(
'
PrometheusAlerts|Error deleting alert
'
);
this
.
isLoading
=
false
;
});
},
},
};
</
script
>
<
template
>
<div
:class=
"
{ show: isOpen }"
class="prometheus-alert-widget dropdown"
>
<span
v-if=
"errorMessage"
class=
"alert-error-message"
>
{{
errorMessage
}}
</span>
<span
v-else
class=
"alert-current-setting"
>
<loading-icon
v-show=
"isLoading"
:inline=
"true"
/>
{{
alertSummary
}}
</span>
<button
:aria-label=
"alertStatus"
class=
"btn btn-sm alert-dropdown-button"
type=
"button"
@
click=
"handleDropdownToggle"
>
<icon
:name=
"alertIcon"
:size=
"16"
aria-hidden=
"true"
/>
<icon
:size=
"16"
name=
"arrow-down"
aria-hidden=
"true"
class=
"chevron"
/>
</button>
<div
ref=
"dropdownMenu"
class=
"dropdown-menu alert-dropdown-menu"
>
<div
class=
"dropdown-title"
>
<span>
{{
dropdownTitle
}}
</span>
<button
class=
"dropdown-title-button dropdown-menu-close"
type=
"button"
aria-label=
"Close"
@
click=
"handleDropdownClose"
>
<icon
:size=
"12"
name=
"close"
aria-hidden=
"true"
/>
</button>
</div>
<div
class=
"dropdown-content"
>
<alert-widget-form
ref=
"widgetForm"
:disabled=
"formDisabled"
:alert=
"firstAlert"
:alert-data=
"firstAlertData"
@
create=
"handleCreate"
@
update=
"handleUpdate"
@
delete=
"handleDelete"
@
cancel=
"handleDropdownClose"
/>
</div>
</div>
</div>
</
template
>
ee/app/assets/javascripts/monitoring/components/alert_widget_form.vue
0 → 100644
View file @
238c8c3e
<
script
>
import
{
__
}
from
'
~/locale
'
;
import
Vue
from
'
vue
'
;
import
Translate
from
'
~/vue_shared/translate
'
;
Vue
.
use
(
Translate
);
const
SUBMIT_ACTION_TEXT
=
{
create
:
__
(
'
Add
'
),
update
:
__
(
'
Save
'
),
delete
:
__
(
'
Delete
'
),
};
const
SUBMIT_BUTTON_CLASS
=
{
create
:
'
btn-create
'
,
update
:
'
btn-save
'
,
delete
:
'
btn-remove
'
,
};
const
OPERATORS
=
{
greaterThan
:
'
>
'
,
equalTo
:
'
=
'
,
lessThan
:
'
<
'
,
};
export
default
{
props
:
{
disabled
:
{
type
:
Boolean
,
required
:
true
,
},
alert
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
alertData
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
},
data
()
{
return
{
operators
:
OPERATORS
,
operator
:
this
.
alertData
.
operator
,
threshold
:
this
.
alertData
.
threshold
,
};
},
computed
:
{
haveValuesChanged
()
{
return
(
this
.
operator
&&
this
.
threshold
===
Number
(
this
.
threshold
)
&&
(
this
.
operator
!==
this
.
alertData
.
operator
||
this
.
threshold
!==
this
.
alertData
.
threshold
)
);
},
submitAction
()
{
if
(
!
this
.
alert
)
return
'
create
'
;
if
(
this
.
haveValuesChanged
)
return
'
update
'
;
return
'
delete
'
;
},
submitActionText
()
{
return
SUBMIT_ACTION_TEXT
[
this
.
submitAction
];
},
submitButtonClass
()
{
return
SUBMIT_BUTTON_CLASS
[
this
.
submitAction
];
},
isSubmitDisabled
()
{
return
this
.
disabled
||
(
this
.
submitAction
===
'
create
'
&&
!
this
.
haveValuesChanged
);
},
},
watch
:
{
alertData
()
{
this
.
resetAlertData
();
},
},
methods
:
{
handleCancel
()
{
this
.
resetAlertData
();
this
.
$emit
(
'
cancel
'
);
},
handleSubmit
()
{
this
.
$refs
.
submitButton
.
blur
();
this
.
$emit
(
this
.
submitAction
,
{
alert
:
this
.
alert
,
operator
:
this
.
operator
,
threshold
:
this
.
threshold
,
});
},
resetAlertData
()
{
this
.
operator
=
this
.
alertData
.
operator
;
this
.
threshold
=
this
.
alertData
.
threshold
;
},
},
};
</
script
>
<
template
>
<div
class=
"alert-form"
>
<div
:aria-label=
"s__('PrometheusAlerts|Operator')"
class=
"form-group btn-group"
role=
"group"
>
<button
:class=
"
{ active: operator === operators.greaterThan }"
:disabled="disabled"
type="button"
class="btn btn-default"
@click="operator = operators.greaterThan"
>
{{
operators
.
greaterThan
}}
</button>
<button
:class=
"
{ active: operator === operators.equalTo }"
:disabled="disabled"
type="button"
class="btn btn-default"
@click="operator = operators.equalTo"
>
{{
operators
.
equalTo
}}
</button>
<button
:class=
"
{ active: operator === operators.lessThan }"
:disabled="disabled"
type="button"
class="btn btn-default"
@click="operator = operators.lessThan"
>
{{
operators
.
lessThan
}}
</button>
</div>
<div
class=
"form-group"
>
<label>
{{
s__
(
'
PrometheusAlerts|Threshold
'
)
}}
</label>
<input
v-model.number=
"threshold"
:disabled=
"disabled"
type=
"number"
class=
"form-control"
/>
</div>
<div
class=
"action-group"
>
<button
ref=
"cancelButton"
:disabled=
"disabled"
type=
"button"
class=
"btn btn-default"
@
click=
"handleCancel"
>
{{
__
(
'
Cancel
'
)
}}
</button>
<button
ref=
"submitButton"
:class=
"submitButtonClass"
:disabled=
"isSubmitDisabled"
type=
"button"
class=
"btn btn-inverted"
@
click=
"handleSubmit"
>
{{
submitActionText
}}
</button>
</div>
</div>
</
template
>
ee/app/assets/javascripts/monitoring/components/dashboard_mixin.js
0 → 100644
View file @
238c8c3e
import
AlertWidget
from
'
./alert_widget.vue
'
;
export
default
{
components
:
{
AlertWidget
,
},
props
:
{
alertsEndpoint
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
methods
:
{
getGraphLabel
(
graphData
)
{
if
(
!
graphData
.
queries
||
!
graphData
.
queries
[
0
])
return
undefined
;
return
graphData
.
queries
[
0
].
label
||
graphData
.
y_label
||
'
Average
'
;
},
getQueryAlerts
(
graphData
)
{
if
(
!
graphData
.
queries
)
return
[];
return
graphData
.
queries
.
map
(
query
=>
query
.
alert_path
).
filter
(
Boolean
);
},
},
};
ee/app/assets/javascripts/monitoring/services/alerts_service.js
0 → 100644
View file @
238c8c3e
import
axios
from
'
~/lib/utils/axios_utils
'
;
export
default
class
AlertsService
{
constructor
({
alertsEndpoint
})
{
this
.
alertsEndpoint
=
alertsEndpoint
;
}
getAlerts
()
{
return
axios
.
get
(
this
.
alertsEndpoint
).
then
(
resp
=>
resp
.
data
);
}
createAlert
({
prometheus_metric_id
,
operator
,
threshold
})
{
return
axios
.
post
(
this
.
alertsEndpoint
,
{
prometheus_metric_id
,
operator
,
threshold
})
.
then
(
resp
=>
resp
.
data
);
}
// eslint-disable-next-line class-methods-use-this
readAlert
(
alertPath
)
{
return
axios
.
get
(
alertPath
).
then
(
resp
=>
resp
.
data
);
}
// eslint-disable-next-line class-methods-use-this
updateAlert
(
alertPath
,
{
operator
,
threshold
})
{
return
axios
.
put
(
alertPath
,
{
operator
,
threshold
}).
then
(
resp
=>
resp
.
data
);
}
// eslint-disable-next-line class-methods-use-this
deleteAlert
(
alertPath
)
{
return
axios
.
delete
(
alertPath
).
then
(
resp
=>
resp
.
data
);
}
}
ee/app/assets/javascripts/vue_merge_request_widget/components/codequality_issue_body.vue
View file @
238c8c3e
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
* Fixed: [name] in [link]:[line]
* Fixed: [name] in [link]:[line]
*/
*/
import
ReportLink
from
'
~/vue_shared/components/reports/report_link.vue
'
;
import
ReportLink
from
'
~/vue_shared/components/reports/report_link.vue
'
;
import
{
STATUS_SUCCESS
}
from
'
~/vue_shared/components/reports/constants
'
;
export
default
{
export
default
{
name
:
'
CodequalityIssueBody
'
,
name
:
'
CodequalityIssueBody
'
,
...
@@ -11,10 +12,9 @@ export default {
...
@@ -11,10 +12,9 @@ export default {
components
:
{
components
:
{
ReportLink
,
ReportLink
,
},
},
props
:
{
props
:
{
isStatusSucces
s
:
{
statu
s
:
{
type
:
Boolean
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
issue
:
{
issue
:
{
...
@@ -22,6 +22,11 @@ export default {
...
@@ -22,6 +22,11 @@ export default {
required
:
true
,
required
:
true
,
},
},
},
},
computed
:
{
isStatusSuccess
()
{
return
this
.
status
===
STATUS_SUCCESS
;
},
},
};
};
</
script
>
</
script
>
<
template
>
<
template
>
...
...
ee/app/assets/javascripts/vue_merge_request_widget/components/license_issue_body.vue
View file @
238c8c3e
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
export
default
{
export
default
{
name
:
'
LicenseIssueBody
'
,
props
:
{
props
:
{
issue
:
{
issue
:
{
type
:
Object
,
type
:
Object
,
...
...
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
238c8c3e
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
import
ReportSection
from
'
~/vue_shared/components/reports/report_section.vue
'
;
import
ReportSection
from
'
~/vue_shared/components/reports/report_section.vue
'
;
import
GroupedSecurityReportsApp
from
'
ee/vue_shared/security_reports/grouped_security_reports_app.vue
'
;
import
GroupedSecurityReportsApp
from
'
ee/vue_shared/security_reports/grouped_security_reports_app.vue
'
;
import
reportsMixin
from
'
ee/vue_shared/security_reports/mixins/reports_mixin
'
;
import
reportsMixin
from
'
ee/vue_shared/security_reports/mixins/reports_mixin
'
;
import
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
{
n__
,
s__
,
__
,
sprintf
}
from
'
~/locale
'
;
import
{
n__
,
s__
,
__
,
sprintf
}
from
'
~/locale
'
;
import
CEWidgetOptions
from
'
~/vue_merge_request_widget/mr_widget_options.vue
'
;
import
CEWidgetOptions
from
'
~/vue_merge_request_widget/mr_widget_options.vue
'
;
...
@@ -17,6 +18,7 @@ export default {
...
@@ -17,6 +18,7 @@ export default {
},
},
extends
:
CEWidgetOptions
,
extends
:
CEWidgetOptions
,
mixins
:
[
reportsMixin
],
mixins
:
[
reportsMixin
],
componentNames
,
data
()
{
data
()
{
return
{
return
{
isLoadingCodequality
:
false
,
isLoadingCodequality
:
false
,
...
@@ -255,8 +257,8 @@ export default {
...
@@ -255,8 +257,8 @@ export default {
:unresolved-issues=
"mr.codeclimateMetrics.newIssues"
:unresolved-issues=
"mr.codeclimateMetrics.newIssues"
:resolved-issues=
"mr.codeclimateMetrics.resolvedIssues"
:resolved-issues=
"mr.codeclimateMetrics.resolvedIssues"
:has-issues=
"hasCodequalityIssues"
:has-issues=
"hasCodequalityIssues"
:component=
"$options.componentNames.CodequalityIssueBody"
class=
"js-codequality-widget mr-widget-border-top"
class=
"js-codequality-widget mr-widget-border-top"
type=
"codequality"
/>
/>
<report-section
<report-section
v-if=
"shouldRenderPerformance"
v-if=
"shouldRenderPerformance"
...
@@ -268,8 +270,8 @@ export default {
...
@@ -268,8 +270,8 @@ export default {
:resolved-issues=
"mr.performanceMetrics.improved"
:resolved-issues=
"mr.performanceMetrics.improved"
:neutral-issues=
"mr.performanceMetrics.neutral"
:neutral-issues=
"mr.performanceMetrics.neutral"
:has-issues=
"hasPerformanceMetrics"
:has-issues=
"hasPerformanceMetrics"
:component=
"$options.componentNames.PerformanceIssueBody"
class=
"js-performance-widget mr-widget-border-top"
class=
"js-performance-widget mr-widget-border-top"
type=
"performance"
/>
/>
<grouped-security-reports-app
<grouped-security-reports-app
v-if=
"shouldRenderSecurityReport"
v-if=
"shouldRenderSecurityReport"
...
@@ -299,10 +301,10 @@ export default {
...
@@ -299,10 +301,10 @@ export default {
:loading-text=
"translateText('license management').loading"
:loading-text=
"translateText('license management').loading"
:error-text=
"translateText('license management').error"
:error-text=
"translateText('license management').error"
:success-text=
"licenseReportText"
:success-text=
"licenseReportText"
:
unresolved
-issues=
"mr.licenseReport"
:
neutral
-issues=
"mr.licenseReport"
:has-issues=
"hasLicenseReportIssues"
:has-issues=
"hasLicenseReportIssues"
:component=
"$options.componentNames.LicenseIssueBody"
class=
"js-license-report-widget mr-widget-border-top"
class=
"js-license-report-widget mr-widget-border-top"
type=
"license"
/>
/>
<div
class=
"mr-section-container"
>
<div
class=
"mr-section-container"
>
<div
class=
"mr-widget-section"
>
<div
class=
"mr-widget-section"
>
...
...
ee/app/assets/javascripts/vue_shared/components/reports/issue_body.js
0 → 100644
View file @
238c8c3e
import
{
components
as
componentsCE
,
componentNames
as
componentNamesCE
,
}
from
'
~/vue_shared/components/reports/issue_body
'
;
import
PerformanceIssueBody
from
'
ee/vue_merge_request_widget/components/performance_issue_body.vue
'
;
import
CodequalityIssueBody
from
'
ee/vue_merge_request_widget/components/codequality_issue_body.vue
'
;
import
LicenseIssueBody
from
'
ee/vue_merge_request_widget/components/license_issue_body.vue
'
;
import
SastIssueBody
from
'
ee/vue_shared/security_reports/components/sast_issue_body.vue
'
;
import
SastContainerIssueBody
from
'
ee/vue_shared/security_reports/components/sast_container_issue_body.vue
'
;
import
DastIssueBody
from
'
ee/vue_shared/security_reports/components/dast_issue_body.vue
'
;
export
const
components
=
{
...
componentsCE
,
PerformanceIssueBody
,
CodequalityIssueBody
,
LicenseIssueBody
,
SastContainerIssueBody
,
SastIssueBody
,
DastIssueBody
,
};
export
const
componentNames
=
{
...
componentNamesCE
,
PerformanceIssueBody
:
PerformanceIssueBody
.
name
,
CodequalityIssueBody
:
CodequalityIssueBody
.
name
,
LicenseIssueBody
:
LicenseIssueBody
.
name
,
SastContainerIssueBody
:
SastContainerIssueBody
.
name
,
SastIssueBody
:
SastIssueBody
.
name
,
DastIssueBody
:
DastIssueBody
.
name
,
};
ee/app/assets/javascripts/vue_shared/security_reports/components/dast_issue_body.vue
View file @
238c8c3e
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
import
ModalOpenName
from
'
~/vue_shared/components/reports/modal_open_name.vue
'
;
import
ModalOpenName
from
'
~/vue_shared/components/reports/modal_open_name.vue
'
;
export
default
{
export
default
{
name
:
'
S
astIssueBody
'
,
name
:
'
D
astIssueBody
'
,
components
:
{
components
:
{
ModalOpenName
,
ModalOpenName
,
},
},
...
@@ -16,11 +16,6 @@ export default {
...
@@ -16,11 +16,6 @@ export default {
type
:
Object
,
type
:
Object
,
required
:
true
,
required
:
true
,
},
},
issueIndex
:
{
type
:
Number
,
required
:
true
,
},
// failed || success
// failed || success
status
:
{
status
:
{
type
:
String
,
type
:
String
,
...
...
ee/app/assets/javascripts/vue_shared/security_reports/grouped_security_reports_app.vue
View file @
238c8c3e
...
@@ -3,8 +3,8 @@ import { mapActions, mapState, mapGetters } from 'vuex';
...
@@ -3,8 +3,8 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import
ReportSection
from
'
~/vue_shared/components/reports/report_section.vue
'
;
import
ReportSection
from
'
~/vue_shared/components/reports/report_section.vue
'
;
import
SummaryRow
from
'
~/vue_shared/components/reports/summary_row.vue
'
;
import
SummaryRow
from
'
~/vue_shared/components/reports/summary_row.vue
'
;
import
IssuesList
from
'
~/vue_shared/components/reports/issues_list.vue
'
;
import
IssuesList
from
'
~/vue_shared/components/reports/issues_list.vue
'
;
import
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
IssueModal
from
'
./components/modal.vue
'
;
import
IssueModal
from
'
./components/modal.vue
'
;
import
{
SAST
,
DAST
,
SAST_CONTAINER
}
from
'
./store/constants
'
;
import
securityReportsMixin
from
'
./mixins/security_report_mixin
'
;
import
securityReportsMixin
from
'
./mixins/security_report_mixin
'
;
import
createStore
from
'
./store
'
;
import
createStore
from
'
./store
'
;
...
@@ -111,9 +111,7 @@ export default {
...
@@ -111,9 +111,7 @@ export default {
required
:
true
,
required
:
true
,
},
},
},
},
sast
:
SAST
,
componentNames
,
dast
:
DAST
,
sastContainer
:
SAST_CONTAINER
,
computed
:
{
computed
:
{
...
mapState
([
'
sast
'
,
'
sastContainer
'
,
'
dast
'
,
'
dependencyScanning
'
,
'
summaryCounts
'
]),
...
mapState
([
'
sast
'
,
'
sastContainer
'
,
'
dast
'
,
'
dependencyScanning
'
,
'
summaryCounts
'
]),
...
mapGetters
([
...
mapGetters
([
...
@@ -229,7 +227,7 @@ export default {
...
@@ -229,7 +227,7 @@ export default {
:unresolved-issues=
"sast.newIssues"
:unresolved-issues=
"sast.newIssues"
:resolved-issues=
"sast.resolvedIssues"
:resolved-issues=
"sast.resolvedIssues"
:all-issues=
"sast.allIssues"
:all-issues=
"sast.allIssues"
:
type=
"$options.sast
"
:
component=
"$options.componentNames.SastIssueBody
"
class=
"js-sast-issue-list report-block-group-list"
class=
"js-sast-issue-list report-block-group-list"
/>
/>
</
template
>
</
template
>
...
@@ -248,7 +246,7 @@ export default {
...
@@ -248,7 +246,7 @@ export default {
:unresolved-issues=
"dependencyScanning.newIssues"
:unresolved-issues=
"dependencyScanning.newIssues"
:resolved-issues=
"dependencyScanning.resolvedIssues"
:resolved-issues=
"dependencyScanning.resolvedIssues"
:all-issues=
"dependencyScanning.allIssues"
:all-issues=
"dependencyScanning.allIssues"
:
type=
"$options.sast
"
:
component=
"$options.componentNames.SastIssueBody
"
class=
"js-dss-issue-list report-block-group-list"
class=
"js-dss-issue-list report-block-group-list"
/>
/>
</
template
>
</
template
>
...
@@ -265,7 +263,7 @@ export default {
...
@@ -265,7 +263,7 @@ export default {
v-if=
"sastContainer.newIssues.length || sastContainer.resolvedIssues.length"
v-if=
"sastContainer.newIssues.length || sastContainer.resolvedIssues.length"
:unresolved-issues=
"sastContainer.newIssues"
:unresolved-issues=
"sastContainer.newIssues"
:neutral-issues=
"sastContainer.resolvedIssues"
:neutral-issues=
"sastContainer.resolvedIssues"
:
type=
"$options.sastContainer
"
:
component=
"$options.componentNames.SastContainerIssueBody
"
class=
"report-block-group-list"
class=
"report-block-group-list"
/>
/>
</
template
>
</
template
>
...
@@ -282,7 +280,7 @@ export default {
...
@@ -282,7 +280,7 @@ export default {
v-if=
"dast.newIssues.length || dast.resolvedIssues.length"
v-if=
"dast.newIssues.length || dast.resolvedIssues.length"
:unresolved-issues=
"dast.newIssues"
:unresolved-issues=
"dast.newIssues"
:resolved-issues=
"dast.resolvedIssues"
:resolved-issues=
"dast.resolvedIssues"
:
type=
"$options.dast
"
:
component=
"$options.componentNames.DastIssueBody
"
class=
"report-block-group-list"
class=
"report-block-group-list"
/>
/>
</
template
>
</
template
>
...
...
ee/app/assets/javascripts/vue_shared/security_reports/split_security_reports_app.vue
View file @
238c8c3e
...
@@ -3,7 +3,7 @@ import { mapActions, mapState } from 'vuex';
...
@@ -3,7 +3,7 @@ import { mapActions, mapState } from 'vuex';
import
{
s__
,
sprintf
,
n__
}
from
'
~/locale
'
;
import
{
s__
,
sprintf
,
n__
}
from
'
~/locale
'
;
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
import
ReportSection
from
'
~/vue_shared/components/reports/report_section.vue
'
;
import
ReportSection
from
'
~/vue_shared/components/reports/report_section.vue
'
;
import
{
SAST
,
DAST
,
SAST_CONTAINER
}
from
'
./store/constants
'
;
import
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
IssueModal
from
'
./components/modal.vue
'
;
import
IssueModal
from
'
./components/modal.vue
'
;
import
mixin
from
'
./mixins/security_report_mixin
'
;
import
mixin
from
'
./mixins/security_report_mixin
'
;
import
reportsMixin
from
'
./mixins/reports_mixin
'
;
import
reportsMixin
from
'
./mixins/reports_mixin
'
;
...
@@ -88,9 +88,7 @@ export default {
...
@@ -88,9 +88,7 @@ export default {
required
:
true
,
required
:
true
,
},
},
},
},
sast
:
SAST
,
componentNames
,
dast
:
DAST
,
sastContainer
:
SAST_CONTAINER
,
computed
:
{
computed
:
{
...
mapState
([
'
sast
'
,
'
dependencyScanning
'
,
'
sastContainer
'
,
'
dast
'
]),
...
mapState
([
'
sast
'
,
'
dependencyScanning
'
,
'
sastContainer
'
,
'
dast
'
]),
...
@@ -216,7 +214,7 @@ export default {
...
@@ -216,7 +214,7 @@ export default {
<report-section
<report-section
v-if=
"sastHeadPath"
v-if=
"sastHeadPath"
:always-open=
"alwaysOpen"
:always-open=
"alwaysOpen"
:
type=
"$options.sast
"
:
component=
"$options.componentNames.SastIssueBody
"
:status=
"checkReportStatus(sast.isLoading, sast.hasError)"
:status=
"checkReportStatus(sast.isLoading, sast.hasError)"
:loading-text=
"translateText('SAST').loading"
:loading-text=
"translateText('SAST').loading"
:error-text=
"translateText('SAST').error"
:error-text=
"translateText('SAST').error"
...
@@ -230,7 +228,7 @@ export default {
...
@@ -230,7 +228,7 @@ export default {
<report-section
<report-section
v-if=
"dependencyScanningHeadPath"
v-if=
"dependencyScanningHeadPath"
:always-open=
"alwaysOpen"
:always-open=
"alwaysOpen"
:
type=
"$options.sast
"
:
component=
"$options.componentNames.SastIssueBody
"
:status=
"checkReportStatus(dependencyScanning.isLoading, dependencyScanning.hasError)"
:status=
"checkReportStatus(dependencyScanning.isLoading, dependencyScanning.hasError)"
:loading-text=
"translateText('Dependency scanning').loading"
:loading-text=
"translateText('Dependency scanning').loading"
:error-text=
"translateText('Dependency scanning').error"
:error-text=
"translateText('Dependency scanning').error"
...
@@ -244,7 +242,7 @@ export default {
...
@@ -244,7 +242,7 @@ export default {
<report-section
<report-section
v-if=
"sastContainerHeadPath"
v-if=
"sastContainerHeadPath"
:always-open=
"alwaysOpen"
:always-open=
"alwaysOpen"
:
type=
"$options.sastContainer
"
:
component=
"$options.componentNames.SastContainerIssueBody
"
:status=
"checkReportStatus(sastContainer.isLoading, sastContainer.hasError)"
:status=
"checkReportStatus(sastContainer.isLoading, sastContainer.hasError)"
:loading-text=
"translateText('Container scanning').loading"
:loading-text=
"translateText('Container scanning').loading"
:error-text=
"translateText('Container scanning').error"
:error-text=
"translateText('Container scanning').error"
...
@@ -258,7 +256,7 @@ export default {
...
@@ -258,7 +256,7 @@ export default {
<report-section
<report-section
v-if=
"dastHeadPath"
v-if=
"dastHeadPath"
:always-open=
"alwaysOpen"
:always-open=
"alwaysOpen"
:
type=
"$options.dast
"
:
component=
"$options.componentNames.DastIssueBody
"
:status=
"checkReportStatus(dast.isLoading, dast.hasError)"
:status=
"checkReportStatus(dast.isLoading, dast.hasError)"
:loading-text=
"translateText('DAST').loading"
:loading-text=
"translateText('DAST').loading"
:error-text=
"translateText('DAST').error"
:error-text=
"translateText('DAST').error"
...
...
ee/app/controllers/ee/projects/prometheus/metrics_controller.rb
View file @
238c8c3e
...
@@ -47,7 +47,7 @@ module EE
...
@@ -47,7 +47,7 @@ module EE
def
update
def
update
@metric
=
project
.
prometheus_metrics
.
find
(
params
[
:id
])
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@metric
=
project
.
prometheus_metrics
.
find
(
params
[
:id
])
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@metric
.
update
(
metrics_params
)
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@metric
=
update_metrics_service
(
@metric
).
execute
# rubocop:disable Gitlab/ModuleWithInstanceVariables
if
@metric
.
persisted?
# rubocop:disable Gitlab/ModuleWithInstanceVariables
if
@metric
.
persisted?
# rubocop:disable Gitlab/ModuleWithInstanceVariables
redirect_to
edit_project_service_path
(
project
,
::
PrometheusService
),
redirect_to
edit_project_service_path
(
project
,
::
PrometheusService
),
...
@@ -63,7 +63,7 @@ module EE
...
@@ -63,7 +63,7 @@ module EE
def
destroy
def
destroy
metric
=
project
.
prometheus_metrics
.
find
(
params
[
:id
])
metric
=
project
.
prometheus_metrics
.
find
(
params
[
:id
])
metric
.
destroy
destroy_metrics_service
(
metric
).
execute
respond_to
do
|
format
|
respond_to
do
|
format
|
format
.
html
do
format
.
html
do
...
@@ -77,6 +77,14 @@ module EE
...
@@ -77,6 +77,14 @@ module EE
private
private
def
update_metrics_service
(
metric
)
::
Projects
::
Prometheus
::
Metrics
::
UpdateService
.
new
(
metric
,
metrics_params
)
end
def
destroy_metrics_service
(
metric
)
::
Projects
::
Prometheus
::
Metrics
::
DestroyService
.
new
(
metric
)
end
def
metrics_params
def
metrics_params
params
.
require
(
:prometheus_metric
).
permit
(
:title
,
:query
,
:y_label
,
:unit
,
:legend
,
:group
)
params
.
require
(
:prometheus_metric
).
permit
(
:title
,
:query
,
:y_label
,
:unit
,
:legend
,
:group
)
end
end
...
...
ee/app/controllers/projects/prometheus/alerts_controller.rb
0 → 100644
View file @
238c8c3e
module
Projects
module
Prometheus
class
AlertsController
<
Projects
::
ApplicationController
respond_to
:json
protect_from_forgery
except:
[
:notify
]
before_action
:authorize_read_prometheus_alerts!
,
except:
[
:notify
]
before_action
:authorize_admin_project!
,
except:
[
:notify
]
before_action
:alert
,
only:
[
:update
,
:show
,
:destroy
]
def
index
alerts
=
project
.
prometheus_alerts
.
reorder
(
id: :asc
)
render
json:
serialize_as_json
(
alerts
)
end
def
show
render
json:
serialize_as_json
(
alert
)
end
def
notify
NotificationService
.
new
.
async
.
prometheus_alerts_fired
(
project
,
params
[
"alerts"
])
head
:ok
end
def
create
@alert
=
project
.
prometheus_alerts
.
create
(
alerts_params
)
if
@alert
schedule_prometheus_update!
render
json:
serialize_as_json
(
@alert
)
else
head
:no_content
end
end
def
update
if
alert
.
update
(
alerts_params
)
schedule_prometheus_update!
render
json:
serialize_as_json
(
alert
)
else
head
:no_content
end
end
def
destroy
if
alert
.
destroy
schedule_prometheus_update!
head
:ok
else
head
:no_content
end
end
private
def
alerts_params
alerts_params
=
params
.
permit
(
:operator
,
:threshold
,
:environment_id
,
:prometheus_metric_id
)
if
alerts_params
[
:operator
].
present?
alerts_params
[
:operator
]
=
PrometheusAlert
.
operator_to_enum
(
alerts_params
[
:operator
])
end
alerts_params
end
def
schedule_prometheus_update!
::
Clusters
::
Applications
::
ScheduleUpdateService
.
new
(
application
,
project
).
execute
end
def
serialize_as_json
(
alert_obj
)
serializer
.
represent
(
alert_obj
)
end
def
serializer
PrometheusAlertSerializer
.
new
(
project:
project
,
current_user:
current_user
)
end
def
alert
@alert
||=
project
.
prometheus_alerts
.
find_by
(
prometheus_metric:
params
[
:id
])
||
render_404
end
def
application
@application
||=
alert
.
environment
.
cluster_prometheus_adapter
end
end
end
end
ee/app/helpers/ee/environments_helper.rb
0 → 100644
View file @
238c8c3e
module
EE
module
EnvironmentsHelper
def
metrics_data
(
project
,
environment
)
ee_metrics_data
=
{
"alerts-endpoint"
=>
project_prometheus_alerts_path
(
project
,
environment_id:
environment
.
id
,
format: :json
),
"prometheus-alerts-available"
=>
"
#{
can?
(
current_user
,
:read_prometheus_alerts
,
project
)
}
"
}
super
.
merge
(
ee_metrics_data
)
end
end
end
ee/app/mailers/emails/ee/projects.rb
View file @
238c8c3e
...
@@ -17,6 +17,25 @@ module Emails
...
@@ -17,6 +17,25 @@ module Emails
mail
(
to:
new_mirror_user
.
notification_email
,
mail
(
to:
new_mirror_user
.
notification_email
,
subject:
subject
(
'Mirror user changed'
))
subject:
subject
(
'Mirror user changed'
))
end
end
def
prometheus_alert_fired_email
(
project_id
,
user_id
,
alert_params
)
alert_metric_id
=
alert_params
[
"labels"
][
"gitlab_alert_id"
]
@project
=
Project
.
find_by
(
id:
project_id
)
return
unless
@project
@alert
=
@project
.
prometheus_alerts
.
find_by
(
prometheus_metric:
alert_metric_id
)
return
unless
@alert
@environment
=
@alert
.
environment
user
=
User
.
find_by
(
id:
user_id
)
return
unless
user
subject_text
=
"Alert:
#{
@environment
.
name
}
-
#{
@alert
.
title
}
#{
@alert
.
computed_operator
}
#{
@alert
.
threshold
}
for 5 minutes"
mail
(
to:
user
.
notification_email
,
subject:
subject
(
subject_text
))
end
end
end
end
end
end
end
ee/app/models/concerns/ee/clusters/application_status.rb
0 → 100644
View file @
238c8c3e
module
EE
module
Clusters
module
ApplicationStatus
extend
ActiveSupport
::
Concern
prepended
do
state_machine
:status
,
initial: :not_installable
do
state
:updating
,
value:
4
state
:updated
,
value:
5
state
:update_errored
,
value:
6
event
:make_updating
do
transition
[
:installed
,
:updated
,
:update_errored
]
=>
:updating
end
event
:make_updated
do
transition
[
:updating
]
=>
:updated
end
event
:make_update_errored
do
transition
any
=>
:update_errored
end
before_transition
any
=>
[
:updating
]
do
|
app_status
,
_
|
app_status
.
status_reason
=
nil
end
before_transition
any
=>
[
:update_errored
]
do
|
app_status
,
transition
|
status_reason
=
transition
.
args
.
first
app_status
.
status_reason
=
status_reason
if
status_reason
end
end
end
end
end
end
ee/app/models/concerns/ee/prometheus_adapter.rb
0 → 100644
View file @
238c8c3e
module
EE
module
PrometheusAdapter
extend
::
Gitlab
::
Utils
::
Override
def
clear_prometheus_reactive_cache!
(
query_name
,
*
args
)
query_class
=
query_klass_for
(
query_name
)
query_args
=
build_query_args
(
*
args
)
clear_reactive_cache!
(
query_class
.
name
,
*
query_args
)
end
private
override
:build_query_args
def
build_query_args
(
*
args
)
args
.
map
do
|
arg
|
arg
.
respond_to?
(
:id
)
?
arg
.
id
:
arg
end
end
end
end
ee/app/models/ee/clusters/applications/prometheus.rb
0 → 100644
View file @
238c8c3e
module
EE
module
Clusters
module
Applications
module
Prometheus
extend
ActiveSupport
::
Concern
prepended
do
state_machine
:status
do
after_transition
any
=>
:updating
do
|
application
|
application
.
update
(
last_update_started_at:
Time
.
now
)
end
end
end
def
ready_status
super
+
[
:updating
,
:updated
,
:update_errored
]
end
def
updated_since?
(
timestamp
)
last_update_started_at
&&
last_update_started_at
>
timestamp
&&
!
update_errored?
end
def
update_in_progress?
status_name
==
:updating
end
def
update_errored?
status_name
==
:update_errored
end
def
get_command
::
Gitlab
::
Kubernetes
::
Helm
::
GetCommand
.
new
(
name
)
end
def
upgrade_command
(
values
)
::
Gitlab
::
Kubernetes
::
Helm
::
UpgradeCommand
.
new
(
name
,
chart:
chart
,
version:
version
,
values:
values
)
end
end
end
end
end
ee/app/models/ee/environment.rb
View file @
238c8c3e
module
EE
module
EE
module
Environment
module
Environment
extend
ActiveSupport
::
Concern
prepended
do
has_many
:prometheus_alerts
,
inverse_of: :environment
end
def
pod_names
def
pod_names
return
[]
unless
rollout_status
return
[]
unless
rollout_status
...
@@ -7,5 +13,13 @@ module EE
...
@@ -7,5 +13,13 @@ module EE
instance
[
:pod_name
]
instance
[
:pod_name
]
end
end
end
end
def
clear_prometheus_reactive_cache!
(
query_name
)
cluster_prometheus_adapter
&
.
clear_prometheus_reactive_cache!
(
query_name
,
self
)
end
def
cluster_prometheus_adapter
@cluster_prometheus_adapter
||=
Prometheus
::
AdapterService
.
new
(
project
,
deployment_platform
).
cluster_prometheus_adapter
end
end
end
end
end
ee/app/models/ee/project.rb
View file @
238c8c3e
...
@@ -39,6 +39,8 @@ module EE
...
@@ -39,6 +39,8 @@ module EE
has_many
:source_pipelines
,
class_name:
'Ci::Sources::Pipeline'
,
foreign_key: :project_id
has_many
:source_pipelines
,
class_name:
'Ci::Sources::Pipeline'
,
foreign_key: :project_id
has_many
:prometheus_alerts
,
inverse_of: :project
scope
:with_shared_runners_limit_enabled
,
->
{
with_shared_runners
.
non_public_only
}
scope
:with_shared_runners_limit_enabled
,
->
{
with_shared_runners
.
non_public_only
}
scope
:mirror
,
->
{
where
(
mirror:
true
)
}
scope
:mirror
,
->
{
where
(
mirror:
true
)
}
...
@@ -99,6 +101,12 @@ module EE
...
@@ -99,6 +101,12 @@ module EE
pipelines
.
newest_first
(
default_branch
).
with_security_reports
.
first
pipelines
.
newest_first
(
default_branch
).
with_security_reports
.
first
end
end
def
environments_for_scope
(
scope
)
quoted_scope
=
::
Gitlab
::
SQL
::
Glob
.
q
(
scope
)
environments
.
where
(
"name LIKE (
#{
::
Gitlab
::
SQL
::
Glob
.
to_like
(
quoted_scope
)
}
)"
)
# rubocop:disable GitlabSecurity/SqlInjection
end
def
ensure_external_webhook_token
def
ensure_external_webhook_token
return
if
external_webhook_token
.
present?
return
if
external_webhook_token
.
present?
...
...
ee/app/models/license.rb
View file @
238c8c3e
...
@@ -75,6 +75,7 @@ class License < ActiveRecord::Base
...
@@ -75,6 +75,7 @@ class License < ActiveRecord::Base
chatops
chatops
pod_logs
pod_logs
pseudonymizer
pseudonymizer
prometheus_alerts
]
.
freeze
]
.
freeze
# List all features available for early adopters,
# List all features available for early adopters,
...
...
ee/app/models/prometheus_alert.rb
0 → 100644
View file @
238c8c3e
class
PrometheusAlert
<
ActiveRecord
::
Base
OPERATORS_MAP
=
{
lt:
"<"
,
eq:
"="
,
gt:
">"
}.
freeze
belongs_to
:environment
,
required:
true
,
validate:
true
,
inverse_of: :prometheus_alerts
belongs_to
:project
,
required:
true
,
validate:
true
,
inverse_of: :prometheus_alerts
belongs_to
:prometheus_metric
,
required:
true
,
validate:
true
,
inverse_of: :prometheus_alert
after_save
:clear_prometheus_adapter_cache!
after_destroy
:clear_prometheus_adapter_cache!
enum
operator:
[
:lt
,
:eq
,
:gt
]
delegate
:title
,
:query
,
to: :prometheus_metric
def
self
.
operator_to_enum
(
op
)
OPERATORS_MAP
.
invert
.
fetch
(
op
)
end
def
full_query
"
#{
query
}
#{
computed_operator
}
#{
threshold
}
"
end
def
computed_operator
OPERATORS_MAP
.
fetch
(
operator
.
to_sym
)
end
def
to_param
{
"alert"
=>
title
,
"expr"
=>
full_query
,
"for"
=>
"5m"
,
"labels"
=>
{
"gitlab"
=>
"hook"
,
"gitlab_alert_id"
=>
prometheus_metric_id
}
}
end
private
def
clear_prometheus_adapter_cache!
environment
.
clear_prometheus_reactive_cache!
(
:additional_metrics_environment
)
end
end
ee/app/models/prometheus_metric.rb
View file @
238c8c3e
class
PrometheusMetric
<
ActiveRecord
::
Base
class
PrometheusMetric
<
ActiveRecord
::
Base
belongs_to
:project
,
required:
true
,
validate:
true
,
inverse_of: :prometheus_metrics
belongs_to
:project
,
required:
true
,
validate:
true
,
inverse_of: :prometheus_metrics
has_one
:prometheus_alert
,
inverse_of: :prometheus_metric
enum
group:
[
:business
,
:response
,
:system
]
enum
group:
[
:business
,
:response
,
:system
]
validates
:title
,
presence:
true
validates
:title
,
presence:
true
...
@@ -19,7 +22,7 @@ class PrometheusMetric < ActiveRecord::Base
...
@@ -19,7 +22,7 @@ class PrometheusMetric < ActiveRecord::Base
end
end
def
to_query_metric
def
to_query_metric
Gitlab
::
Prometheus
::
Metric
.
new
(
title:
title
,
required_metrics:
[],
weight:
0
,
y_label:
y_label
,
queries:
build_queries
)
Gitlab
::
Prometheus
::
Metric
.
new
(
id:
id
,
title:
title
,
required_metrics:
[],
weight:
0
,
y_label:
y_label
,
queries:
build_queries
)
end
end
private
private
...
...
ee/app/policies/ee/project_policy.rb
View file @
238c8c3e
...
@@ -49,6 +49,10 @@ module EE
...
@@ -49,6 +49,10 @@ module EE
@subject
.
feature_available?
(
:pod_logs
,
@user
)
@subject
.
feature_available?
(
:pod_logs
,
@user
)
end
end
condition
(
:prometheus_alerts_enabled
)
do
@subject
.
feature_available?
(
:prometheus_alerts
,
@user
)
end
rule
{
admin
}.
enable
:change_repository_storage
rule
{
admin
}.
enable
:change_repository_storage
rule
{
support_bot
}.
enable
:guest_access
rule
{
support_bot
}.
enable
:guest_access
...
@@ -97,6 +101,7 @@ module EE
...
@@ -97,6 +101,7 @@ module EE
end
end
rule
{
pod_logs_enabled
&
can?
(
:maintainer_access
)
}.
enable
:read_pod_logs
rule
{
pod_logs_enabled
&
can?
(
:maintainer_access
)
}.
enable
:read_pod_logs
rule
{
prometheus_alerts_enabled
&
can?
(
:maintainer_access
)
}.
enable
:read_prometheus_alerts
rule
{
auditor
}.
policy
do
rule
{
auditor
}.
policy
do
enable
:public_user_access
enable
:public_user_access
...
...
ee/app/serializers/prometheus_alert_entity.rb
0 → 100644
View file @
238c8c3e
class
PrometheusAlertEntity
<
Grape
::
Entity
include
RequestAwareEntity
expose
:id
expose
:title
expose
:query
expose
:threshold
expose
:operator
do
|
prometheus_alert
|
prometheus_alert
.
computed_operator
end
expose
:alert_path
do
|
prometheus_alert
|
project_prometheus_alert_path
(
prometheus_alert
.
project
,
prometheus_alert
.
prometheus_metric_id
,
environment_id:
prometheus_alert
.
environment
.
id
,
format: :json
)
end
private
alias_method
:prometheus_alert
,
:object
def
can_read_prometheus_alerts?
can?
(
request
.
current_user
,
:read_prometheus_alerts
,
prometheus_alert
.
project
)
end
end
ee/app/serializers/prometheus_alert_serializer.rb
0 → 100644
View file @
238c8c3e
class
PrometheusAlertSerializer
<
BaseSerializer
entity
PrometheusAlertEntity
end
ee/app/services/clusters/applications/check_upgrade_progress_service.rb
0 → 100644
View file @
238c8c3e
module
Clusters
module
Applications
class
CheckUpgradeProgressService
<
BaseHelmService
def
execute
return
unless
app
.
updating?
case
phase
when
::
Gitlab
::
Kubernetes
::
Pod
::
SUCCEEDED
on_success
when
::
Gitlab
::
Kubernetes
::
Pod
::
FAILED
on_failed
else
check_timeout
end
rescue
::
Kubeclient
::
HttpError
=>
e
app
.
make_update_errored!
(
"Kubernetes error:
#{
e
.
message
}
"
)
unless
app
.
update_errored?
end
private
def
on_success
app
.
make_updated!
ensure
remove_pod
end
def
on_failed
app
.
make_update_errored!
(
errors
||
'Update silently failed'
)
ensure
remove_pod
end
def
check_timeout
if
timeouted?
begin
app
.
make_update_errored!
(
'Update timed out'
)
ensure
remove_pod
end
else
::
ClusterWaitForAppUpdateWorker
.
perform_in
(
::
ClusterWaitForAppUpdateWorker
::
INTERVAL
,
app
.
name
,
app
.
id
)
end
end
def
timeouted?
Time
.
now
.
utc
-
app
.
updated_at
.
to_time
.
utc
>
::
ClusterWaitForAppUpdateWorker
::
TIMEOUT
end
def
remove_pod
helm_api
.
delete_pod!
(
upgrade_command
.
pod_name
)
rescue
# no-op
end
def
phase
helm_api
.
status
(
upgrade_command
.
pod_name
)
end
def
errors
helm_api
.
log
(
upgrade_command
.
pod_name
)
end
end
end
end
ee/app/services/clusters/applications/prometheus_update_service.rb
0 → 100644
View file @
238c8c3e
module
Clusters
module
Applications
class
PrometheusUpdateService
<
BaseHelmService
attr_accessor
:project
def
initialize
(
app
,
project
)
super
(
app
)
@project
=
project
end
def
execute
app
.
make_updating!
response
=
helm_api
.
get_config_map
(
app
.
get_command
)
config
=
extract_config
(
response
)
data
=
if
has_alerts?
generate_alert_manager
(
config
)
else
reset_alert_manager
(
config
)
end
helm_api
.
update
(
upgrade_command
(
data
.
to_yaml
))
::
ClusterWaitForAppUpdateWorker
.
perform_in
(
::
ClusterWaitForAppUpdateWorker
::
INTERVAL
,
app
.
name
,
app
.
id
)
rescue
::
Kubeclient
::
HttpError
=>
ke
app
.
make_update_errored!
(
"Kubernetes error:
#{
ke
.
message
}
"
)
rescue
StandardError
=>
e
app
.
make_update_errored!
(
e
.
message
)
end
private
def
reset_alert_manager
(
config
)
config
=
set_alert_manager_enabled
(
config
,
false
)
config
.
delete
(
"alertmanagerFiles"
)
config
[
"serverFiles"
][
"alerts"
]
=
{}
config
end
def
generate_alert_manager
(
config
)
config
=
set_alert_manager_enabled
(
config
,
true
)
config
=
set_alert_manager_files
(
config
)
set_alert_manager_groups
(
config
)
end
def
set_alert_manager_enabled
(
config
,
enabled
)
config
[
"alertmanager"
][
"enabled"
]
=
enabled
config
end
def
set_alert_manager_files
(
config
)
config
[
"alertmanagerFiles"
]
=
{
"alertmanager.yml"
=>
{
"receivers"
=>
alert_manager_receivers_params
,
"route"
=>
alert_manager_route_params
}
}
config
end
def
set_alert_manager_groups
(
config
)
config
[
"serverFiles"
][
"alerts"
][
"groups"
]
||=
[]
environments_with_alerts
.
each
do
|
env_name
,
alerts
|
index
=
config
[
"serverFiles"
][
"alerts"
][
"groups"
].
find_index
do
|
group
|
group
[
"name"
]
==
env_name
end
if
index
config
[
"serverFiles"
][
"alerts"
][
"groups"
][
index
][
"rules"
]
=
alerts
else
config
[
"serverFiles"
][
"alerts"
][
"groups"
]
<<
{
"name"
=>
env_name
,
"rules"
=>
alerts
}
end
end
config
end
def
alert_manager_receivers_params
[
{
"name"
=>
"gitlab"
,
"webhook_configs"
=>
[
{
"url"
=>
notify_url
,
"send_resolved"
=>
false
}
]
}
]
end
def
alert_manager_route_params
{
"receiver"
=>
"gitlab"
,
"group_wait"
=>
"30s"
,
"group_interval"
=>
"5m"
,
"repeat_interval"
=>
"4h"
}
end
def
notify_url
::
Gitlab
::
Routing
.
url_helpers
.
notify_namespace_project_prometheus_alerts_url
(
namespace_id:
project
.
namespace
.
path
,
project_id:
project
.
path
,
format: :json
)
end
def
extract_config
(
response
)
YAML
.
safe_load
(
response
.
data
.
values
)
end
def
has_alerts?
environments_with_alerts
.
values
.
flatten
.
any?
end
def
environments_with_alerts
@environments_with_alerts
||=
environments
.
each_with_object
({})
do
|
environment
,
hsh
|
name
=
rule_name
(
environment
)
hsh
[
name
]
=
environment
.
prometheus_alerts
.
map
(
&
:to_param
)
end
end
def
rule_name
(
environment
)
"
#{
environment
.
name
}
.rules"
end
def
environments
project
.
environments_for_scope
(
cluster
.
environment_scope
)
end
end
end
end
ee/app/services/clusters/applications/schedule_update_service.rb
0 → 100644
View file @
238c8c3e
module
Clusters
module
Applications
class
ScheduleUpdateService
BACKOFF_DELAY
=
2
.
minutes
attr_accessor
:application
,
:project
def
initialize
(
application
,
project
)
@application
=
application
@project
=
project
end
def
execute
return
unless
application
if
recently_scheduled?
worker_class
.
perform_in
(
BACKOFF_DELAY
,
application
.
name
,
application
.
id
,
project
.
id
,
Time
.
now
)
else
worker_class
.
perform_async
(
application
.
name
,
application
.
id
,
project
.
id
,
Time
.
now
)
end
end
private
def
worker_class
::
ClusterUpdateAppWorker
end
def
recently_scheduled?
return
false
unless
application
.
last_update_started_at
application
.
last_update_started_at
.
utc
>=
Time
.
now
.
utc
-
BACKOFF_DELAY
end
end
end
end
ee/app/services/ee/clusters/applications/base_helm_service.rb
0 → 100644
View file @
238c8c3e
module
EE
module
Clusters
module
Applications
module
BaseHelmService
protected
def
upgrade_command
(
new_values
=
""
)
@upgrade_command
||=
app
.
upgrade_command
(
new_values
)
end
end
end
end
end
ee/app/services/ee/notification_service.rb
View file @
238c8c3e
...
@@ -42,6 +42,18 @@ module EE
...
@@ -42,6 +42,18 @@ module EE
mailer
.
project_mirror_user_changed_email
(
new_mirror_user
.
id
,
deleted_user_name
,
project
.
id
).
deliver_later
mailer
.
project_mirror_user_changed_email
(
new_mirror_user
.
id
,
deleted_user_name
,
project
.
id
).
deliver_later
end
end
def
prometheus_alerts_fired
(
project
,
alerts
)
recipients
=
project
.
members
.
active_without_invites_and_requests
.
owners_and_masters
if
recipients
.
empty?
&&
project
.
group
recipients
=
project
.
group
.
members
.
active_without_invites_and_requests
.
owners_and_masters
end
recipients
.
product
(
alerts
).
each
do
|
recipient
,
alert
|
mailer
.
prometheus_alert_fired_email
(
project
.
id
,
recipient
.
user
.
id
,
alert
).
deliver_later
end
end
private
private
def
add_mr_approvers_email
(
merge_request
,
approvers
,
current_user
)
def
add_mr_approvers_email
(
merge_request
,
approvers
,
current_user
)
...
...
ee/app/services/projects/prometheus/metrics/base_service.rb
0 → 100644
View file @
238c8c3e
module
Projects
module
Prometheus
module
Metrics
class
BaseService
def
initialize
(
metric
,
params
=
{})
@metric
=
metric
@project
=
metric
.
project
@params
=
params
.
dup
end
protected
attr_reader
:metric
,
:project
,
:params
def
application
metric
.
prometheus_alert
.
environment
.
cluster_prometheus_adapter
end
def
schedule_alert_update
::
Clusters
::
Applications
::
ScheduleUpdateService
.
new
(
application
,
project
).
execute
end
def
has_alert?
metric
.
prometheus_alert
.
present?
end
end
end
end
end
ee/app/services/projects/prometheus/metrics/destroy_service.rb
0 → 100644
View file @
238c8c3e
module
Projects
module
Prometheus
module
Metrics
class
DestroyService
<
Metrics
::
BaseService
def
execute
schedule_alert_update
if
has_alert?
metric
.
destroy
end
end
end
end
end
ee/app/services/projects/prometheus/metrics/update_service.rb
0 → 100644
View file @
238c8c3e
module
Projects
module
Prometheus
module
Metrics
class
UpdateService
<
Metrics
::
BaseService
def
execute
metric
.
update!
(
params
)
schedule_alert_update
if
requires_alert_update?
metric
end
private
def
requires_alert_update?
has_alert?
&&
(
changing_title?
||
changing_query?
)
end
def
changing_title?
metric
.
previous_changes
.
include?
(
:title
)
end
def
changing_query?
metric
.
previous_changes
.
include?
(
:query
)
end
end
end
end
end
ee/app/views/notify/prometheus_alert_fired_email.html.haml
0 → 100644
View file @
238c8c3e
%p
An alert has been triggered in
#{
@project
.
full_path
}
.
%p
Environment:
#{
@environment
.
name
}
%p
Metric:
%pre
=
@alert
.
full_query
%p
=
link_to
(
"View
#{
@environment
.
name
}
performance dashboard."
,
metrics_project_environment_url
(
@environment
.
project
,
@environment
))
ee/app/views/notify/prometheus_alert_fired_email.text.erb
0 → 100644
View file @
238c8c3e
An alert has been triggered in
<%=
@project
.
full_path
%>
.
Environment:
<%=
@environment
.
name
%>
Metric:
<%=
@alert
.
full_query
%>
You can view the
<%=
@environment
.
name
%>
performance dashboard at
<%=
metrics_project_environment_url
(
@environment
.
project
,
@environment
)
%>
.
ee/app/workers/cluster_update_app_worker.rb
0 → 100644
View file @
238c8c3e
class
ClusterUpdateAppWorker
UpdateAlreadyInProgressError
=
Class
.
new
(
StandardError
)
include
ApplicationWorker
include
ClusterQueue
include
ClusterApplications
sidekiq_options
retry:
3
,
dead:
false
def
perform
(
app_name
,
app_id
,
project_id
,
scheduled_time
)
project
=
Project
.
find_by
(
id:
project_id
)
return
unless
project
find_application
(
app_name
,
app_id
)
do
|
app
|
break
if
app
.
updated_since?
(
scheduled_time
)
break
if
app
.
update_in_progress?
Clusters
::
Applications
::
PrometheusUpdateService
.
new
(
app
,
project
).
execute
end
end
end
ee/app/workers/cluster_wait_for_app_update_worker.rb
0 → 100644
View file @
238c8c3e
class
ClusterWaitForAppUpdateWorker
include
ApplicationWorker
include
ClusterQueue
include
ClusterApplications
INTERVAL
=
10
.
seconds
TIMEOUT
=
20
.
minutes
def
perform
(
app_name
,
app_id
)
find_application
(
app_name
,
app_id
)
do
|
app
|
::
Clusters
::
Applications
::
CheckUpgradeProgressService
.
new
(
app
).
execute
end
end
end
ee/changelogs/unreleased/5158-backend-metrics-alerting.yml
0 → 100644
View file @
238c8c3e
---
title
:
Adds SLI alerts to custom prometheus metrics
merge_request
:
6590
author
:
type
:
added
ee/db/migrate/20180320142552_create_prometheus_alerts.rb
0 → 100644
View file @
238c8c3e
class
CreatePrometheusAlerts
<
ActiveRecord
::
Migration
DOWNTIME
=
false
def
up
create_table
:prometheus_alerts
do
|
t
|
t
.
datetime_with_timezone
:created_at
,
null:
false
t
.
datetime_with_timezone
:updated_at
,
null:
false
t
.
float
:threshold
,
null:
false
t
.
integer
:operator
,
null:
false
t
.
references
:environment
,
index:
true
,
null:
false
,
foreign_key:
{
on_delete: :cascade
}
t
.
references
:project
,
null:
false
,
foreign_key:
{
on_delete: :cascade
}
t
.
references
:prometheus_metric
,
null:
false
,
index:
{
unique:
true
},
foreign_key:
{
on_delete: :cascade
}
end
end
def
down
remove_foreign_key
:prometheus_alerts
,
column: :project_id
drop_table
:prometheus_alerts
end
end
ee/db/migrate/20180524115107_add_last_update_started_at_to_applications_prometheus.rb
0 → 100644
View file @
238c8c3e
class
AddLastUpdateStartedAtToApplicationsPrometheus
<
ActiveRecord
::
Migration
DOWNTIME
=
false
def
change
add_column
:clusters_applications_prometheus
,
:last_update_started_at
,
:datetime_with_timezone
end
end
ee/lib/ee/gitlab/kubernetes/helm/api.rb
0 → 100644
View file @
238c8c3e
module
EE
module
Gitlab
module
Kubernetes
module
Helm
module
Api
def
get_config_map
(
command
)
namespace
.
ensure_exists!
return
unless
command
.
config_map?
kubeclient
.
get_config_map
(
command
.
config_map_name
,
namespace
.
name
)
end
def
update
(
command
)
namespace
.
ensure_exists!
update_config_map
(
command
)
if
command
.
config_map?
kubeclient
.
create_pod
(
command
.
pod_resource
)
end
private
def
update_config_map
(
command
)
command
.
config_map_resource
.
tap
do
|
config_map_resource
|
kubeclient
.
update_config_map
(
config_map_resource
)
end
end
end
end
end
end
end
ee/lib/ee/gitlab/prometheus/queries/query_additional_metrics.rb
0 → 100644
View file @
238c8c3e
module
EE
module
Gitlab
module
Prometheus
module
Queries
module
QueryAdditionalMetrics
def
query_metrics
(
project
,
environment
,
query_context
)
super
.
map
(
&
query_with_alert
(
project
,
environment
))
end
protected
def
query_with_alert
(
project
,
environment
)
alerts_map
=
project
.
prometheus_alerts
.
each_with_object
({})
do
|
alert
,
hsh
|
hsh
[
alert
[
:prometheus_metric_id
]]
=
alert
.
prometheus_metric_id
end
proc
do
|
group
|
group
[
:metrics
]
=
group
[
:metrics
]
&
.
map
do
|
metric
|
key
=
metric
[
:id
]
if
key
&&
alerts_map
[
key
]
metric
[
:queries
]
=
metric
[
:queries
]
&
.
map
do
|
item
|
item
[
:alert_path
]
=
alert_path
(
alerts_map
,
key
,
project
,
environment
)
item
end
end
metric
end
group
end
end
private
def
alert_path
(
alerts_map
,
key
,
project
,
environment
)
::
Gitlab
::
Routing
.
url_helpers
.
project_prometheus_alert_path
(
project
,
alerts_map
[
key
],
environment_id:
environment
.
id
,
format: :json
)
end
end
end
end
end
end
ee/lib/gitlab/kubernetes/helm/get_command.rb
0 → 100644
View file @
238c8c3e
require_dependency
'lib/gitlab/kubernetes/helm.rb'
module
Gitlab
module
Kubernetes
module
Helm
class
GetCommand
<
BaseCommand
def
config_map?
true
end
def
config_map_name
::
Gitlab
::
Kubernetes
::
ConfigMap
.
new
(
name
).
config_map_name
end
end
end
end
end
ee/lib/gitlab/kubernetes/helm/upgrade_command.rb
0 → 100644
View file @
238c8c3e
require_dependency
'lib/gitlab/kubernetes/helm.rb'
module
Gitlab
module
Kubernetes
module
Helm
class
UpgradeCommand
<
BaseCommand
attr_reader
:chart
,
:version
,
:repository
,
:values
def
initialize
(
name
,
chart
:,
values
:,
version:
nil
,
repository:
nil
)
super
(
name
)
@chart
=
chart
@version
=
version
@values
=
values
@repository
=
repository
end
def
generate_script
super
+
[
init_command
,
repository_command
,
script_command
].
compact
.
join
(
"
\n
"
)
end
def
config_map?
true
end
def
config_map_resource
::
Gitlab
::
Kubernetes
::
ConfigMap
.
new
(
name
,
values
).
generate
end
def
pod_name
"upgrade-
#{
name
}
"
end
private
def
init_command
'helm init --client-only >/dev/null'
end
def
repository_command
"helm repo add
#{
name
}
#{
repository
}
"
if
repository
end
def
script_command
<<~
HEREDOC
helm upgrade
#{
name
}#{
optional_version_flag
}
#{
chart
}
--reset-values --install --namespace
#{
::
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
}
-f /data/helm/
#{
name
}
/config/values.yaml >/dev/null
HEREDOC
end
def
optional_version_flag
" --version
#{
version
}
"
if
version
end
end
end
end
end
ee/spec/controllers/projects/prometheus/alerts_controller_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
Projects
::
Prometheus
::
AlertsController
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
let
(
:metric
)
{
create
(
:prometheus_metric
,
project:
project
)
}
before
do
stub_licensed_features
(
prometheus_alerts:
true
)
project
.
add_master
(
user
)
sign_in
(
user
)
end
describe
'GET #index'
do
context
'when project has no prometheus alert'
do
it
'renders forbidden when unlicensed'
do
stub_licensed_features
(
prometheus_alerts:
false
)
get
:index
,
project_params
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
it
'returns an empty response'
do
get
:index
,
project_params
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
JSON
.
parse
(
response
.
body
)).
to
be_empty
end
end
context
'when project has prometheus alerts'
do
before
do
create_list
(
:prometheus_alert
,
3
,
project:
project
,
environment:
environment
)
end
it
'contains prometheus alerts'
do
get
:index
,
project_params
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
JSON
.
parse
(
response
.
body
).
count
).
to
eq
(
3
)
end
end
end
describe
'GET #show'
do
context
'when alert does not exist'
do
it
'renders 404'
do
get
:show
,
project_params
(
id:
PrometheusAlert
.
all
.
maximum
(
:prometheus_metric_id
).
to_i
+
1
)
expect
(
response
).
to
have_gitlab_http_status
(
404
)
end
end
context
'when alert exists'
do
let
(
:alert
)
{
create
(
:prometheus_alert
,
project:
project
,
environment:
environment
,
prometheus_metric:
metric
)
}
it
'renders forbidden when unlicensed'
do
stub_licensed_features
(
prometheus_alerts:
false
)
get
:show
,
project_params
(
id:
alert
.
prometheus_metric_id
)
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
it
'renders the alert'
do
alert_params
=
{
"id"
=>
alert
.
id
,
"title"
=>
alert
.
title
,
"query"
=>
alert
.
query
,
"operator"
=>
alert
.
computed_operator
,
"threshold"
=>
alert
.
threshold
,
"alert_path"
=>
Gitlab
::
Routing
.
url_helpers
.
project_prometheus_alert_path
(
project
,
alert
.
prometheus_metric_id
,
environment_id:
alert
.
environment
.
id
,
format: :json
)
}
get
:show
,
project_params
(
id:
alert
.
prometheus_metric_id
)
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
JSON
.
parse
(
response
.
body
)).
to
include
(
alert_params
)
end
end
end
describe
'POST #notify'
do
it
'sends a notification'
do
alert
=
create
(
:prometheus_alert
,
project:
project
,
environment:
environment
,
prometheus_metric:
metric
)
notification_service
=
spy
alert_params
=
{
"alert"
=>
alert
.
title
,
"expr"
=>
"
#{
alert
.
query
}
#{
alert
.
computed_operator
}
#{
alert
.
threshold
}
"
,
"for"
=>
"5m"
,
"labels"
=>
{
"gitlab"
=>
"hook"
,
"gitlab_alert_id"
=>
alert
.
prometheus_metric_id
}
}
allow
(
NotificationService
).
to
receive
(
:new
).
and_return
(
notification_service
)
expect
(
notification_service
).
to
receive_message_chain
(
:async
,
:prometheus_alerts_fired
).
with
(
project
,
[
alert_params
])
post
:notify
,
project_params
(
alerts:
[
alert
])
expect
(
response
).
to
have_gitlab_http_status
(
200
)
end
end
describe
'POST #create'
do
it
'renders forbidden when unlicensed'
do
stub_licensed_features
(
prometheus_alerts:
false
)
post
:create
,
project_params
(
operator:
">"
,
threshold:
"1"
,
environment_id:
environment
.
id
,
prometheus_metric_id:
metric
.
id
)
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
it
'creates a new prometheus alert'
do
schedule_update_service
=
spy
alert_params
=
{
"title"
=>
metric
.
title
,
"query"
=>
metric
.
query
,
"operator"
=>
">"
,
"threshold"
=>
1.0
}
allow
(
::
Clusters
::
Applications
::
ScheduleUpdateService
).
to
receive
(
:new
).
and_return
(
schedule_update_service
)
post
:create
,
project_params
(
operator:
">"
,
threshold:
"1"
,
environment_id:
environment
.
id
,
prometheus_metric_id:
metric
.
id
)
expect
(
schedule_update_service
).
to
have_received
(
:execute
)
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
JSON
.
parse
(
response
.
body
)).
to
include
(
alert_params
)
end
end
describe
'POST #update'
do
let
(
:schedule_update_service
)
{
spy
}
let
(
:alert
)
{
create
(
:prometheus_alert
,
project:
project
,
environment:
environment
,
prometheus_metric:
metric
)
}
before
do
allow
(
::
Clusters
::
Applications
::
ScheduleUpdateService
).
to
receive
(
:new
).
and_return
(
schedule_update_service
)
end
it
'renders forbidden when unlicensed'
do
stub_licensed_features
(
prometheus_alerts:
false
)
put
:update
,
project_params
(
id:
alert
.
prometheus_metric_id
,
operator:
"<"
)
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
it
'updates an already existing prometheus alert'
do
alert_params
=
{
"id"
=>
alert
.
id
,
"title"
=>
alert
.
title
,
"query"
=>
alert
.
query
,
"operator"
=>
"<"
,
"threshold"
=>
alert
.
threshold
,
"alert_path"
=>
Gitlab
::
Routing
.
url_helpers
.
project_prometheus_alert_path
(
project
,
alert
.
prometheus_metric_id
,
environment_id:
alert
.
environment
.
id
,
format: :json
)
}
expect
do
put
:update
,
project_params
(
id:
alert
.
prometheus_metric_id
,
operator:
"<"
)
end
.
to
change
{
alert
.
reload
.
operator
}.
to
(
"lt"
)
expect
(
schedule_update_service
).
to
have_received
(
:execute
)
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
JSON
.
parse
(
response
.
body
)).
to
include
(
alert_params
)
end
end
describe
'DELETE #destroy'
do
let
(
:schedule_update_service
)
{
spy
}
let!
(
:alert
)
{
create
(
:prometheus_alert
,
project:
project
,
prometheus_metric:
metric
)
}
before
do
allow
(
::
Clusters
::
Applications
::
ScheduleUpdateService
).
to
receive
(
:new
).
and_return
(
schedule_update_service
)
end
it
'renders forbidden when unlicensed'
do
stub_licensed_features
(
prometheus_alerts:
false
)
delete
:destroy
,
project_params
(
id:
alert
.
prometheus_metric_id
)
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
it
'destroys the specified prometheus alert'
do
expect
do
delete
:destroy
,
project_params
(
id:
alert
.
prometheus_metric_id
)
end
.
to
change
{
PrometheusAlert
.
count
}.
from
(
1
).
to
(
0
)
expect
(
schedule_update_service
).
to
have_received
(
:execute
)
end
end
def
project_params
(
opts
=
{})
opts
.
reverse_merge
(
namespace_id:
project
.
namespace
,
project_id:
project
)
end
end
ee/spec/factories/prometheus_alert.rb
0 → 100644
View file @
238c8c3e
FactoryBot
.
define
do
factory
:prometheus_alert
do
project
environment
prometheus_metric
operator
:gt
threshold
1
end
end
ee/spec/javascripts/geo_nodes/components/app_spec.js
View file @
238c8c3e
...
@@ -8,7 +8,13 @@ import GeoNodesStore from 'ee/geo_nodes/store/geo_nodes_store';
...
@@ -8,7 +8,13 @@ import GeoNodesStore from 'ee/geo_nodes/store/geo_nodes_store';
import
GeoNodesService
from
'
ee/geo_nodes/service/geo_nodes_service
'
;
import
GeoNodesService
from
'
ee/geo_nodes/service/geo_nodes_service
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
NODE_ACTIONS
}
from
'
ee/geo_nodes/constants
'
;
import
{
NODE_ACTIONS
}
from
'
ee/geo_nodes/constants
'
;
import
{
PRIMARY_VERSION
,
NODE_DETAILS_PATH
,
mockNodes
,
mockNode
,
rawMockNodeDetails
}
from
'
../mock_data
'
;
import
{
PRIMARY_VERSION
,
NODE_DETAILS_PATH
,
mockNodes
,
mockNode
,
rawMockNodeDetails
,
}
from
'
../mock_data
'
;
const
createComponent
=
()
=>
{
const
createComponent
=
()
=>
{
const
Component
=
Vue
.
extend
(
appComponent
);
const
Component
=
Vue
.
extend
(
appComponent
);
...
@@ -86,187 +92,244 @@ describe('AppComponent', () => {
...
@@ -86,187 +92,244 @@ describe('AppComponent', () => {
});
});
describe
(
'
fetchGeoNodes
'
,
()
=>
{
describe
(
'
fetchGeoNodes
'
,
()
=>
{
it
(
'
calls service.getGeoNodes and sets response to the store on success
'
,
(
done
)
=>
{
it
(
'
calls service.getGeoNodes and sets response to the store on success
'
,
done
=>
{
spyOn
(
vm
.
store
,
'
setNodes
'
);
spyOn
(
vm
.
store
,
'
setNodes
'
);
vm
.
fetchGeoNodes
();
vm
setTimeout
(()
=>
{
.
fetchGeoNodes
()
.
then
(()
=>
{
expect
(
vm
.
store
.
setNodes
).
toHaveBeenCalledWith
(
mockNodes
);
expect
(
vm
.
store
.
setNodes
).
toHaveBeenCalledWith
(
mockNodes
);
expect
(
vm
.
isLoading
).
toBe
(
false
);
expect
(
vm
.
isLoading
).
toBe
(
false
);
done
();
})
},
0
);
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
it
(
'
sets error flag and message on failure
'
,
(
done
)
=>
{
it
(
'
sets error flag and message on failure
'
,
done
=>
{
response
=
'
Something went wrong
'
;
response
=
'
Something went wrong
'
;
statusCode
=
500
;
statusCode
=
500
;
vm
.
fetchGeoNodes
();
vm
setTimeout
(()
=>
{
.
fetchGeoNodes
()
.
then
(()
=>
{
expect
(
vm
.
isLoading
).
toBe
(
false
);
expect
(
vm
.
isLoading
).
toBe
(
false
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while fetching nodes
'
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
done
();
'
Something went wrong while fetching nodes
'
,
},
0
);
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
});
describe
(
'
fetchNodeDetails
'
,
()
=>
{
describe
(
'
fetchNodeDetails
'
,
()
=>
{
it
(
'
calls service.getGeoNodeDetails and sets response to the store on success
'
,
(
done
)
=>
{
it
(
'
calls service.getGeoNodeDetails and sets response to the store on success
'
,
done
=>
{
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
200
,
rawMockNodeDetails
);
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
200
,
rawMockNodeDetails
);
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
vm
.
fetchNodeDetails
(
mockNode
);
vm
setTimeout
(()
=>
{
.
fetchNodeDetails
(
mockNode
)
.
then
(()
=>
{
expect
(
vm
.
service
.
getGeoNodeDetails
).
toHaveBeenCalled
();
expect
(
vm
.
service
.
getGeoNodeDetails
).
toHaveBeenCalled
();
expect
(
Object
.
keys
(
vm
.
store
.
state
.
nodeDetails
).
length
).
not
.
toBe
(
0
);
expect
(
Object
.
keys
(
vm
.
store
.
state
.
nodeDetails
).
length
).
not
.
toBe
(
0
);
expect
(
vm
.
store
.
state
.
nodeDetails
[
'
1
'
]).
toBeDefined
();
expect
(
vm
.
store
.
state
.
nodeDetails
[
'
1
'
]).
toBeDefined
();
done
();
})
},
0
);
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
it
(
'
emits `nodeDetailsLoaded` event with fake nodeDetails object on 404 failure
'
,
(
done
)
=>
{
it
(
'
emits `nodeDetailsLoaded` event with fake nodeDetails object on 404 failure
'
,
done
=>
{
spyOn
(
eventHub
,
'
$emit
'
);
spyOn
(
eventHub
,
'
$emit
'
);
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
404
,
{});
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
404
,
{});
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
vm
.
fetchNodeDetails
(
mockNode
);
vm
setTimeout
(()
=>
{
.
fetchNodeDetails
(
mockNode
)
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoaded
'
,
jasmine
.
any
(
Object
));
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoaded
'
,
jasmine
.
any
(
Object
));
const
nodeDetails
=
vm
.
store
.
state
.
nodeDetails
[
'
1
'
];
const
nodeDetails
=
vm
.
store
.
state
.
nodeDetails
[
'
1
'
];
expect
(
nodeDetails
).
toBeDefined
();
expect
(
nodeDetails
).
toBeDefined
();
expect
(
nodeDetails
.
syncStatusUnavailable
).
toBe
(
true
);
expect
(
nodeDetails
.
syncStatusUnavailable
).
toBe
(
true
);
expect
(
nodeDetails
.
health
).
toBe
(
'
Request failed with status code 404
'
);
expect
(
nodeDetails
.
health
).
toBe
(
'
Request failed with status code 404
'
);
done
();
})
},
0
);
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
it
(
'
emits `nodeDetailsLoaded` event with fake nodeDetails object on 500 failure
'
,
(
done
)
=>
{
it
(
'
emits `nodeDetailsLoaded` event with fake nodeDetails object on 500 failure
'
,
done
=>
{
spyOn
(
eventHub
,
'
$emit
'
);
spyOn
(
eventHub
,
'
$emit
'
);
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
500
,
{});
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
500
,
{});
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
vm
.
fetchNodeDetails
(
mockNode
);
vm
setTimeout
(()
=>
{
.
fetchNodeDetails
(
mockNode
)
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoaded
'
,
jasmine
.
any
(
Object
));
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoaded
'
,
jasmine
.
any
(
Object
));
const
nodeDetails
=
vm
.
store
.
state
.
nodeDetails
[
'
1
'
];
const
nodeDetails
=
vm
.
store
.
state
.
nodeDetails
[
'
1
'
];
expect
(
nodeDetails
).
toBeDefined
();
expect
(
nodeDetails
).
toBeDefined
();
expect
(
nodeDetails
.
syncStatusUnavailable
).
toBe
(
true
);
expect
(
nodeDetails
.
syncStatusUnavailable
).
toBe
(
true
);
expect
(
nodeDetails
.
health
).
toBe
(
'
Request failed with status code 500
'
);
expect
(
nodeDetails
.
health
).
toBe
(
'
Request failed with status code 500
'
);
done
();
})
},
0
);
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
it
(
'
emits `nodeDetailsLoadFailed` event on failure when there is no response
'
,
(
done
)
=>
{
it
(
'
emits `nodeDetailsLoadFailed` event on failure when there is no response
'
,
done
=>
{
spyOn
(
eventHub
,
'
$emit
'
);
spyOn
(
eventHub
,
'
$emit
'
);
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
500
,
null
);
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
500
,
null
);
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
vm
.
fetchNodeDetails
(
mockNode
);
vm
setTimeout
(()
=>
{
.
fetchNodeDetails
(
mockNode
)
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoadFailed
'
,
mockNode
.
id
,
jasmine
.
any
(
Object
));
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoadFailed
'
,
mockNode
.
id
,
jasmine
.
any
(
Object
),
);
done
();
done
();
},
0
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
});
describe
(
'
repairNode
'
,
()
=>
{
describe
(
'
repairNode
'
,
()
=>
{
it
(
'
calls service.repairNode and shows success Flash message on request success
'
,
(
done
)
=>
{
it
(
'
calls service.repairNode and shows success Flash message on request success
'
,
done
=>
{
const
node
=
{
...
mockNode
};
const
node
=
{
...
mockNode
};
mock
.
onPost
(
node
.
repairPath
).
reply
(
200
);
mock
.
onPost
(
node
.
repairPath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
200
];
});
spyOn
(
vm
.
service
,
'
repairNode
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
repairNode
'
).
and
.
callThrough
();
vm
.
repairNode
(
node
);
vm
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
.
repairNode
(
node
)
setTimeout
(()
=>
{
.
then
(()
=>
{
expect
(
vm
.
service
.
repairNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
service
.
repairNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Node Authentication was successfully repaired.
'
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Node Authentication was successfully repaired.
'
,
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
done
();
})
},
0
);
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
it
(
'
calls service.repairNode and shows failure Flash message on request failure
'
,
(
done
)
=>
{
it
(
'
calls service.repairNode and shows failure Flash message on request failure
'
,
done
=>
{
const
node
=
{
...
mockNode
};
const
node
=
{
...
mockNode
};
mock
.
onPost
(
node
.
repairPath
).
reply
(
500
);
mock
.
onPost
(
node
.
repairPath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
500
];
});
spyOn
(
vm
.
service
,
'
repairNode
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
repairNode
'
).
and
.
callThrough
();
vm
.
repairNode
(
node
);
vm
setTimeout
(()
=>
{
.
repairNode
(
node
)
.
then
(()
=>
{
expect
(
vm
.
service
.
repairNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
service
.
repairNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while repairing node
'
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while repairing node
'
,
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
done
();
})
},
0
);
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
});
describe
(
'
toggleNode
'
,
()
=>
{
describe
(
'
toggleNode
'
,
()
=>
{
it
(
'
calls service.toggleNode for enabling node and updates toggle button on request success
'
,
(
done
)
=>
{
it
(
'
calls service.toggleNode for enabling node and updates toggle button on request success
'
,
done
=>
{
const
node
=
{
...
mockNode
};
const
node
=
{
...
mockNode
};
mock
.
onPut
(
node
.
basePath
).
reply
(
200
,
{
mock
.
onPut
(
node
.
basePath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
200
,
{
enabled
:
true
,
enabled
:
true
,
},
];
});
});
spyOn
(
vm
.
service
,
'
toggleNode
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
toggleNode
'
).
and
.
callThrough
();
node
.
enabled
=
false
;
node
.
enabled
=
false
;
vm
.
toggleNode
(
node
);
vm
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
.
toggleNode
(
node
)
setTimeout
(()
=>
{
.
then
(()
=>
{
expect
(
vm
.
service
.
toggleNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
service
.
toggleNode
).
toHaveBeenCalledWith
(
node
);
expect
(
node
.
enabled
).
toBe
(
true
);
expect
(
node
.
enabled
).
toBe
(
true
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
done
();
})
},
0
);
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
it
(
'
calls service.toggleNode and shows Flash error on request failure
'
,
(
done
)
=>
{
it
(
'
calls service.toggleNode and shows Flash error on request failure
'
,
done
=>
{
const
node
=
{
...
mockNode
};
const
node
=
{
...
mockNode
};
mock
.
onPut
(
node
.
basePath
).
reply
(
500
);
mock
.
onPut
(
node
.
basePath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
500
];
});
spyOn
(
vm
.
service
,
'
toggleNode
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
toggleNode
'
).
and
.
callThrough
();
node
.
enabled
=
false
;
node
.
enabled
=
false
;
vm
.
toggleNode
(
node
);
vm
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
.
toggleNode
(
node
)
setTimeout
(()
=>
{
.
then
(()
=>
{
expect
(
vm
.
service
.
toggleNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
service
.
toggleNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while changing node status
'
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while changing node status
'
,
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
done
();
})
},
0
);
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
});
describe
(
'
removeNode
'
,
()
=>
{
describe
(
'
removeNode
'
,
()
=>
{
it
(
'
calls service.removeNode for removing node and shows Flash message on request success
'
,
(
done
)
=>
{
it
(
'
calls service.removeNode for removing node and shows Flash message on request success
'
,
done
=>
{
const
node
=
{
...
mockNode
};
const
node
=
{
...
mockNode
};
mock
.
onDelete
(
node
.
basePath
).
reply
(
200
);
mock
.
onDelete
(
node
.
basePath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
200
];
});
spyOn
(
vm
.
service
,
'
removeNode
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
removeNode
'
).
and
.
callThrough
();
spyOn
(
vm
.
store
,
'
removeNode
'
).
and
.
stub
();
spyOn
(
vm
.
store
,
'
removeNode
'
).
and
.
stub
();
vm
.
removeNode
(
node
);
vm
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
.
removeNode
(
node
)
setTimeout
(()
=>
{
.
then
(()
=>
{
expect
(
vm
.
service
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
service
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
store
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
store
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Node was successfully removed.
'
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
done
();
'
Node was successfully removed.
'
,
},
0
);
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
it
(
'
calls service.removeNode and shows Flash message on request failure
'
,
(
done
)
=>
{
it
(
'
calls service.removeNode and shows Flash message on request failure
'
,
done
=>
{
const
node
=
{
...
mockNode
};
const
node
=
{
...
mockNode
};
mock
.
onDelete
(
node
.
basePath
).
reply
(
500
);
mock
.
onDelete
(
node
.
basePath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
500
];
});
spyOn
(
vm
.
service
,
'
removeNode
'
).
and
.
callThrough
();
spyOn
(
vm
.
service
,
'
removeNode
'
).
and
.
callThrough
();
spyOn
(
vm
.
store
,
'
removeNode
'
).
and
.
stub
();
spyOn
(
vm
.
store
,
'
removeNode
'
).
and
.
stub
();
vm
.
removeNode
(
node
);
vm
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
.
removeNode
(
node
)
setTimeout
(()
=>
{
.
then
(()
=>
{
expect
(
vm
.
service
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
service
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
store
.
removeNode
).
not
.
toHaveBeenCalled
();
expect
(
vm
.
store
.
removeNode
).
not
.
toHaveBeenCalled
();
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while removing node
'
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
done
();
'
Something went wrong while removing node
'
,
},
0
);
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
});
...
@@ -399,7 +462,9 @@ describe('AppComponent', () => {
...
@@ -399,7 +462,9 @@ describe('AppComponent', () => {
it
(
'
renders loading animation when `isLoading` is true
'
,
()
=>
{
it
(
'
renders loading animation when `isLoading` is true
'
,
()
=>
{
vm
.
isLoading
=
true
;
vm
.
isLoading
=
true
;
expect
(
vm
.
$el
.
querySelectorAll
(
'
.loading-animation.prepend-top-20.append-bottom-20
'
).
length
).
not
.
toBe
(
0
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
.loading-animation.prepend-top-20.append-bottom-20
'
).
length
,
).
not
.
toBe
(
0
);
});
});
});
});
});
});
spec/javascripts/vue_mr_widget/components/codequality_issue_body_spec.js
→
ee/
spec/javascripts/vue_mr_widget/components/codequality_issue_body_spec.js
View file @
238c8c3e
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
component
from
'
ee/vue_merge_request_widget/components/codequality_issue_body.vue
'
;
import
component
from
'
ee/vue_merge_request_widget/components/codequality_issue_body.vue
'
;
import
mountComponent
from
'
../../helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
STATUS_FAILED
,
STATUS_NEUTRAL
,
STATUS_SUCCESS
,
}
from
'
~/vue_shared/components/reports/constants
'
;
describe
(
'
sast
issue body
'
,
()
=>
{
describe
(
'
code quality issue body
issue body
'
,
()
=>
{
let
vm
;
let
vm
;
const
Component
=
Vue
.
extend
(
component
);
const
Component
=
Vue
.
extend
(
component
);
...
@@ -22,7 +27,7 @@ describe('sast issue body', () => {
...
@@ -22,7 +27,7 @@ describe('sast issue body', () => {
it
(
'
renders fixed label
'
,
()
=>
{
it
(
'
renders fixed label
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
vm
=
mountComponent
(
Component
,
{
issue
:
codequalityIssue
,
issue
:
codequalityIssue
,
isStatusSuccess
:
true
,
status
:
STATUS_SUCCESS
,
});
});
expect
(
vm
.
$el
.
textContent
.
trim
()).
toContain
(
'
Fixed
'
);
expect
(
vm
.
$el
.
textContent
.
trim
()).
toContain
(
'
Fixed
'
);
...
@@ -33,7 +38,7 @@ describe('sast issue body', () => {
...
@@ -33,7 +38,7 @@ describe('sast issue body', () => {
it
(
'
renders fixed label
'
,
()
=>
{
it
(
'
renders fixed label
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
vm
=
mountComponent
(
Component
,
{
issue
:
codequalityIssue
,
issue
:
codequalityIssue
,
isStatusSuccess
:
false
,
status
:
STATUS_FAILED
,
});
});
expect
(
vm
.
$el
.
textContent
.
trim
()).
not
.
toContain
(
'
Fixed
'
);
expect
(
vm
.
$el
.
textContent
.
trim
()).
not
.
toContain
(
'
Fixed
'
);
...
@@ -44,7 +49,7 @@ describe('sast issue body', () => {
...
@@ -44,7 +49,7 @@ describe('sast issue body', () => {
it
(
'
renders name
'
,
()
=>
{
it
(
'
renders name
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
vm
=
mountComponent
(
Component
,
{
issue
:
codequalityIssue
,
issue
:
codequalityIssue
,
isStatusSuccess
:
false
,
status
:
STATUS_NEUTRAL
,
});
});
expect
(
vm
.
$el
.
textContent
.
trim
()).
toContain
(
codequalityIssue
.
name
);
expect
(
vm
.
$el
.
textContent
.
trim
()).
toContain
(
codequalityIssue
.
name
);
...
@@ -55,15 +60,11 @@ describe('sast issue body', () => {
...
@@ -55,15 +60,11 @@ describe('sast issue body', () => {
it
(
'
renders name
'
,
()
=>
{
it
(
'
renders name
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
vm
=
mountComponent
(
Component
,
{
issue
:
codequalityIssue
,
issue
:
codequalityIssue
,
isStatusSuccess
:
false
,
status
:
STATUS_NEUTRAL
,
});
});
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
getAttribute
(
'
href
'
)).
toEqual
(
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
getAttribute
(
'
href
'
)).
toEqual
(
codequalityIssue
.
urlPath
);
codequalityIssue
.
urlPath
,
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
textContent
.
trim
()).
toEqual
(
codequalityIssue
.
path
);
);
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
textContent
.
trim
()).
toEqual
(
codequalityIssue
.
path
,
);
});
});
});
});
});
});
spec/javascripts/vue_mr_widget/components/performance_issue_body_spec.js
→
ee/
spec/javascripts/vue_mr_widget/components/performance_issue_body_spec.js
View file @
238c8c3e
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
component
from
'
ee/vue_merge_request_widget/components/performance_issue_body.vue
'
;
import
component
from
'
ee/vue_merge_request_widget/components/performance_issue_body.vue
'
;
import
mountComponent
from
'
../..
/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec
/helpers/vue_mount_component_helper
'
;
describe
(
'
performance issue body
'
,
()
=>
{
describe
(
'
performance issue body
'
,
()
=>
{
let
vm
;
let
vm
;
...
...
spec/javascripts/vue_shared/components/reports/report_issues_spec.js
→
ee/
spec/javascripts/vue_shared/components/reports/report_issues_spec.js
View file @
238c8c3e
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
reportIssues
from
'
~/vue_shared/components/reports/report_issues.vue
'
;
import
reportIssues
from
'
~/vue_shared/components/reports/report_issues.vue
'
;
import
{
STATUS_FAILED
,
STATUS_SUCCESS
}
from
'
~/vue_shared/components/reports/constants
'
;
import
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
store
from
'
ee/vue_shared/security_reports/store
'
;
import
store
from
'
ee/vue_shared/security_reports/store
'
;
import
mountComponent
,
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
,
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
import
{
codequalityParsedIssues
}
from
'
spec/vue_mr_widget/mock_data
'
;
codequalityParsedIssues
,
}
from
'
spec/vue_mr_widget/mock_data
'
;
import
{
import
{
sastParsedIssues
,
sastParsedIssues
,
dockerReportParsed
,
dockerReportParsed
,
...
@@ -28,8 +28,8 @@ describe('Report issues', () => {
...
@@ -28,8 +28,8 @@ describe('Report issues', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
codequalityParsedIssues
,
issues
:
codequalityParsedIssues
,
type
:
'
codequality
'
,
component
:
componentNames
.
CodequalityIssueBody
,
status
:
'
success
'
,
status
:
STATUS_SUCCESS
,
});
});
});
});
...
@@ -49,8 +49,8 @@ describe('Report issues', () => {
...
@@ -49,8 +49,8 @@ describe('Report issues', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
codequalityParsedIssues
,
issues
:
codequalityParsedIssues
,
type
:
'
codequality
'
,
component
:
componentNames
.
CodequalityIssueBody
,
status
:
'
failed
'
,
status
:
STATUS_FAILED
,
});
});
});
});
...
@@ -68,8 +68,8 @@ describe('Report issues', () => {
...
@@ -68,8 +68,8 @@ describe('Report issues', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
sastParsedIssues
,
issues
:
sastParsedIssues
,
type
:
'
SAST
'
,
component
:
componentNames
.
SastIssueBody
,
status
:
'
failed
'
,
status
:
STATUS_FAILED
,
});
});
});
});
...
@@ -82,8 +82,8 @@ describe('Report issues', () => {
...
@@ -82,8 +82,8 @@ describe('Report issues', () => {
it
(
'
should render location
'
,
()
=>
{
it
(
'
should render location
'
,
()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
sastParsedIssues
,
issues
:
sastParsedIssues
,
type
:
'
SAST
'
,
component
:
componentNames
.
SastIssueBody
,
status
:
'
failed
'
,
status
:
STATUS_FAILED
,
});
});
expect
(
vm
.
$el
.
querySelector
(
'
.report-block-list li
'
).
textContent
).
toContain
(
'
in
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.report-block-list li
'
).
textContent
).
toContain
(
'
in
'
);
...
@@ -97,8 +97,8 @@ describe('Report issues', () => {
...
@@ -97,8 +97,8 @@ describe('Report issues', () => {
issues
:
[{
issues
:
[{
title
:
'
foo
'
,
title
:
'
foo
'
,
}],
}],
type
:
'
SAST
'
,
component
:
componentNames
.
SastIssueBody
,
status
:
'
failed
'
,
status
:
STATUS_SUCCESS
,
});
});
expect
(
vm
.
$el
.
querySelector
(
'
.report-block-list li
'
).
textContent
).
not
.
toContain
(
'
in
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.report-block-list li
'
).
textContent
).
not
.
toContain
(
'
in
'
);
...
@@ -110,8 +110,8 @@ describe('Report issues', () => {
...
@@ -110,8 +110,8 @@ describe('Report issues', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
dockerReportParsed
.
unapproved
,
issues
:
dockerReportParsed
.
unapproved
,
type
:
'
SAST_CONTAINER
'
,
component
:
componentNames
.
SastContainerIssueBody
,
status
:
'
failed
'
,
status
:
STATUS_FAILED
,
});
});
});
});
...
@@ -142,8 +142,8 @@ describe('Report issues', () => {
...
@@ -142,8 +142,8 @@ describe('Report issues', () => {
vm
=
mountComponentWithStore
(
ReportIssues
,
{
store
,
vm
=
mountComponentWithStore
(
ReportIssues
,
{
store
,
props
:
{
props
:
{
issues
:
parsedDast
,
issues
:
parsedDast
,
type
:
'
DAST
'
,
component
:
componentNames
.
DastIssueBody
,
status
:
'
failed
'
,
status
:
STATUS_FAILED
,
},
},
});
});
});
});
...
...
ee/spec/javascripts/vue_shared/components/reports/report_section_mock_data.js
0 → 100644
View file @
238c8c3e
// eslint-disable-next-line import/prefer-default-export
export
const
fullReport
=
{
status
:
'
SUCCESS
'
,
successText
:
'
SAST improved on 1 security vulnerability and degraded on 1 security vulnerability
'
,
errorText
:
'
Failed to load security report
'
,
hasIssues
:
true
,
loadingText
:
'
Loading security report
'
,
resolvedIssues
:
[
{
cve
:
'
CVE-2016-9999
'
,
file
:
'
Gemfile.lock
'
,
message
:
'
Test Information Leak Vulnerability in Action View
'
,
title
:
'
Test Information Leak Vulnerability in Action View
'
,
path
:
'
Gemfile.lock
'
,
solution
:
'
upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1
'
,
tool
:
'
bundler_audit
'
,
url
:
'
https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00
'
,
urlPath
:
'
/Gemfile.lock
'
,
},
],
unresolvedIssues
:
[
{
cve
:
'
CVE-2014-7829
'
,
file
:
'
Gemfile.lock
'
,
message
:
'
Arbitrary file existence disclosure in Action Pack
'
,
title
:
'
Arbitrary file existence disclosure in Action Pack
'
,
path
:
'
Gemfile.lock
'
,
solution
:
'
upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8
'
,
tool
:
'
bundler_audit
'
,
url
:
'
https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk
'
,
urlPath
:
'
/Gemfile.lock
'
,
},
],
allIssues
:
[
{
cve
:
'
CVE-2016-0752
'
,
file
:
'
Gemfile.lock
'
,
message
:
'
Possible Information Leak Vulnerability in Action View
'
,
title
:
'
Possible Information Leak Vulnerability in Action View
'
,
path
:
'
Gemfile.lock
'
,
solution
:
'
upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1
'
,
tool
:
'
bundler_audit
'
,
url
:
'
https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00
'
,
urlPath
:
'
/Gemfile.lock
'
,
},
],
};
ee/spec/javascripts/vue_shared/components/reports/report_section_spec.js
0 → 100644
View file @
238c8c3e
import
Vue
from
'
vue
'
;
import
reportSection
from
'
~/vue_shared/components/reports/report_section.vue
'
;
import
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
fullReport
}
from
'
./report_section_mock_data
'
;
describe
(
'
Report section
'
,
()
=>
{
let
vm
;
const
ReportSection
=
Vue
.
extend
(
reportSection
);
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
With full report
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
component
:
componentNames
.
SastIssueBody
,
...
fullReport
,
});
});
it
(
'
should render full report section
'
,
done
=>
{
vm
.
$el
.
querySelector
(
'
button
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-expand-full-list
'
).
textContent
.
trim
()).
toEqual
(
'
Show complete code vulnerabilities report
'
,
);
done
();
});
});
it
(
'
should expand full list when clicked and hide the show all button
'
,
done
=>
{
vm
.
$el
.
querySelector
(
'
button
'
).
click
();
Vue
.
nextTick
(()
=>
{
vm
.
$el
.
querySelector
(
'
.js-expand-full-list
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-mr-code-all-issues
'
).
textContent
.
trim
()).
toContain
(
'
Possible Information Leak Vulnerability in Action View
'
,
);
done
();
});
});
});
});
});
ee/spec/lib/ee/gitlab/kubernetes/helm/api_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
Gitlab
::
Kubernetes
::
Helm
::
Api
do
let
(
:kubeclient
)
{
spy
}
let
(
:namespace
)
{
spy
}
let
(
:application
)
{
build
(
:clusters_applications_prometheus
)
}
subject
{
described_class
.
new
(
kubeclient
)
}
before
do
allow
(
Gitlab
::
Kubernetes
::
Namespace
)
.
to
receive
(
:new
)
.
with
(
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
,
kubeclient
)
.
and_return
(
namespace
)
end
describe
'#get_config_map'
do
let
(
:command
)
{
Gitlab
::
Kubernetes
::
Helm
::
GetCommand
.
new
(
application
.
name
)
}
it
'ensures the namespace exists before retrieving the config map'
do
expect
(
namespace
).
to
receive
(
:ensure_exists!
).
once
subject
.
get_config_map
(
command
)
end
it
'gets the config map on kubeclient'
do
expect
(
kubeclient
).
to
receive
(
:get_config_map
)
.
with
(
command
.
config_map_name
,
namespace
.
name
)
.
once
subject
.
get_config_map
(
command
)
end
end
describe
'#update'
do
let
(
:command
)
do
Gitlab
::
Kubernetes
::
Helm
::
UpgradeCommand
.
new
(
application
.
name
,
chart:
application
.
chart
,
values:
application
.
values
)
end
it
'ensures the namespace exists before creating the pod'
do
expect
(
namespace
).
to
receive
(
:ensure_exists!
).
once
.
ordered
expect
(
kubeclient
).
to
receive
(
:create_pod
).
once
.
ordered
subject
.
update
(
command
)
end
it
'updates the config map on kubeclient when one exists'
do
resource
=
Gitlab
::
Kubernetes
::
ConfigMap
.
new
(
application
.
name
,
application
.
values
).
generate
expect
(
kubeclient
).
to
receive
(
:update_config_map
).
with
(
resource
).
once
subject
.
update
(
command
)
end
end
end
ee/spec/lib/gitlab/kubernetes/helm/get_command_spec.rb
0 → 100644
View file @
238c8c3e
require
'rails_helper'
describe
Gitlab
::
Kubernetes
::
Helm
::
GetCommand
do
let
(
:application
)
{
build
(
:clusters_applications_prometheus
)
}
subject
(
:get_command
)
{
described_class
.
new
(
application
.
name
)
}
describe
'#config_map?'
do
it
'returns true'
do
expect
(
get_command
.
config_map?
).
to
be
true
end
end
describe
'#config_map_name'
do
it
'returns the ConfigMap name'
do
expect
(
get_command
.
config_map_name
).
to
eq
(
"values-content-configuration-
#{
application
.
name
}
"
)
end
end
end
ee/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb
0 → 100644
View file @
238c8c3e
require
'rails_helper'
describe
Gitlab
::
Kubernetes
::
Helm
::
UpgradeCommand
do
let
(
:application
)
{
build
(
:clusters_applications_prometheus
)
}
let
(
:namespace
)
{
::
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
}
subject
do
described_class
.
new
(
application
.
name
,
chart:
application
.
chart
,
values:
application
.
values
)
end
it_behaves_like
'helm commands'
do
let
(
:commands
)
do
<<~
EOS
helm init --client-only >/dev/null
helm upgrade
#{
application
.
name
}
#{
application
.
chart
}
--reset-values --install --namespace
#{
namespace
}
-f /data/helm/
#{
application
.
name
}
/config/values.yaml >/dev/null
EOS
end
end
context
'with an application with a repository'
do
let
(
:ci_runner
)
{
create
(
:ci_runner
)
}
let
(
:application
)
{
build
(
:clusters_applications_runner
,
runner:
ci_runner
)
}
subject
do
described_class
.
new
(
application
.
name
,
chart:
application
.
chart
,
values:
application
.
values
,
repository:
application
.
repository
)
end
it_behaves_like
'helm commands'
do
let
(
:commands
)
do
<<~
EOS
helm init --client-only >/dev/null
helm repo add
#{
application
.
name
}
#{
application
.
repository
}
helm upgrade
#{
application
.
name
}
#{
application
.
chart
}
--reset-values --install --namespace
#{
namespace
}
-f /data/helm/
#{
application
.
name
}
/config/values.yaml >/dev/null
EOS
end
end
end
describe
'#config_map?'
do
it
'returns true'
do
expect
(
subject
.
config_map?
).
to
be_truthy
end
end
describe
'#config_map_resource'
do
it
'returns a KubeClient resource with config map content for the application'
do
metadata
=
{
name:
"values-content-configuration-
#{
application
.
name
}
"
,
namespace:
namespace
,
labels:
{
name:
"values-content-configuration-
#{
application
.
name
}
"
}
}
resource
=
::
Kubeclient
::
Resource
.
new
(
metadata:
metadata
,
data:
{
values:
application
.
values
})
expect
(
subject
.
config_map_resource
).
to
eq
(
resource
)
end
end
describe
'#pod_name'
do
it
'returns the pod name'
do
expect
(
subject
.
pod_name
).
to
eq
(
"upgrade-
#{
application
.
name
}
"
)
end
end
end
ee/spec/models/ee/clusters/applications/prometheus_spec.rb
0 → 100644
View file @
238c8c3e
require
'rails_helper'
describe
Clusters
::
Applications
::
Prometheus
do
describe
'transition to updating'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:cluster
)
{
create
(
:cluster
,
projects:
[
project
])
}
subject
{
create
(
:clusters_applications_prometheus
,
:installed
,
cluster:
cluster
)
}
it
'sets last_update_started_at to now'
do
Timecop
.
freeze
do
expect
{
subject
.
make_updating
}.
to
change
{
subject
.
reload
.
last_update_started_at
}.
to
be_within
(
1
.
second
).
of
(
Time
.
now
)
end
end
end
describe
'#ready'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:cluster
)
{
create
(
:cluster
,
projects:
[
project
])
}
it
'returns true when updating'
do
application
=
build
(
:clusters_applications_prometheus
,
:updating
,
cluster:
cluster
)
expect
(
application
).
to
be_ready
end
it
'returns true when updated'
do
application
=
build
(
:clusters_applications_prometheus
,
:updated
,
cluster:
cluster
)
expect
(
application
).
to
be_ready
end
it
'returns true when errored'
do
application
=
build
(
:clusters_applications_prometheus
,
:update_errored
,
cluster:
cluster
)
expect
(
application
).
to
be_ready
end
end
context
'#updated_since?'
do
let
(
:cluster
)
{
create
(
:cluster
)
}
let
(
:prometheus_app
)
{
build
(
:clusters_applications_prometheus
,
cluster:
cluster
)
}
let
(
:timestamp
)
{
Time
.
now
-
5
.
minutes
}
around
do
|
example
|
Timecop
.
freeze
{
example
.
run
}
end
before
do
prometheus_app
.
last_update_started_at
=
Time
.
now
end
context
'when app does not have status failed'
do
it
'returns true when last update started after the timestamp'
do
expect
(
prometheus_app
.
updated_since?
(
timestamp
)).
to
be
true
end
it
'returns false when last update started before the timestamp'
do
expect
(
prometheus_app
.
updated_since?
(
Time
.
now
+
5
.
minutes
)).
to
be
false
end
end
context
'when app has status failed'
do
it
'returns false when last update started after the timestamp'
do
prometheus_app
.
status
=
6
expect
(
prometheus_app
.
updated_since?
(
timestamp
)).
to
be
false
end
end
end
describe
'#update_in_progress?'
do
context
'when app is updating'
do
it
'returns true'
do
cluster
=
create
(
:cluster
)
prometheus_app
=
build
(
:clusters_applications_prometheus
,
:updating
,
cluster:
cluster
)
expect
(
prometheus_app
.
update_in_progress?
).
to
be
true
end
end
end
describe
'#update_errored?'
do
context
'when app errored'
do
it
'returns true'
do
cluster
=
create
(
:cluster
)
prometheus_app
=
build
(
:clusters_applications_prometheus
,
:update_errored
,
cluster:
cluster
)
expect
(
prometheus_app
.
update_errored?
).
to
be
true
end
end
end
describe
'#get_command'
do
let
(
:prometheus
)
{
build
(
:clusters_applications_prometheus
)
}
it
'returns an instance of Gitlab::Kubernetes::Helm::GetCommand'
do
expect
(
prometheus
.
get_command
).
to
be_an_instance_of
(
::
Gitlab
::
Kubernetes
::
Helm
::
GetCommand
)
end
it
'should be initialized with 1 argument'
do
command
=
prometheus
.
get_command
expect
(
command
.
name
).
to
eq
(
'prometheus'
)
end
end
describe
'#upgrade_command'
do
let
(
:prometheus
)
{
build
(
:clusters_applications_prometheus
)
}
let
(
:values
)
{
{
foo:
'bar'
}
}
it
'returns an instance of Gitlab::Kubernetes::Helm::GetCommand'
do
expect
(
prometheus
.
upgrade_command
(
values
)).
to
be_an_instance_of
(
::
Gitlab
::
Kubernetes
::
Helm
::
UpgradeCommand
)
end
it
'should be initialized with 3 arguments'
do
command
=
prometheus
.
upgrade_command
(
values
)
expect
(
command
.
name
).
to
eq
(
'prometheus'
)
expect
(
command
.
chart
).
to
eq
(
'stable/prometheus'
)
expect
(
command
.
values
).
to
eq
(
values
)
end
end
end
ee/spec/models/project_spec.rb
View file @
238c8c3e
...
@@ -152,6 +152,24 @@ describe Project do
...
@@ -152,6 +152,24 @@ describe Project do
end
end
end
end
describe
'#environments_for_scope'
do
set
(
:project
)
{
create
(
:project
)
}
before
do
create_list
(
:environment
,
2
,
project:
project
)
end
it
'retrieves all project environments when using the * wildcard'
do
expect
(
project
.
environments_for_scope
(
"*"
)).
to
eq
(
project
.
environments
)
end
it
'retrieves a specific project environment when using the name of that environment'
do
environment
=
project
.
environments
.
first
expect
(
project
.
environments_for_scope
(
environment
.
name
)).
to
eq
([
environment
])
end
end
describe
'#ensure_external_webhook_token'
do
describe
'#ensure_external_webhook_token'
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:project
)
{
create
(
:project
,
:repository
)
}
...
...
ee/spec/models/prometheus_alert_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
PrometheusAlert
do
let
(
:metric
)
{
create
(
:prometheus_metric
)
}
describe
'associations'
do
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
belong_to
(
:environment
)
}
end
describe
'#full_query'
do
it
'returns the concatenated query'
do
subject
.
operator
=
"gt"
subject
.
threshold
=
1
subject
.
prometheus_metric_id
=
metric
.
id
expect
(
subject
.
full_query
).
to
eq
(
"
#{
metric
.
query
}
> 1.0"
)
end
end
describe
'#to_param'
do
it
'returns the params of the prometheus alert'
do
subject
.
operator
=
"gt"
subject
.
threshold
=
1
subject
.
prometheus_metric_id
=
metric
.
id
alert_params
=
{
"alert"
=>
metric
.
title
,
"expr"
=>
"
#{
metric
.
query
}
> 1.0"
,
"for"
=>
"5m"
,
"labels"
=>
{
"gitlab"
=>
"hook"
,
"gitlab_alert_id"
=>
metric
.
id
}
}
expect
(
subject
.
to_param
).
to
eq
(
alert_params
)
end
end
end
ee/spec/serializers/prometheus_alert_entity_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
PrometheusAlertEntity
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:prometheus_alert
)
{
create
(
:prometheus_alert
)
}
let
(
:request
)
{
double
(
'prometheus_alert'
,
current_user:
user
)
}
let
(
:entity
)
{
described_class
.
new
(
prometheus_alert
,
request:
request
)
}
subject
{
entity
.
as_json
}
context
'when user can read prometheus alerts'
do
before
do
prometheus_alert
.
project
.
add_master
(
user
)
stub_licensed_features
(
prometheus_alerts:
true
)
end
it
'exposes prometheus_alert attributes'
do
expect
(
subject
).
to
include
(
:id
,
:title
,
:query
,
:operator
,
:threshold
)
end
it
'exposes alert_path'
do
expect
(
subject
).
to
include
(
:alert_path
)
end
end
end
ee/spec/services/clusters/applications/check_upgrade_progress_service_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
Clusters
::
Applications
::
CheckUpgradeProgressService
do
RESCHEDULE_PHASES
=
::
Gitlab
::
Kubernetes
::
Pod
::
PHASES
-
[
::
Gitlab
::
Kubernetes
::
Pod
::
SUCCEEDED
,
::
Gitlab
::
Kubernetes
::
Pod
::
FAILED
,
::
Gitlab
].
freeze
let
(
:application
)
{
create
(
:clusters_applications_prometheus
,
:updating
)
}
let
(
:service
)
{
described_class
.
new
(
application
)
}
let
(
:phase
)
{
::
Gitlab
::
Kubernetes
::
Pod
::
UNKNOWN
}
let
(
:errors
)
{
nil
}
shared_examples
'a terminated upgrade'
do
it
'removes the POD'
do
expect
(
service
).
to
receive
(
:remove_pod
).
once
service
.
execute
end
end
shared_examples
'a not yet terminated upgrade'
do
|
a_phase
|
let
(
:phase
)
{
a_phase
}
context
"when phase is
#{
a_phase
}
"
do
context
'when not timed out'
do
it
'reschedule a new check'
do
expect
(
::
ClusterWaitForAppUpdateWorker
).
to
receive
(
:perform_in
).
once
expect
(
service
).
not_to
receive
(
:remove_pod
)
service
.
execute
expect
(
application
).
to
be_updating
expect
(
application
.
status_reason
).
to
be_nil
end
end
context
'when timed out'
do
let
(
:application
)
{
create
(
:clusters_applications_prometheus
,
:timeouted
,
:updating
)
}
it_behaves_like
'a terminated upgrade'
it
'make the application update errored'
do
expect
(
::
ClusterWaitForAppUpdateWorker
).
not_to
receive
(
:perform_in
)
service
.
execute
expect
(
application
).
to
be_update_errored
expect
(
application
.
status_reason
).
to
eq
(
"Update timed out"
)
end
end
end
end
before
do
allow
(
service
).
to
receive
(
:phase
).
once
.
and_return
(
phase
)
allow
(
service
).
to
receive
(
:errors
).
and_return
(
errors
)
allow
(
service
).
to
receive
(
:remove_pod
).
and_return
(
nil
)
end
describe
'#execute'
do
context
'when upgrade pod succeeded'
do
let
(
:phase
)
{
::
Gitlab
::
Kubernetes
::
Pod
::
SUCCEEDED
}
it_behaves_like
'a terminated upgrade'
it
'make the application upgraded'
do
expect
(
::
ClusterWaitForAppUpdateWorker
).
not_to
receive
(
:perform_in
)
service
.
execute
expect
(
application
).
to
be_updated
expect
(
application
.
status_reason
).
to
be_nil
end
end
context
'when upgrade pod failed'
do
let
(
:phase
)
{
::
Gitlab
::
Kubernetes
::
Pod
::
FAILED
}
let
(
:errors
)
{
'test installation failed'
}
it_behaves_like
'a terminated upgrade'
it
'make the application update errored'
do
service
.
execute
expect
(
application
).
to
be_update_errored
expect
(
application
.
status_reason
).
to
eq
(
errors
)
end
end
RESCHEDULE_PHASES
.
each
{
|
phase
|
it_behaves_like
'a not yet terminated upgrade'
,
phase
}
end
end
ee/spec/services/clusters/applications/prometheus_update_service_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
Clusters
::
Applications
::
PrometheusUpdateService
do
describe
'#execute'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
let
(
:cluster
)
{
create
(
:cluster
,
projects:
[
project
])
}
let
(
:application
)
{
create
(
:clusters_applications_prometheus
,
:installed
,
cluster:
cluster
)
}
let!
(
:get_command_values
)
{
OpenStruct
.
new
(
data:
OpenStruct
.
new
(
values:
application
.
values
))
}
let!
(
:upgrade_command
)
{
application
.
upgrade_command
(
""
)
}
let
(
:helm_client
)
{
instance_double
(
::
Gitlab
::
Kubernetes
::
Helm
::
Api
)
}
subject
(
:service
)
{
described_class
.
new
(
application
,
project
)
}
before
do
allow
(
service
).
to
receive
(
:upgrade_command
).
and_return
(
upgrade_command
)
allow
(
service
).
to
receive
(
:helm_api
).
and_return
(
helm_client
)
end
context
'when there are no errors'
do
before
do
expect
(
helm_client
).
to
receive
(
:get_config_map
).
and_return
(
get_command_values
)
expect
(
helm_client
).
to
receive
(
:update
).
with
(
upgrade_command
)
allow
(
::
ClusterWaitForAppUpdateWorker
).
to
receive
(
:perform_in
).
and_return
(
nil
)
end
context
'when prometheus alerts exist'
do
it
'generates the alert manager values'
do
create
(
:prometheus_alert
,
project:
project
,
environment:
environment
)
expect
(
service
).
to
receive
(
:generate_alert_manager
).
once
service
.
execute
end
end
context
'when prometheus alerts do not exist'
do
it
'resets the alert manager values'
do
expect
(
service
).
to
receive
(
:reset_alert_manager
).
once
service
.
execute
end
end
it
'make the application updating'
do
expect
(
application
.
cluster
).
not_to
be_nil
service
.
execute
expect
(
application
).
to
be_updating
end
it
'schedules async update status check'
do
expect
(
::
ClusterWaitForAppUpdateWorker
).
to
receive
(
:perform_in
).
once
service
.
execute
end
end
context
'when k8s cluster communication fails'
do
it
'make the application update errored'
do
error
=
::
Kubeclient
::
HttpError
.
new
(
500
,
'system failure'
,
nil
)
allow
(
helm_client
).
to
receive
(
:get_config_map
).
and_raise
(
error
)
service
.
execute
expect
(
application
).
to
be_update_errored
expect
(
application
.
status_reason
).
to
match
(
/kubernetes error:/i
)
end
end
context
'when application cannot be persisted'
do
let
(
:application
)
{
build
(
:clusters_applications_prometheus
,
:installed
)
}
it
'make the application update errored'
do
allow
(
application
).
to
receive
(
:make_updating!
).
once
.
and_raise
(
ActiveRecord
::
RecordInvalid
)
expect
(
helm_client
).
not_to
receive
(
:get_config_map
)
expect
(
helm_client
).
not_to
receive
(
:update
)
service
.
execute
expect
(
application
).
to
be_update_errored
end
end
end
end
ee/spec/services/clusters/applications/schedule_update_service_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
Clusters
::
Applications
::
ScheduleUpdateService
do
describe
'#execute'
do
let
(
:project
)
{
create
(
:project
)
}
around
do
|
example
|
Timecop
.
freeze
{
example
.
run
}
end
context
'when application is able to be updated'
do
context
'when the application was recently scheduled'
do
it
'schedules worker with a backoff delay'
do
application
=
create
(
:clusters_applications_prometheus
,
:installed
,
last_update_started_at:
Time
.
now
+
5
.
minutes
)
service
=
described_class
.
new
(
application
,
project
)
expect
(
::
ClusterUpdateAppWorker
).
to
receive
(
:perform_in
).
with
(
described_class
::
BACKOFF_DELAY
,
application
.
name
,
application
.
id
,
project
.
id
,
Time
.
now
).
once
service
.
execute
end
end
context
'when the application has not been recently updated'
do
it
'schedules worker'
do
application
=
create
(
:clusters_applications_prometheus
,
:installed
)
service
=
described_class
.
new
(
application
,
project
)
expect
(
::
ClusterUpdateAppWorker
).
to
receive
(
:perform_async
).
with
(
application
.
name
,
application
.
id
,
project
.
id
,
Time
.
now
).
once
service
.
execute
end
end
end
end
end
ee/spec/services/ee/notification_service_spec.rb
View file @
238c8c3e
...
@@ -238,6 +238,23 @@ describe EE::NotificationService, :mailer do
...
@@ -238,6 +238,23 @@ describe EE::NotificationService, :mailer do
end
end
end
end
describe
'#prometheus_alerts_fired'
do
it
'sends the email to owners and masters'
do
project
=
create
(
:project
)
prometheus_alert
=
create
(
:prometheus_alert
,
project:
project
)
master
=
create
(
:user
)
developer
=
create
(
:user
)
project
.
add_master
(
master
)
expect
(
Notify
).
to
receive
(
:prometheus_alert_fired_email
).
with
(
project
.
id
,
master
.
id
,
prometheus_alert
).
and_call_original
expect
(
Notify
).
to
receive
(
:prometheus_alert_fired_email
).
with
(
project
.
id
,
project
.
owner
.
id
,
prometheus_alert
).
and_call_original
expect
(
Notify
).
not_to
receive
(
:prometheus_alert_fired_email
).
with
(
project
.
id
,
developer
.
id
,
prometheus_alert
)
subject
.
prometheus_alerts_fired
(
prometheus_alert
.
project
,
[
prometheus_alert
])
end
end
describe
'Notes'
do
describe
'Notes'
do
around
do
|
example
|
around
do
|
example
|
perform_enqueued_jobs
do
perform_enqueued_jobs
do
...
...
ee/spec/services/projects/prometheus/metrics/destroy_service_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
Projects
::
Prometheus
::
Metrics
::
DestroyService
do
let
(
:metric
)
{
create
(
:prometheus_metric
)
}
subject
{
described_class
.
new
(
metric
)
}
it
'destroys metric'
do
subject
.
execute
expect
(
PrometheusMetric
.
find_by
(
id:
metric
.
id
)).
to
be_nil
end
context
'when metric has a prometheus alert associated'
do
it
'schedules a prometheus alert update'
do
create
(
:prometheus_alert
,
prometheus_metric:
metric
)
schedule_update_service
=
spy
allow
(
::
Clusters
::
Applications
::
ScheduleUpdateService
).
to
receive
(
:new
).
and_return
(
schedule_update_service
)
subject
.
execute
expect
(
schedule_update_service
).
to
have_received
(
:execute
)
end
end
end
ee/spec/services/projects/prometheus/metrics/update_service_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
Projects
::
Prometheus
::
Metrics
::
UpdateService
do
let
(
:metric
)
{
create
(
:prometheus_metric
)
}
it
'updates the prometheus metric'
do
expect
do
described_class
.
new
(
metric
,
{
title:
"bar"
}).
execute
end
.
to
change
{
metric
.
reload
.
title
}.
to
(
"bar"
)
end
context
'when metric has a prometheus alert associated'
do
let
(
:schedule_update_service
)
{
spy
}
before
do
create
(
:prometheus_alert
,
prometheus_metric:
metric
)
allow
(
::
Clusters
::
Applications
::
ScheduleUpdateService
).
to
receive
(
:new
).
and_return
(
schedule_update_service
)
end
context
'when updating title'
do
it
'schedules a prometheus alert update'
do
described_class
.
new
(
metric
,
{
title:
"bar"
}).
execute
expect
(
schedule_update_service
).
to
have_received
(
:execute
)
end
end
context
'when updating query'
do
it
'schedules a prometheus alert update'
do
described_class
.
new
(
metric
,
{
query:
"sum(bar)"
}).
execute
expect
(
schedule_update_service
).
to
have_received
(
:execute
)
end
end
it
'does not schedule a prometheus alert update without title nor query being changed'
do
described_class
.
new
(
metric
,
{
y_label:
"bar"
}).
execute
expect
(
schedule_update_service
).
not_to
have_received
(
:execute
)
end
end
end
ee/spec/workers/cluster_update_app_worker_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
ClusterUpdateAppWorker
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:prometheus_update_service
)
{
spy
}
subject
{
described_class
.
new
}
around
do
|
example
|
Timecop
.
freeze
(
Time
.
now
)
{
example
.
run
}
end
before
do
allow
(
::
Clusters
::
Applications
::
PrometheusUpdateService
).
to
receive
(
:new
).
and_return
(
prometheus_update_service
)
end
describe
'#perform'
do
context
'when the application last_update_started_at is higher than the time the job was scheduled in'
do
it
'does nothing'
do
application
=
create
(
:clusters_applications_prometheus
,
:updated
,
last_update_started_at:
Time
.
now
)
expect
(
prometheus_update_service
).
not_to
receive
(
:execute
)
expect
(
subject
.
perform
(
application
.
name
,
application
.
id
,
project
.
id
,
Time
.
now
-
5
.
minutes
)).
to
be_nil
end
end
context
'when another worker is already running'
do
it
'returns nil'
do
application
=
create
(
:clusters_applications_prometheus
,
:updating
)
expect
(
subject
.
perform
(
application
.
name
,
application
.
id
,
project
.
id
,
Time
.
now
)).
to
be_nil
end
end
it
'executes PrometheusUpdateService'
do
application
=
create
(
:clusters_applications_prometheus
,
:installed
)
expect
(
prometheus_update_service
).
to
receive
(
:execute
)
subject
.
perform
(
application
.
name
,
application
.
id
,
project
.
id
,
Time
.
now
)
end
end
end
ee/spec/workers/cluster_wait_for_app_update_worker_spec.rb
0 → 100644
View file @
238c8c3e
require
'spec_helper'
describe
ClusterWaitForAppUpdateWorker
do
let
(
:check_upgrade_progress_service
)
{
spy
}
before
do
allow
(
::
Clusters
::
Applications
::
CheckUpgradeProgressService
).
to
receive
(
:new
).
and_return
(
check_upgrade_progress_service
)
end
it
'runs CheckUpgradeProgressService when application is found'
do
application
=
create
(
:clusters_applications_prometheus
)
expect
(
check_upgrade_progress_service
).
to
receive
(
:execute
)
subject
.
perform
(
application
.
name
,
application
.
id
)
end
it
'does not run CheckUpgradeProgressService when application is not found'
do
expect
(
check_upgrade_progress_service
).
not_to
receive
(
:execute
)
expect
do
subject
.
perform
(
"prometheus"
,
-
1
)
end
.
to
raise_error
(
ActiveRecord
::
RecordNotFound
)
end
end
lib/gitlab/kubernetes/config_map.rb
View file @
238c8c3e
module
Gitlab
module
Gitlab
module
Kubernetes
module
Kubernetes
class
ConfigMap
class
ConfigMap
def
initialize
(
name
,
values
)
def
initialize
(
name
,
values
=
""
)
@name
=
name
@name
=
name
@values
=
values
@values
=
values
end
end
...
@@ -13,6 +13,10 @@ module Gitlab
...
@@ -13,6 +13,10 @@ module Gitlab
resource
resource
end
end
def
config_map_name
"values-content-configuration-
#{
name
}
"
end
private
private
attr_reader
:name
,
:values
attr_reader
:name
,
:values
...
@@ -25,10 +29,6 @@ module Gitlab
...
@@ -25,10 +29,6 @@ module Gitlab
}
}
end
end
def
config_map_name
"values-content-configuration-
#{
name
}
"
end
def
namespace
def
namespace
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
end
end
...
...
lib/gitlab/kubernetes/helm/api.rb
View file @
238c8c3e
...
@@ -2,15 +2,17 @@ module Gitlab
...
@@ -2,15 +2,17 @@ module Gitlab
module
Kubernetes
module
Kubernetes
module
Helm
module
Helm
class
Api
class
Api
prepend
EE
::
Gitlab
::
Kubernetes
::
Helm
::
Api
def
initialize
(
kubeclient
)
def
initialize
(
kubeclient
)
@kubeclient
=
kubeclient
@kubeclient
=
kubeclient
@namespace
=
Gitlab
::
Kubernetes
::
Namespace
.
new
(
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
,
kubeclient
)
@namespace
=
Gitlab
::
Kubernetes
::
Namespace
.
new
(
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
,
kubeclient
)
end
end
def
install
(
command
)
def
install
(
command
)
@
namespace
.
ensure_exists!
namespace
.
ensure_exists!
create_config_map
(
command
)
if
command
.
config_map?
create_config_map
(
command
)
if
command
.
config_map?
@
kubeclient
.
create_pod
(
command
.
pod_resource
)
kubeclient
.
create_pod
(
command
.
pod_resource
)
end
end
##
##
...
@@ -20,23 +22,25 @@ module Gitlab
...
@@ -20,23 +22,25 @@ module Gitlab
#
#
# values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
# values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
#
#
def
installation_
status
(
pod_name
)
def
status
(
pod_name
)
@kubeclient
.
get_pod
(
pod_name
,
@
namespace
.
name
).
status
.
phase
kubeclient
.
get_pod
(
pod_name
,
namespace
.
name
).
status
.
phase
end
end
def
installation_
log
(
pod_name
)
def
log
(
pod_name
)
@kubeclient
.
get_pod_log
(
pod_name
,
@
namespace
.
name
).
body
kubeclient
.
get_pod_log
(
pod_name
,
namespace
.
name
).
body
end
end
def
delete_
installation_
pod!
(
pod_name
)
def
delete_pod!
(
pod_name
)
@kubeclient
.
delete_pod
(
pod_name
,
@
namespace
.
name
)
kubeclient
.
delete_pod
(
pod_name
,
namespace
.
name
)
end
end
private
private
attr_reader
:kubeclient
,
:namespace
def
create_config_map
(
command
)
def
create_config_map
(
command
)
command
.
config_map_resource
.
tap
do
|
config_map_resource
|
command
.
config_map_resource
.
tap
do
|
config_map_resource
|
@
kubeclient
.
create_config_map
(
config_map_resource
)
kubeclient
.
create_config_map
(
config_map_resource
)
end
end
end
end
end
end
...
...
lib/gitlab/prometheus/metric.rb
View file @
238c8c3e
...
@@ -3,7 +3,7 @@ module Gitlab
...
@@ -3,7 +3,7 @@ module Gitlab
class
Metric
class
Metric
include
ActiveModel
::
Model
include
ActiveModel
::
Model
attr_accessor
:title
,
:required_metrics
,
:weight
,
:y_label
,
:queries
attr_accessor
:
id
,
:
title
,
:required_metrics
,
:weight
,
:y_label
,
:queries
validates
:title
,
:required_metrics
,
:weight
,
:y_label
,
:queries
,
presence:
true
validates
:title
,
:required_metrics
,
:weight
,
:y_label
,
:queries
,
presence:
true
...
...
lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
View file @
238c8c3e
...
@@ -8,6 +8,7 @@ module Gitlab
...
@@ -8,6 +8,7 @@ module Gitlab
Deployment
.
find_by
(
id:
deployment_id
).
try
do
|
deployment
|
Deployment
.
find_by
(
id:
deployment_id
).
try
do
|
deployment
|
query_metrics
(
query_metrics
(
deployment
.
project
,
deployment
.
project
,
deployment
.
environment
,
common_query_context
(
common_query_context
(
deployment
.
environment
,
deployment
.
environment
,
timeframe_start:
(
deployment
.
created_at
-
30
.
minutes
).
to_f
,
timeframe_start:
(
deployment
.
created_at
-
30
.
minutes
).
to_f
,
...
...
lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
View file @
238c8c3e
...
@@ -8,6 +8,7 @@ module Gitlab
...
@@ -8,6 +8,7 @@ module Gitlab
::
Environment
.
find_by
(
id:
environment_id
).
try
do
|
environment
|
::
Environment
.
find_by
(
id:
environment_id
).
try
do
|
environment
|
query_metrics
(
query_metrics
(
environment
.
project
,
environment
.
project
,
environment
,
common_query_context
(
environment
,
timeframe_start:
8
.
hours
.
ago
.
to_f
,
timeframe_end:
Time
.
now
.
to_f
)
common_query_context
(
environment
,
timeframe_start:
8
.
hours
.
ago
.
to_f
,
timeframe_end:
Time
.
now
.
to_f
)
)
)
end
end
...
...
lib/gitlab/prometheus/queries/query_additional_metrics.rb
View file @
238c8c3e
...
@@ -2,7 +2,9 @@ module Gitlab
...
@@ -2,7 +2,9 @@ module Gitlab
module
Prometheus
module
Prometheus
module
Queries
module
Queries
module
QueryAdditionalMetrics
module
QueryAdditionalMetrics
def
query_metrics
(
project
,
query_context
)
prepend
EE
::
Gitlab
::
Prometheus
::
Queries
::
QueryAdditionalMetrics
def
query_metrics
(
project
,
environment
,
query_context
)
matched_metrics
(
project
).
map
(
&
query_group
(
query_context
))
matched_metrics
(
project
).
map
(
&
query_group
(
query_context
))
.
select
(
&
method
(
:group_with_any_metrics
))
.
select
(
&
method
(
:group_with_any_metrics
))
end
end
...
@@ -14,12 +16,16 @@ module Gitlab
...
@@ -14,12 +16,16 @@ module Gitlab
lambda
do
|
group
|
lambda
do
|
group
|
metrics
=
group
.
metrics
.
map
do
|
metric
|
metrics
=
group
.
metrics
.
map
do
|
metric
|
{
metric_hsh
=
{
title:
metric
.
title
,
title:
metric
.
title
,
weight:
metric
.
weight
,
weight:
metric
.
weight
,
y_label:
metric
.
y_label
,
y_label:
metric
.
y_label
,
queries:
metric
.
queries
.
map
(
&
query_processor
).
select
(
&
method
(
:query_with_result
))
queries:
metric
.
queries
.
map
(
&
query_processor
).
select
(
&
method
(
:query_with_result
))
}
}
metric_hsh
[
:id
]
=
metric
.
id
if
metric
.
id
metric_hsh
end
end
{
{
...
...
locale/gitlab.pot
View file @
238c8c3e
...
@@ -5060,6 +5060,36 @@ msgstr ""
...
@@ -5060,6 +5060,36 @@ msgstr ""
msgid "ProjectsDropdown|Sorry, no projects matched your search"
msgid "ProjectsDropdown|Sorry, no projects matched your search"
msgstr ""
msgstr ""
msgid "PrometheusAlerts|Add alert"
msgstr ""
msgid "PrometheusAlerts|Alert set"
msgstr ""
msgid "PrometheusAlerts|Edit alert"
msgstr ""
msgid "PrometheusAlerts|Error creating alert"
msgstr ""
msgid "PrometheusAlerts|Error deleting alert"
msgstr ""
msgid "PrometheusAlerts|Error fetching alert"
msgstr ""
msgid "PrometheusAlerts|Error saving alert"
msgstr ""
msgid "PrometheusAlerts|No alert set"
msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
msgid "PrometheusAlerts|Threshold"
msgstr ""
msgid "PrometheusDashboard|Time"
msgid "PrometheusDashboard|Time"
msgstr ""
msgstr ""
...
@@ -6227,7 +6257,7 @@ msgstr ""
...
@@ -6227,7 +6257,7 @@ msgstr ""
msgid "This application will be able to:"
msgid "This application will be able to:"
msgstr ""
msgstr ""
msgid "This board
\\
's scope is reduced"
msgid "This board's scope is reduced"
msgstr ""
msgstr ""
msgid "This diff is collapsed."
msgid "This diff is collapsed."
...
...
spec/factories/clusters/applications/helm.rb
View file @
238c8c3e
...
@@ -22,11 +22,24 @@ FactoryBot.define do
...
@@ -22,11 +22,24 @@ FactoryBot.define do
status
3
status
3
end
end
trait
:updating
do
status
4
end
trait
:updated
do
status
5
end
trait
:errored
do
trait
:errored
do
status
(
-
1
)
status
(
-
1
)
status_reason
'something went wrong'
status_reason
'something went wrong'
end
end
trait
:update_errored
do
status
(
6
)
status_reason
'something went wrong'
end
trait
:timeouted
do
trait
:timeouted
do
installing
installing
updated_at
ClusterWaitForAppInstallationWorker
::
TIMEOUT
.
ago
updated_at
ClusterWaitForAppInstallationWorker
::
TIMEOUT
.
ago
...
...
spec/javascripts/monitoring/alert_widget_form_spec.js
0 → 100644
View file @
238c8c3e
import
Vue
from
'
vue
'
;
import
AlertWidgetForm
from
'
ee/monitoring/components/alert_widget_form.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
describe
(
'
AlertWidgetForm
'
,
()
=>
{
let
AlertWidgetFormComponent
;
let
vm
;
const
props
=
{
disabled
:
false
,
};
beforeAll
(()
=>
{
AlertWidgetFormComponent
=
Vue
.
extend
(
AlertWidgetForm
);
});
afterEach
(()
=>
{
if
(
vm
)
vm
.
$destroy
();
});
it
(
'
disables the input when disabled prop is set
'
,
()
=>
{
vm
=
mountComponent
(
AlertWidgetFormComponent
,
{
...
props
,
disabled
:
true
});
expect
(
vm
.
$refs
.
cancelButton
).
toBeDisabled
();
expect
(
vm
.
$refs
.
submitButton
).
toBeDisabled
();
});
it
(
'
emits a "create" event when form submitted without existing alert
'
,
done
=>
{
vm
=
mountComponent
(
AlertWidgetFormComponent
,
props
);
expect
(
vm
.
$refs
.
submitButton
.
innerText
).
toContain
(
'
Add
'
);
vm
.
$once
(
'
create
'
,
alert
=>
{
expect
(
alert
).
toEqual
({
alert
:
null
,
operator
:
'
<
'
,
threshold
:
5
,
});
done
();
});
// the button should be disabled until an operator and threshold are selected
expect
(
vm
.
$refs
.
submitButton
).
toBeDisabled
();
vm
.
operator
=
'
<
'
;
vm
.
threshold
=
5
;
Vue
.
nextTick
(()
=>
{
vm
.
$refs
.
submitButton
.
click
();
});
});
it
(
'
emits a "delete" event when form submitted with existing alert and no changes are made
'
,
done
=>
{
vm
=
mountComponent
(
AlertWidgetFormComponent
,
{
...
props
,
alert
:
'
alert
'
,
alertData
:
{
operator
:
'
<
'
,
threshold
:
5
},
});
vm
.
$once
(
'
delete
'
,
alert
=>
{
expect
(
alert
).
toEqual
({
alert
:
'
alert
'
,
operator
:
'
<
'
,
threshold
:
5
,
});
done
();
});
expect
(
vm
.
$refs
.
submitButton
.
innerText
).
toContain
(
'
Delete
'
);
vm
.
$refs
.
submitButton
.
click
();
});
it
(
'
emits a "update" event when form submitted with existing alert
'
,
done
=>
{
vm
=
mountComponent
(
AlertWidgetFormComponent
,
{
...
props
,
alert
:
'
alert
'
,
alertData
:
{
operator
:
'
<
'
,
threshold
:
5
},
});
expect
(
vm
.
$refs
.
submitButton
.
innerText
).
toContain
(
'
Delete
'
);
vm
.
$once
(
'
update
'
,
alert
=>
{
expect
(
alert
).
toEqual
({
alert
:
'
alert
'
,
operator
:
'
=
'
,
threshold
:
5
,
});
done
();
});
// change operator to allow update
vm
.
operator
=
'
=
'
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$refs
.
submitButton
.
innerText
).
toContain
(
'
Save
'
);
vm
.
$refs
.
submitButton
.
click
();
});
});
});
spec/javascripts/monitoring/alert_widget_spec.js
0 → 100644
View file @
238c8c3e
import
Vue
from
'
vue
'
;
import
AlertWidget
from
'
ee/monitoring/components/alert_widget.vue
'
;
import
AlertsService
from
'
ee/monitoring/services/alerts_service
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
describe
(
'
AlertWidget
'
,
()
=>
{
let
AlertWidgetComponent
;
let
vm
;
const
props
=
{
alertsEndpoint
:
''
,
customMetricId
:
5
,
label
:
'
alert-label
'
,
currentAlerts
:
[
'
my/alert.json
'
],
};
beforeAll
(()
=>
{
AlertWidgetComponent
=
Vue
.
extend
(
AlertWidget
);
});
beforeEach
(()
=>
{
setFixtures
(
'
<div id="alert-widget"></div>
'
);
});
afterEach
(()
=>
{
if
(
vm
)
vm
.
$destroy
();
});
it
(
'
displays a loading spinner when fetching alerts
'
,
done
=>
{
let
resolveReadAlert
;
spyOn
(
AlertsService
.
prototype
,
'
readAlert
'
).
and
.
returnValue
(
new
Promise
(
cb
=>
{
resolveReadAlert
=
cb
;
}),
);
vm
=
mountComponent
(
AlertWidgetComponent
,
props
,
'
#alert-widget
'
);
// expect loading spinner to exist during fetch
expect
(
vm
.
isLoading
).
toBeTruthy
();
expect
(
vm
.
$el
.
querySelector
(
'
.loading-container
'
)).
toBeVisible
();
resolveReadAlert
({
operator
:
'
=
'
,
threshold
:
42
});
// expect loading spinner to go away after fetch
setTimeout
(()
=>
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
isLoading
).
toEqual
(
false
);
expect
(
vm
.
$el
.
querySelector
(
'
.loading-container
'
)).
toBeHidden
();
done
();
}),
);
});
it
(
'
displays an error message when fetch fails
'
,
done
=>
{
spyOn
(
AlertsService
.
prototype
,
'
readAlert
'
).
and
.
returnValue
(
Promise
.
reject
());
vm
=
mountComponent
(
AlertWidgetComponent
,
props
,
'
#alert-widget
'
);
setTimeout
(()
=>
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
errorMessage
).
toBe
(
'
Error fetching alert
'
);
expect
(
vm
.
isLoading
).
toEqual
(
false
);
expect
(
vm
.
$el
.
querySelector
(
'
.alert-error-message
'
)).
toBeVisible
();
done
();
}),
);
});
it
(
'
displays an alert summary when fetch succeeds
'
,
done
=>
{
spyOn
(
AlertsService
.
prototype
,
'
readAlert
'
).
and
.
returnValue
(
Promise
.
resolve
({
operator
:
'
>
'
,
threshold
:
42
}),
);
vm
=
mountComponent
(
AlertWidgetComponent
,
props
,
'
#alert-widget
'
);
setTimeout
(()
=>
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
isLoading
).
toEqual
(
false
);
expect
(
vm
.
alertSummary
).
toBe
(
'
alert-label > 42
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.alert-current-setting
'
)).
toBeVisible
();
done
();
}),
);
});
it
(
'
opens and closes a dropdown menu by clicking close button
'
,
done
=>
{
vm
=
mountComponent
(
AlertWidgetComponent
,
{
...
props
,
currentAlerts
:
[]
});
expect
(
vm
.
isOpen
).
toEqual
(
false
);
expect
(
vm
.
$el
.
querySelector
(
'
.alert-dropdown-menu
'
)).
toBeHidden
();
vm
.
$el
.
querySelector
(
'
.alert-dropdown-button
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
isOpen
).
toEqual
(
true
);
expect
(
vm
.
$el
).
toHaveClass
(
'
show
'
);
vm
.
$el
.
querySelector
(
'
.dropdown-menu-close
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
isOpen
).
toEqual
(
false
);
expect
(
vm
.
$el
).
not
.
toHaveClass
(
'
show
'
);
done
();
});
});
});
it
(
'
opens and closes a dropdown menu by clicking outside the menu
'
,
done
=>
{
vm
=
mountComponent
(
AlertWidgetComponent
,
{
...
props
,
currentAlerts
:
[]
});
expect
(
vm
.
isOpen
).
toEqual
(
false
);
expect
(
vm
.
$el
.
querySelector
(
'
.alert-dropdown-menu
'
)).
toBeHidden
();
vm
.
$el
.
querySelector
(
'
.alert-dropdown-button
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
isOpen
).
toEqual
(
true
);
expect
(
vm
.
$el
).
toHaveClass
(
'
show
'
);
document
.
body
.
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
isOpen
).
toEqual
(
false
);
expect
(
vm
.
$el
).
not
.
toHaveClass
(
'
show
'
);
done
();
});
});
});
it
(
'
creates an alert with an appropriate handler
'
,
done
=>
{
const
alertParams
=
{
operator
:
'
<
'
,
threshold
:
4
,
prometheus_metric_id
:
5
,
};
spyOn
(
AlertsService
.
prototype
,
'
createAlert
'
).
and
.
returnValue
(
Promise
.
resolve
({
alert_path
:
'
foo/bar
'
,
...
alertParams
,
}),
);
vm
=
mountComponent
(
AlertWidgetComponent
,
{
...
props
,
currentAlerts
:
[]
});
vm
.
$refs
.
widgetForm
.
$emit
(
'
create
'
,
alertParams
);
expect
(
AlertsService
.
prototype
.
createAlert
).
toHaveBeenCalledWith
(
alertParams
);
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
isLoading
).
toEqual
(
false
);
expect
(
vm
.
alertSummary
).
toBe
(
'
alert-label < 4
'
);
done
();
});
});
it
(
'
updates an alert with an appropriate handler
'
,
done
=>
{
const
alertPath
=
'
my/test/alert.json
'
;
const
alertParams
=
{
operator
:
'
<
'
,
threshold
:
4
,
};
spyOn
(
AlertsService
.
prototype
,
'
readAlert
'
).
and
.
returnValue
(
Promise
.
resolve
(
alertParams
));
spyOn
(
AlertsService
.
prototype
,
'
updateAlert
'
).
and
.
returnValue
(
Promise
.
resolve
());
vm
=
mountComponent
(
AlertWidgetComponent
,
{
...
props
,
currentAlerts
:
[
alertPath
]
});
vm
.
$refs
.
widgetForm
.
$emit
(
'
update
'
,
{
...
alertParams
,
alert
:
alertPath
,
operator
:
'
=
'
,
threshold
:
12
,
});
expect
(
AlertsService
.
prototype
.
updateAlert
).
toHaveBeenCalledWith
(
alertPath
,
{
...
alertParams
,
operator
:
'
=
'
,
threshold
:
12
,
});
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
isLoading
).
toEqual
(
false
);
expect
(
vm
.
alertSummary
).
toBe
(
'
alert-label = 12
'
);
done
();
});
});
it
(
'
deletes an alert with an appropriate handler
'
,
done
=>
{
const
alertPath
=
'
my/test/alert.json
'
;
const
alertParams
=
{
operator
:
'
<
'
,
threshold
:
4
,
};
spyOn
(
AlertsService
.
prototype
,
'
readAlert
'
).
and
.
returnValue
(
Promise
.
resolve
(
alertParams
));
spyOn
(
AlertsService
.
prototype
,
'
deleteAlert
'
).
and
.
returnValue
(
Promise
.
resolve
());
vm
=
mountComponent
(
AlertWidgetComponent
,
{
...
props
,
currentAlerts
:
[
alertPath
]
});
vm
.
$refs
.
widgetForm
.
$emit
(
'
delete
'
,
{
alert
:
alertPath
});
expect
(
AlertsService
.
prototype
.
deleteAlert
).
toHaveBeenCalledWith
(
alertPath
);
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
isLoading
).
toEqual
(
false
);
expect
(
vm
.
alertSummary
).
toBeFalsy
();
done
();
});
});
});
spec/javascripts/test_bundle.js
View file @
238c8c3e
...
@@ -91,6 +91,19 @@ beforeEach(() => {
...
@@ -91,6 +91,19 @@ beforeEach(() => {
Vue
.
http
.
interceptors
=
builtinVueHttpInterceptors
.
slice
();
Vue
.
http
.
interceptors
=
builtinVueHttpInterceptors
.
slice
();
});
});
let
longRunningTestTimeoutHandle
;
beforeEach
((
done
)
=>
{
longRunningTestTimeoutHandle
=
setTimeout
(()
=>
{
done
.
fail
(
'
Test is running too long!
'
);
},
1000
);
done
();
});
afterEach
(()
=>
{
clearTimeout
(
longRunningTestTimeoutHandle
);
});
const
axiosDefaultAdapter
=
getDefaultAdapter
();
const
axiosDefaultAdapter
=
getDefaultAdapter
();
// render all of our tests
// render all of our tests
...
...
spec/javascripts/vue_shared/components/reports/report_section_spec.js
View file @
238c8c3e
...
@@ -23,7 +23,7 @@ describe('Report section', () => {
...
@@ -23,7 +23,7 @@ describe('Report section', () => {
describe
(
'
computed
'
,
()
=>
{
describe
(
'
computed
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
vm
=
mountComponent
(
ReportSection
,
{
type
:
'
codequality
'
,
component
:
'
'
,
status
:
'
SUCCESS
'
,
status
:
'
SUCCESS
'
,
loadingText
:
'
Loading codeclimate report
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
errorText
:
'
foo
'
,
...
@@ -89,7 +89,7 @@ describe('Report section', () => {
...
@@ -89,7 +89,7 @@ describe('Report section', () => {
describe
(
'
when it is loading
'
,
()
=>
{
describe
(
'
when it is loading
'
,
()
=>
{
it
(
'
should render loading indicator
'
,
()
=>
{
it
(
'
should render loading indicator
'
,
()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
vm
=
mountComponent
(
ReportSection
,
{
type
:
'
codequality
'
,
component
:
'
'
,
status
:
'
LOADING
'
,
status
:
'
LOADING
'
,
loadingText
:
'
Loading codeclimate report
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
errorText
:
'
foo
'
,
...
@@ -103,7 +103,7 @@ describe('Report section', () => {
...
@@ -103,7 +103,7 @@ describe('Report section', () => {
describe
(
'
with success status
'
,
()
=>
{
describe
(
'
with success status
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
vm
=
mountComponent
(
ReportSection
,
{
type
:
'
codequality
'
,
component
:
'
'
,
status
:
'
SUCCESS
'
,
status
:
'
SUCCESS
'
,
loadingText
:
'
Loading codeclimate report
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
errorText
:
'
foo
'
,
...
@@ -161,7 +161,7 @@ describe('Report section', () => {
...
@@ -161,7 +161,7 @@ describe('Report section', () => {
describe
(
'
with failed request
'
,
()
=>
{
describe
(
'
with failed request
'
,
()
=>
{
it
(
'
should render error indicator
'
,
()
=>
{
it
(
'
should render error indicator
'
,
()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
vm
=
mountComponent
(
ReportSection
,
{
type
:
'
codequality
'
,
component
:
'
'
,
status
:
'
ERROR
'
,
status
:
'
ERROR
'
,
loadingText
:
'
Loading codeclimate report
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
Failed to load codeclimate report
'
,
errorText
:
'
Failed to load codeclimate report
'
,
...
@@ -171,87 +171,4 @@ describe('Report section', () => {
...
@@ -171,87 +171,4 @@ describe('Report section', () => {
expect
(
vm
.
$el
.
textContent
.
trim
()).
toEqual
(
'
Failed to load codeclimate report
'
);
expect
(
vm
.
$el
.
textContent
.
trim
()).
toEqual
(
'
Failed to load codeclimate report
'
);
});
});
});
});
describe
(
'
With full report
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
status
:
'
SUCCESS
'
,
successText
:
'
SAST improved on 1 security vulnerability and degraded on 1 security vulnerability
'
,
type
:
'
SAST
'
,
errorText
:
'
Failed to load security report
'
,
hasIssues
:
true
,
loadingText
:
'
Loading security report
'
,
resolvedIssues
:
[
{
cve
:
'
CVE-2016-9999
'
,
file
:
'
Gemfile.lock
'
,
message
:
'
Test Information Leak Vulnerability in Action View
'
,
title
:
'
Test Information Leak Vulnerability in Action View
'
,
path
:
'
Gemfile.lock
'
,
solution
:
'
upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1
'
,
tool
:
'
bundler_audit
'
,
url
:
'
https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00
'
,
urlPath
:
'
/Gemfile.lock
'
,
},
],
unresolvedIssues
:
[
{
cve
:
'
CVE-2014-7829
'
,
file
:
'
Gemfile.lock
'
,
message
:
'
Arbitrary file existence disclosure in Action Pack
'
,
title
:
'
Arbitrary file existence disclosure in Action Pack
'
,
path
:
'
Gemfile.lock
'
,
solution
:
'
upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8
'
,
tool
:
'
bundler_audit
'
,
url
:
'
https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk
'
,
urlPath
:
'
/Gemfile.lock
'
,
},
],
allIssues
:
[
{
cve
:
'
CVE-2016-0752
'
,
file
:
'
Gemfile.lock
'
,
message
:
'
Possible Information Leak Vulnerability in Action View
'
,
title
:
'
Possible Information Leak Vulnerability in Action View
'
,
path
:
'
Gemfile.lock
'
,
solution
:
'
upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1
'
,
tool
:
'
bundler_audit
'
,
url
:
'
https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00
'
,
urlPath
:
'
/Gemfile.lock
'
,
},
],
});
});
it
(
'
should render full report section
'
,
done
=>
{
vm
.
$el
.
querySelector
(
'
button
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-expand-full-list
'
).
textContent
.
trim
()).
toEqual
(
'
Show complete code vulnerabilities report
'
,
);
done
();
});
});
it
(
'
should expand full list when clicked and hide the show all button
'
,
done
=>
{
vm
.
$el
.
querySelector
(
'
button
'
).
click
();
Vue
.
nextTick
(()
=>
{
vm
.
$el
.
querySelector
(
'
.js-expand-full-list
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-mr-code-all-issues
'
).
textContent
.
trim
()).
toContain
(
'
Possible Information Leak Vulnerability in Action View
'
,
);
done
();
});
});
});
});
});
});
spec/javascripts/vue_shared/security_reports/components/sast_issue_body_spec.js
View file @
238c8c3e
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
component
from
'
ee/vue_shared/security_reports/components/sast_issue_body.vue
'
;
import
component
from
'
ee/vue_shared/security_reports/components/sast_issue_body.vue
'
;
import
mountComponent
from
'
../../../helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
STATUS_FAILED
}
from
'
~/vue_shared/components/reports/constants
'
;
describe
(
'
sast issue body
'
,
()
=>
{
describe
(
'
sast issue body
'
,
()
=>
{
let
vm
;
let
vm
;
...
@@ -23,7 +24,7 @@ describe('sast issue body', () => {
...
@@ -23,7 +24,7 @@ describe('sast issue body', () => {
confidence
:
'
Low
'
,
confidence
:
'
Low
'
,
};
};
const
status
=
'
failed
'
;
const
status
=
STATUS_FAILED
;
afterEach
(()
=>
{
afterEach
(()
=>
{
vm
.
$destroy
();
vm
.
$destroy
();
...
...
spec/lib/gitlab/import_export/all_models.yml
View file @
238c8c3e
...
@@ -333,6 +333,7 @@ project:
...
@@ -333,6 +333,7 @@ project:
-
ci_cd_settings
-
ci_cd_settings
-
import_export_upload
-
import_export_upload
-
vulnerability_feedback
-
vulnerability_feedback
-
prometheus_alerts
award_emoji
:
award_emoji
:
-
awardable
-
awardable
-
user
-
user
...
@@ -340,6 +341,7 @@ priorities:
...
@@ -340,6 +341,7 @@ priorities:
-
label
-
label
prometheus_metrics
:
prometheus_metrics
:
-
project
-
project
-
prometheus_alert
timelogs
:
timelogs
:
-
issue
-
issue
-
merge_request
-
merge_request
...
...
spec/lib/gitlab/kubernetes/config_map_spec.rb
View file @
238c8c3e
...
@@ -22,4 +22,10 @@ describe Gitlab::Kubernetes::ConfigMap do
...
@@ -22,4 +22,10 @@ describe Gitlab::Kubernetes::ConfigMap do
is_expected
.
to
eq
(
resource
)
is_expected
.
to
eq
(
resource
)
end
end
end
end
describe
'#config_map_name'
do
it
'returns the config_map name'
do
expect
(
config_map
.
config_map_name
).
to
eq
(
"values-content-configuration-
#{
application
.
name
}
"
)
end
end
end
end
spec/lib/gitlab/kubernetes/helm/api_spec.rb
View file @
238c8c3e
...
@@ -49,33 +49,33 @@ describe Gitlab::Kubernetes::Helm::Api do
...
@@ -49,33 +49,33 @@ describe Gitlab::Kubernetes::Helm::Api do
end
end
end
end
describe
'#
installation_
status'
do
describe
'#status'
do
let
(
:phase
)
{
Gitlab
::
Kubernetes
::
Pod
::
RUNNING
}
let
(
:phase
)
{
Gitlab
::
Kubernetes
::
Pod
::
RUNNING
}
let
(
:pod
)
{
Kubeclient
::
Resource
.
new
(
status:
{
phase:
phase
})
}
# partial representation
let
(
:pod
)
{
Kubeclient
::
Resource
.
new
(
status:
{
phase:
phase
})
}
# partial representation
it
'fetches POD phase from kubernetes cluster'
do
it
'fetches POD phase from kubernetes cluster'
do
expect
(
client
).
to
receive
(
:get_pod
).
with
(
command
.
pod_name
,
gitlab_namespace
).
once
.
and_return
(
pod
)
expect
(
client
).
to
receive
(
:get_pod
).
with
(
command
.
pod_name
,
gitlab_namespace
).
once
.
and_return
(
pod
)
expect
(
subject
.
installation_
status
(
command
.
pod_name
)).
to
eq
(
phase
)
expect
(
subject
.
status
(
command
.
pod_name
)).
to
eq
(
phase
)
end
end
end
end
describe
'#
installation_
log'
do
describe
'#log'
do
let
(
:log
)
{
'some output'
}
let
(
:log
)
{
'some output'
}
let
(
:response
)
{
RestClient
::
Response
.
new
(
log
)
}
let
(
:response
)
{
RestClient
::
Response
.
new
(
log
)
}
it
'fetches POD phase from kubernetes cluster'
do
it
'fetches POD phase from kubernetes cluster'
do
expect
(
client
).
to
receive
(
:get_pod_log
).
with
(
command
.
pod_name
,
gitlab_namespace
).
once
.
and_return
(
response
)
expect
(
client
).
to
receive
(
:get_pod_log
).
with
(
command
.
pod_name
,
gitlab_namespace
).
once
.
and_return
(
response
)
expect
(
subject
.
installation_
log
(
command
.
pod_name
)).
to
eq
(
log
)
expect
(
subject
.
log
(
command
.
pod_name
)).
to
eq
(
log
)
end
end
end
end
describe
'#delete_
installation_
pod!'
do
describe
'#delete_pod!'
do
it
'deletes the POD from kubernetes cluster'
do
it
'deletes the POD from kubernetes cluster'
do
expect
(
client
).
to
receive
(
:delete_pod
).
with
(
command
.
pod_name
,
gitlab_namespace
).
once
expect
(
client
).
to
receive
(
:delete_pod
).
with
(
command
.
pod_name
,
gitlab_namespace
).
once
subject
.
delete_
installation_
pod!
(
command
.
pod_name
)
subject
.
delete_pod!
(
command
.
pod_name
)
end
end
end
end
end
end
spec/models/clusters/applications/prometheus_spec.rb
View file @
238c8c3e
...
@@ -34,6 +34,47 @@ describe Clusters::Applications::Prometheus do
...
@@ -34,6 +34,47 @@ describe Clusters::Applications::Prometheus do
end
end
end
end
describe
'#ready'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:cluster
)
{
create
(
:cluster
,
projects:
[
project
])
}
it
'returns true when installed'
do
application
=
build
(
:clusters_applications_prometheus
,
:installed
,
cluster:
cluster
)
expect
(
application
).
to
be_ready
end
it
'returns false when not_installable'
do
application
=
build
(
:clusters_applications_prometheus
,
:not_installable
,
cluster:
cluster
)
expect
(
application
).
not_to
be_ready
end
it
'returns false when installable'
do
application
=
build
(
:clusters_applications_prometheus
,
:installable
,
cluster:
cluster
)
expect
(
application
).
not_to
be_ready
end
it
'returns false when scheduled'
do
application
=
build
(
:clusters_applications_prometheus
,
:scheduled
,
cluster:
cluster
)
expect
(
application
).
not_to
be_ready
end
it
'returns false when installing'
do
application
=
build
(
:clusters_applications_prometheus
,
:installing
,
cluster:
cluster
)
expect
(
application
).
not_to
be_ready
end
it
'returns false when errored'
do
application
=
build
(
:clusters_applications_prometheus
,
:errored
,
cluster:
cluster
)
expect
(
application
).
not_to
be_ready
end
end
describe
'#prometheus_client'
do
describe
'#prometheus_client'
do
context
'cluster is nil'
do
context
'cluster is nil'
do
it
'returns nil'
do
it
'returns nil'
do
...
@@ -102,15 +143,17 @@ describe Clusters::Applications::Prometheus do
...
@@ -102,15 +143,17 @@ describe Clusters::Applications::Prometheus do
let
(
:kubeclient
)
{
double
(
'kubernetes client'
)
}
let
(
:kubeclient
)
{
double
(
'kubernetes client'
)
}
let
(
:prometheus
)
{
create
(
:clusters_applications_prometheus
)
}
let
(
:prometheus
)
{
create
(
:clusters_applications_prometheus
)
}
subject
{
prometheus
.
install_command
}
it
'returns an instance of Gitlab::Kubernetes::Helm::InstallCommand'
do
expect
(
prometheus
.
install_command
).
to
be_an_instance_of
(
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
)
it
{
is_expected
.
to
be_an_instance_of
(
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
)
}
end
it
'should be initialized with 3 arguments'
do
it
'should be initialized with 3 arguments'
do
expect
(
subject
.
name
).
to
eq
(
'prometheus'
)
command
=
prometheus
.
install_command
expect
(
subject
.
chart
).
to
eq
(
'stable/prometheus'
)
expect
(
subject
.
version
).
to
eq
(
'6.7.3'
)
expect
(
command
.
name
).
to
eq
(
'prometheus'
)
expect
(
subject
.
values
).
to
eq
(
prometheus
.
values
)
expect
(
command
.
chart
).
to
eq
(
'stable/prometheus'
)
expect
(
command
.
version
).
to
eq
(
'6.7.3'
)
expect
(
command
.
values
).
to
eq
(
prometheus
.
values
)
end
end
end
end
...
...
spec/support/prometheus/additional_metrics_shared_examples.rb
View file @
238c8c3e
...
@@ -25,7 +25,7 @@ RSpec.shared_examples 'additional metrics query' do
...
@@ -25,7 +25,7 @@ RSpec.shared_examples 'additional metrics query' do
shared_examples
'query context containing environment slug and filter'
do
shared_examples
'query context containing environment slug and filter'
do
it
'contains ci_environment_slug'
do
it
'contains ci_environment_slug'
do
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
project
,
hash_including
(
ci_environment_slug:
environment
.
slug
))
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
project
,
environment
,
hash_including
(
ci_environment_slug:
environment
.
slug
))
subject
.
query
(
*
query_params
)
subject
.
query
(
*
query_params
)
end
end
...
@@ -33,6 +33,7 @@ RSpec.shared_examples 'additional metrics query' do
...
@@ -33,6 +33,7 @@ RSpec.shared_examples 'additional metrics query' do
it
'contains environment filter'
do
it
'contains environment filter'
do
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
project
,
project
,
environment
,
hash_including
(
hash_including
(
environment_filter:
"container_name!=
\"
POD
\"
,environment=
\"
#{
environment
.
slug
}
\"
"
environment_filter:
"container_name!=
\"
POD
\"
,environment=
\"
#{
environment
.
slug
}
\"
"
)
)
...
@@ -50,7 +51,7 @@ RSpec.shared_examples 'additional metrics query' do
...
@@ -50,7 +51,7 @@ RSpec.shared_examples 'additional metrics query' do
it_behaves_like
'query context containing environment slug and filter'
it_behaves_like
'query context containing environment slug and filter'
it
'query context contains kube_namespace'
do
it
'query context contains kube_namespace'
do
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
project
,
hash_including
(
kube_namespace:
kube_namespace
))
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
project
,
environment
,
hash_including
(
kube_namespace:
kube_namespace
))
subject
.
query
(
*
query_params
)
subject
.
query
(
*
query_params
)
end
end
...
@@ -74,7 +75,7 @@ RSpec.shared_examples 'additional metrics query' do
...
@@ -74,7 +75,7 @@ RSpec.shared_examples 'additional metrics query' do
it_behaves_like
'query context containing environment slug and filter'
it_behaves_like
'query context containing environment slug and filter'
it
'query context contains empty kube_namespace'
do
it
'query context contains empty kube_namespace'
do
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
project
,
hash_including
(
kube_namespace:
''
))
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
project
,
environment
,
hash_including
(
kube_namespace:
''
))
subject
.
query
(
*
query_params
)
subject
.
query
(
*
query_params
)
end
end
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment