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
Hide 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
'
underscore
'
;
import
Vue
from
'
vue
'
;
...
...
@@ -56,7 +54,7 @@ export default () => {
gl
.
IssueBoardsApp
=
new
Vue
({
el
:
$boardApp
,
components
:
{
'
board
'
:
gl
.
issueBoards
.
Board
,
board
:
gl
.
issueBoards
.
Board
,
'
board-sidebar
'
:
gl
.
issueBoards
.
BoardSidebar
,
BoardAddIssuesModal
,
},
...
...
@@ -74,11 +72,11 @@ export default () => {
defaultAvatar
:
$boardApp
.
dataset
.
defaultAvatar
,
},
computed
:
{
detailIssueVisible
()
{
detailIssueVisible
()
{
return
Object
.
keys
(
this
.
detailIssue
.
issue
).
length
;
},
},
created
()
{
created
()
{
gl
.
boardService
=
new
BoardService
({
boardsEndpoint
:
this
.
boardsEndpoint
,
listsEndpoint
:
this
.
listsEndpoint
,
...
...
@@ -100,15 +98,16 @@ export default () => {
sidebarEventHub
.
$off
(
'
toggleSubscription
'
,
this
.
toggleSubscription
);
sidebarEventHub
.
$off
(
'
updateWeight
'
,
this
.
updateWeight
);
},
mounted
()
{
mounted
()
{
this
.
filterManager
=
new
FilteredSearchBoards
(
Store
.
filter
,
true
,
Store
.
cantEdit
);
this
.
filterManager
.
setup
();
Store
.
disabled
=
this
.
disabled
;
gl
.
boardService
.
all
()
gl
.
boardService
.
all
()
.
then
(
res
=>
res
.
data
)
.
then
(
(
data
)
=>
{
data
.
forEach
(
(
board
)
=>
{
.
then
(
data
=>
{
data
.
forEach
(
board
=>
{
const
list
=
Store
.
addList
(
board
,
this
.
defaultAvatar
);
if
(
list
.
type
===
'
closed
'
)
{
...
...
@@ -140,7 +139,7 @@ export default () => {
newIssue
.
setFetchingState
(
'
epic
'
,
true
);
BoardService
.
getIssueInfo
(
sidebarInfoEndpoint
)
.
then
(
res
=>
res
.
data
)
.
then
(
(
data
)
=>
{
.
then
(
data
=>
{
newIssue
.
setFetchingState
(
'
subscriptions
'
,
false
);
newIssue
.
setFetchingState
(
'
weight
'
,
false
);
newIssue
.
setFetchingState
(
'
epic
'
,
false
);
...
...
@@ -185,7 +184,7 @@ export default () => {
issue
.
setLoadingState
(
'
weight
'
,
true
);
BoardService
.
updateWeight
(
issue
.
sidebarInfoEndpoint
,
newWeight
)
.
then
(
res
=>
res
.
data
)
.
then
(
(
data
)
=>
{
.
then
(
data
=>
{
issue
.
setLoadingState
(
'
weight
'
,
false
);
issue
.
updateData
({
weight
:
data
.
weight
,
...
...
@@ -196,7 +195,7 @@ export default () => {
Flash
(
__
(
'
An error occurred when updating the issue weight
'
));
});
}
}
}
,
},
});
...
...
@@ -206,7 +205,7 @@ export default () => {
filters
:
Store
.
state
.
filters
,
milestoneTitle
:
$boardApp
.
dataset
.
boardMilestoneTitle
,
},
mounted
()
{
mounted
()
{
gl
.
issueBoards
.
newListDropdownInit
();
},
});
...
...
@@ -231,8 +230,8 @@ export default () => {
return
this
.
canAdminList
?
'
Edit board
'
:
'
View scope
'
;
},
tooltipTitle
()
{
return
this
.
hasScope
?
__
(
'
This board
\'
s scope is reduced
'
)
:
''
;
}
return
this
.
hasScope
?
__
(
"
This board's scope is reduced
"
)
:
''
;
}
,
},
methods
:
{
showPage
:
page
=>
gl
.
issueBoards
.
BoardsStore
.
showPage
(
page
),
...
...
@@ -254,76 +253,80 @@ export default () => {
});
}
gl
.
IssueBoardsModalAddBtn
=
new
Vue
({
el
:
document
.
getElementById
(
'
js-add-issues-btn
'
),
mixins
:
[
modalMixin
],
data
()
{
return
{
modal
:
ModalStore
.
store
,
store
:
Store
.
state
,
isFullscreen
:
false
,
focusModeAvailable
:
$boardApp
.
hasAttribute
(
'
data-focus-mode-available
'
),
canAdminList
:
this
.
$options
.
el
.
hasAttribute
(
'
data-can-admin-list
'
),
};
},
computed
:
{
disabled
()
{
if
(
!
this
.
store
)
{
return
true
;
}
return
!
this
.
store
.
lists
.
filter
(
list
=>
!
list
.
preset
).
length
;
const
issueBoardsModal
=
document
.
getElementById
(
'
js-add-issues-btn
'
);
if
(
issueBoardsModal
)
{
gl
.
IssueBoardsModalAddBtn
=
new
Vue
({
el
:
issueBoardsModal
,
mixins
:
[
modalMixin
],
data
()
{
return
{
modal
:
ModalStore
.
store
,
store
:
Store
.
state
,
isFullscreen
:
false
,
focusModeAvailable
:
$boardApp
.
hasAttribute
(
'
data-focus-mode-available
'
),
canAdminList
:
this
.
$options
.
el
.
hasAttribute
(
'
data-can-admin-list
'
),
};
},
tooltipTitle
()
{
if
(
this
.
disabled
)
{
return
'
Please add a list to your board first
'
;
}
computed
:
{
disabled
()
{
if
(
!
this
.
store
)
{
return
true
;
}
return
!
this
.
store
.
lists
.
filter
(
list
=>
!
list
.
preset
).
length
;
},
tooltipTitle
()
{
if
(
this
.
disabled
)
{
return
'
Please add a list to your board first
'
;
}
return
''
;
return
''
;
},
},
},
watch
:
{
disabled
()
{
watch
:
{
disabled
()
{
this
.
updateTooltip
();
},
},
mounted
()
{
this
.
updateTooltip
();
},
},
mounted
()
{
this
.
updateTooltip
();
},
methods
:
{
updateTooltip
()
{
const
$tooltip
=
$
(
this
.
$refs
.
addIssuesButton
);
methods
:
{
updateTooltip
()
{
const
$tooltip
=
$
(
this
.
$refs
.
addIssuesButton
);
this
.
$nextTick
(()
=>
{
if
(
this
.
disabled
)
{
$tooltip
.
tooltip
();
}
else
{
$tooltip
.
tooltip
(
'
dispose
'
);
this
.
$nextTick
(()
=>
{
if
(
this
.
disabled
)
{
$tooltip
.
tooltip
();
}
else
{
$tooltip
.
tooltip
(
'
dispose
'
);
}
});
},
openModal
()
{
if
(
!
this
.
disabled
)
{
this
.
toggleModal
(
true
);
}
});
},
openModal
()
{
if
(
!
this
.
disabled
)
{
this
.
toggleModal
(
true
);
}
},
},
},
template
:
`
<div class="board-extra-actions">
<button
class="btn btn-create prepend-left-10
"
type="button
"
data-placement="bottom
"
ref="addIssuesButton
"
:class="{ 'disabled': disabled }
"
:title="tooltipTitle
"
:aria-disabled="disabled
"
v-if="canAdminList"
@click="openModal">
Add issues
</
button
>
</div>
`
,
}
);
template
:
`
<div class="board-extra-actions">
<button
class="btn btn-create prepend-left-10"
type="button
"
data-placement="bottom
"
ref="addIssuesButton
"
:class="{ 'disabled': disabled }
"
:title="tooltipTitle
"
:aria-disabled="disabled
"
v-if="canAdminList
"
@click="openModal">
Add issues
</button>
</
div
>
`
,
});
}
gl
.
IssueBoardsToggleFocusBtn
=
new
Vue
({
el
:
document
.
getElementById
(
'
js-toggle-focus-btn
'
),
...
...
@@ -335,7 +338,9 @@ export default () => {
},
methods
:
{
toggleFocusMode
()
{
if
(
!
this
.
focusModeAvailable
)
{
return
;
}
if
(
!
this
.
focusModeAvailable
)
{
return
;
}
$
(
this
.
$refs
.
toggleFocusModeButton
).
tooltip
(
'
hide
'
);
issueBoardsContent
.
classList
.
toggle
(
'
is-focused
'
);
...
...
@@ -369,6 +374,6 @@ export default () => {
el
:
'
#js-multiple-boards-switcher
'
,
components
:
{
'
boards-selector
'
:
gl
.
issueBoards
.
BoardsSelector
,
}
}
,
});
};
app/assets/javascripts/monitoring/components/dashboard.vue
View file @
238c8c3e
<
script
>
// ee-only
import
DashboardMixin
from
'
ee/monitoring/components/dashboard_mixin
'
;
import
_
from
'
underscore
'
;
import
{
s__
}
from
'
~/locale
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
...
...
@@ -17,6 +20,10 @@ export default {
EmptyState
,
Icon
,
},
// ee-only
mixins
:
[
DashboardMixin
],
props
:
{
hasMetrics
:
{
type
:
Boolean
,
...
...
@@ -137,7 +144,7 @@ export default {
.
catch
(()
=>
Flash
(
s__
(
'
Metrics|There was an error getting deployment information.
'
))),
this
.
service
.
getEnvironmentsData
()
.
then
(
(
data
)
=>
this
.
store
.
storeEnvironmentsData
(
data
))
.
then
(
data
=>
this
.
store
.
storeEnvironmentsData
(
data
))
.
catch
(()
=>
Flash
(
s__
(
'
Metrics|There was an error getting environments information.
'
))),
])
.
then
(()
=>
{
...
...
@@ -225,7 +232,13 @@ export default {
:small-graph=
"forceSmallGraph"
>
<!-- 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-group>
</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
>
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
{
SAST_CONTAINER
}
from
'
ee/vue_shared/security_reports/store/constants
'
;
/**
* Renders block of issues
...
...
@@ -13,7 +18,10 @@ export default {
IssuesBlock
,
SastContainerInfo
,
},
sastContainer
:
SAST_CONTAINER
,
componentNames
,
success
:
STATUS_SUCCESS
,
failed
:
STATUS_FAILED
,
neutral
:
STATUS_NEUTRAL
,
props
:
{
unresolvedIssues
:
{
type
:
Array
,
...
...
@@ -35,9 +43,10 @@ export default {
required
:
false
,
default
:
()
=>
[],
},
type
:
{
component
:
{
type
:
String
,
required
:
true
,
required
:
false
,
default
:
''
,
},
},
data
()
{
...
...
@@ -45,11 +54,6 @@ export default {
isFullReportVisible
:
false
,
};
},
computed
:
{
unresolvedIssuesStatus
()
{
return
this
.
type
===
'
license
'
?
'
neutral
'
:
'
failed
'
;
},
},
methods
:
{
openFullReport
()
{
this
.
isFullReportVisible
=
true
;
...
...
@@ -59,38 +63,37 @@ export default {
</
script
>
<
template
>
<div
class=
"report-block-container"
>
<sast-container-info
v-if=
"type === $options.sastContainer"
/>
<sast-container-info
v-if=
"component === $options.componentNames.SastContainerIssueBody"
/>
<issues-block
v-if=
"unresolvedIssues.length"
:type=
"type"
:status=
"unresolvedIssuesStatus"
:component=
"component"
:issues=
"unresolvedIssues"
:status=
"$options.failed"
class=
"js-mr-code-new-issues"
/>
<issues-block
v-if=
"isFullReportVisible"
:
type=
"type
"
:
component=
"component
"
:issues=
"allIssues"
:status=
"$options.failed"
class=
"js-mr-code-all-issues"
status=
"failed"
/>
<issues-block
v-if=
"neutralIssues.length"
:
type=
"type
"
:
component=
"component
"
:issues=
"neutralIssues"
:status=
"$options.neutral"
class=
"js-mr-code-non-issues"
status=
"neutral"
/>
<issues-block
v-if=
"resolvedIssues.length"
:
type=
"type
"
:
component=
"component
"
:issues=
"resolvedIssues"
:status=
"$options.success"
class=
"js-mr-code-resolved-issues"
status=
"success"
/>
<button
...
...
app/assets/javascripts/vue_shared/components/reports/report_issues.vue
View file @
238c8c3e
<
script
>
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
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
'
;
import
IssueStatusIcon
from
'
~/vue_shared/components/reports/issue_status_icon.vue
'
;
import
{
components
,
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
export
default
{
name
:
'
ReportIssues
'
,
components
:
{
Icon
,
SastIssue
,
SastContainerIssue
,
DastIssue
,
PerformanceIssue
,
CodequalityIssue
,
LicenseIssue
,
IssueStatusIcon
,
...
components
,
},
props
:
{
issues
:
{
type
:
Array
,
required
:
true
,
},
// security || codequality || performance || docker || dast || license
type
:
{
component
:
{
type
:
String
,
required
:
true
,
required
:
false
,
default
:
''
,
validator
:
value
=>
value
===
''
||
Object
.
values
(
componentNames
).
includes
(
value
),
},
// failed || success
status
:
{
...
...
@@ -36,44 +25,6 @@ export default {
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
>
<
template
>
...
...
@@ -85,60 +36,16 @@ export default {
:key="index"
class="report-block-list-issue"
>
<div
:class=
"
{
failed: isStatusFailed,
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"
<issue-status-icon
:status=
"issue.status || status"
class=
"append-right-5"
/>
<license-issue
v-else-if=
"isTypeLicense"
<component
v-if=
"component"
:is=
"component"
:issue=
"issue"
:status=
"issue.status || status"
/>
</li>
</ul>
...
...
app/assets/javascripts/vue_shared/components/reports/report_section.vue
View file @
238c8c3e
...
...
@@ -21,7 +21,7 @@ export default {
required
:
false
,
default
:
false
,
},
type
:
{
component
:
{
type
:
String
,
required
:
false
,
default
:
''
,
...
...
@@ -183,8 +183,9 @@ export default {
<issues-list
:unresolved-issues=
"unresolvedIssues"
:resolved-issues=
"resolvedIssues"
:neutral-issues=
"neutralIssues"
:all-issues=
"allIssues"
:
type=
"type
"
:
component=
"component
"
/>
</slot>
</div>
...
...
app/assets/stylesheets/pages/environments.scss
View file @
238c8c3e
...
...
@@ -267,7 +267,7 @@
border
:
1px
solid
$white-light
;
background-color
:
$orange-300
;
border-radius
:
50%
;
content
:
""
;
content
:
''
;
}
}
}
...
...
@@ -287,8 +287,6 @@
}
}
.gl-responsive-table-row
{
.branch-commit
{
max-width
:
100%
;
...
...
@@ -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
prepend
::
EE
::
EnvironmentsHelper
def
environments_list_data
{
endpoint:
project_environments_path
(
@project
,
format: :json
)
}
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
app/models/clusters/applications/prometheus.rb
View file @
238c8c3e
...
...
@@ -11,6 +11,8 @@ module Clusters
include
::
Clusters
::
Concerns
::
ApplicationStatus
include
::
Clusters
::
Concerns
::
ApplicationData
prepend
EE
::
Clusters
::
Applications
::
Prometheus
default_value_for
:version
,
VERSION
state_machine
:status
do
...
...
@@ -21,6 +23,14 @@ module Clusters
end
end
def
ready_status
[
:installed
]
end
def
ready?
ready_status
.
include?
(
status_name
)
end
def
chart
'stable/prometheus'
end
...
...
app/models/clusters/concerns/application_status.rb
View file @
238c8c3e
...
...
@@ -4,6 +4,8 @@ module Clusters
extend
ActiveSupport
::
Concern
included
do
prepend
::
EE
::
Clusters
::
ApplicationStatus
scope
:installed
,
->
{
where
(
status:
self
.
state_machines
[
:status
].
states
[
:installed
].
value
)
}
state_machine
:status
,
initial: :not_installable
do
...
...
app/models/concerns/prometheus_adapter.rb
View file @
238c8c3e
...
...
@@ -3,6 +3,7 @@ module PrometheusAdapter
included
do
include
ReactiveCaching
prepend
EE
::
PrometheusAdapter
self
.
reactive_cache_key
=
->
(
adapter
)
{
[
adapter
.
class
.
model_name
.
singular
,
adapter
.
id
]
}
self
.
reactive_cache_lease_timeout
=
30
.
seconds
...
...
@@ -24,17 +25,10 @@ module PrometheusAdapter
def
query
(
query_name
,
*
args
)
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
|
if
arg
.
respond_to?
(
:id
)
arg
.
id
else
arg
end
end
with_reactive_cache
(
query_class
.
name
,
*
args
,
&
query_class
.
method
(
:transform_reactive_result
))
with_reactive_cache
(
query_class
.
name
,
*
query_args
,
&
query_class
.
method
(
:transform_reactive_result
))
end
# Cache metrics for specific environment
...
...
@@ -50,5 +44,13 @@ module PrometheusAdapter
rescue
Gitlab
::
PrometheusClient
::
Error
=>
err
{
success:
false
,
result:
err
.
message
}
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
app/services/clusters/applications/base_helm_service.rb
View file @
238c8c3e
...
...
@@ -5,6 +5,8 @@ module Clusters
class
BaseHelmService
attr_accessor
:app
prepend
EE
::
Clusters
::
Applications
::
BaseHelmService
def
initialize
(
app
)
@app
=
app
end
...
...
app/services/clusters/applications/check_installation_progress_service.rb
View file @
238c8c3e
...
...
@@ -50,17 +50,17 @@ module Clusters
end
def
remove_installation_pod
helm_api
.
delete_
installation_
pod!
(
install_command
.
pod_name
)
helm_api
.
delete_pod!
(
install_command
.
pod_name
)
rescue
# no-op
end
def
installation_phase
helm_api
.
installation_
status
(
install_command
.
pod_name
)
helm_api
.
status
(
install_command
.
pod_name
)
end
def
installation_errors
helm_api
.
installation_
log
(
install_command
.
pod_name
)
helm_api
.
log
(
install_command
.
pod_name
)
end
end
end
...
...
app/services/prometheus/adapter_service.rb
View file @
238c8c3e
...
...
@@ -30,7 +30,7 @@ module Prometheus
return
unless
deployment_platform
.
respond_to?
(
:cluster
)
cluster
=
deployment_platform
.
cluster
return
unless
cluster
.
application_prometheus
&
.
installed
?
return
unless
cluster
.
application_prometheus
&
.
ready
?
cluster
.
application_prometheus
end
...
...
app/views/projects/environments/metrics.html.haml
View file @
238c8c3e
...
...
@@ -2,17 +2,11 @@
-
page_title
"Metrics for environment"
,
@environment
.
name
.prometheus-container
{
class:
container_class
}
#prometheus-graphs
{
data:
{
"settings-path"
:
edit_project_service_path
(
@project
,
'prometheus'
),
"clusters-path"
:
project_clusters_path
(
@project
),
"current-environment-name"
:
@environment
.
name
,
"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?}"
}
}
.top-area
.row
.col-sm-6
%h3
Environment:
=
link_to
@environment
.
name
,
environment_path
(
@environment
)
#prometheus-graphs
{
data:
metrics_data
(
@project
,
@environment
)
}
app/workers/all_queues.yml
View file @
238c8c3e
...
...
@@ -22,8 +22,10 @@
-
cronjob:prune_web_hook_logs
-
gcp_cluster:cluster_install_app
-
gcp_cluster:cluster_update_app
-
gcp_cluster:cluster_provision
-
gcp_cluster:cluster_wait_for_app_installation
-
gcp_cluster:cluster_wait_for_app_update
-
gcp_cluster:wait_for_cluster_creation
-
gcp_cluster:cluster_wait_for_ingress_ip_address
...
...
config/routes/project.rb
View file @
238c8c3e
...
...
@@ -80,6 +80,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post
:validate_query
,
on: :collection
get
:active_common
,
on: :collection
end
# EE-specific
resources
:alerts
,
constraints:
{
id:
/\d+/
},
only:
[
:index
,
:create
,
:show
,
:update
,
:destroy
]
do
post
:notify
,
on: :collection
end
# EE-specific
end
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
t
.
text
"status_reason"
t
.
datetime_with_timezone
"created_at"
,
null:
false
t
.
datetime_with_timezone
"updated_at"
,
null:
false
t
.
datetime_with_timezone
"last_update_started_at"
end
create_table
"clusters_applications_runners"
,
force: :cascade
do
|
t
|
...
...
@@ -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"
,
[
"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
|
t
.
integer
"project_id"
t
.
string
"title"
,
null:
false
...
...
@@ -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_repository_states"
,
"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
"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
...
...
ee/app/assets/javascripts/geo_nodes/components/app.vue
View file @
238c8c3e
...
...
@@ -83,7 +83,7 @@ export default {
});
},
fetchGeoNodes
()
{
this
.
service
return
this
.
service
.
getGeoNodes
()
.
then
(
res
=>
res
.
data
)
.
then
(
nodes
=>
{
...
...
@@ -92,9 +92,7 @@ export default {
})
.
catch
(()
=>
{
this
.
isLoading
=
false
;
Flash
(
s__
(
'
GeoNodes|Something went wrong while fetching nodes
'
),
);
Flash
(
s__
(
'
GeoNodes|Something went wrong while fetching nodes
'
));
});
},
fetchNodeDetails
(
node
)
{
...
...
@@ -109,10 +107,7 @@ export default {
primaryRevision
:
primaryNodeVersion
.
revision
,
});
this
.
store
.
setNodeDetails
(
nodeId
,
updatedNodeDetails
);
eventHub
.
$emit
(
'
nodeDetailsLoaded
'
,
this
.
store
.
getNodeDetails
(
nodeId
),
);
eventHub
.
$emit
(
'
nodeDetailsLoaded
'
,
this
.
store
.
getNodeDetails
(
nodeId
));
})
.
catch
(
err
=>
{
if
(
err
.
response
&&
err
.
response
.
data
)
{
...
...
@@ -124,10 +119,7 @@ export default {
sync_status_unavailable
:
true
,
storage_shards_match
:
null
,
});
eventHub
.
$emit
(
'
nodeDetailsLoaded
'
,
this
.
store
.
getNodeDetails
(
nodeId
),
);
eventHub
.
$emit
(
'
nodeDetailsLoaded
'
,
this
.
store
.
getNodeDetails
(
nodeId
));
}
else
{
eventHub
.
$emit
(
'
nodeDetailsLoadFailed
'
,
nodeId
,
err
);
}
...
...
@@ -135,14 +127,11 @@ export default {
},
repairNode
(
targetNode
)
{
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
service
return
this
.
service
.
repairNode
(
targetNode
)
.
then
(()
=>
{
this
.
setNodeActionStatus
(
targetNode
,
false
);
Flash
(
s__
(
'
GeoNodes|Node Authentication was successfully repaired.
'
),
'
notice
'
,
);
Flash
(
s__
(
'
GeoNodes|Node Authentication was successfully repaired.
'
),
'
notice
'
);
})
.
catch
(()
=>
{
this
.
setNodeActionStatus
(
targetNode
,
false
);
...
...
@@ -151,7 +140,7 @@ export default {
},
toggleNode
(
targetNode
)
{
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
service
return
this
.
service
.
toggleNode
(
targetNode
)
.
then
(
res
=>
res
.
data
)
.
then
(
node
=>
{
...
...
@@ -162,14 +151,12 @@ export default {
})
.
catch
(()
=>
{
this
.
setNodeActionStatus
(
targetNode
,
false
);
Flash
(
s__
(
'
GeoNodes|Something went wrong while changing node status
'
),
);
Flash
(
s__
(
'
GeoNodes|Something went wrong while changing node status
'
));
});
},
removeNode
(
targetNode
)
{
this
.
setNodeActionStatus
(
targetNode
,
true
);
this
.
service
return
this
.
service
.
removeNode
(
targetNode
)
.
then
(()
=>
{
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 @@
* Fixed: [name] in [link]:[line]
*/
import
ReportLink
from
'
~/vue_shared/components/reports/report_link.vue
'
;
import
{
STATUS_SUCCESS
}
from
'
~/vue_shared/components/reports/constants
'
;
export
default
{
name
:
'
CodequalityIssueBody
'
,
...
...
@@ -11,10 +12,9 @@ export default {
components
:
{
ReportLink
,
},
props
:
{
isStatusSucces
s
:
{
type
:
Boolean
,
statu
s
:
{
type
:
String
,
required
:
true
,
},
issue
:
{
...
...
@@ -22,6 +22,11 @@ export default {
required
:
true
,
},
},
computed
:
{
isStatusSuccess
()
{
return
this
.
status
===
STATUS_SUCCESS
;
},
},
};
</
script
>
<
template
>
...
...
ee/app/assets/javascripts/vue_merge_request_widget/components/license_issue_body.vue
View file @
238c8c3e
...
...
@@ -2,6 +2,7 @@
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
export
default
{
name
:
'
LicenseIssueBody
'
,
props
:
{
issue
:
{
type
:
Object
,
...
...
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
238c8c3e
...
...
@@ -2,6 +2,7 @@
import
ReportSection
from
'
~/vue_shared/components/reports/report_section.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
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
{
n__
,
s__
,
__
,
sprintf
}
from
'
~/locale
'
;
import
CEWidgetOptions
from
'
~/vue_merge_request_widget/mr_widget_options.vue
'
;
...
...
@@ -17,6 +18,7 @@ export default {
},
extends
:
CEWidgetOptions
,
mixins
:
[
reportsMixin
],
componentNames
,
data
()
{
return
{
isLoadingCodequality
:
false
,
...
...
@@ -255,8 +257,8 @@ export default {
:unresolved-issues=
"mr.codeclimateMetrics.newIssues"
:resolved-issues=
"mr.codeclimateMetrics.resolvedIssues"
:has-issues=
"hasCodequalityIssues"
:component=
"$options.componentNames.CodequalityIssueBody"
class=
"js-codequality-widget mr-widget-border-top"
type=
"codequality"
/>
<report-section
v-if=
"shouldRenderPerformance"
...
...
@@ -268,8 +270,8 @@ export default {
:resolved-issues=
"mr.performanceMetrics.improved"
:neutral-issues=
"mr.performanceMetrics.neutral"
:has-issues=
"hasPerformanceMetrics"
:component=
"$options.componentNames.PerformanceIssueBody"
class=
"js-performance-widget mr-widget-border-top"
type=
"performance"
/>
<grouped-security-reports-app
v-if=
"shouldRenderSecurityReport"
...
...
@@ -299,10 +301,10 @@ export default {
:loading-text=
"translateText('license management').loading"
:error-text=
"translateText('license management').error"
:success-text=
"licenseReportText"
:
unresolved
-issues=
"mr.licenseReport"
:
neutral
-issues=
"mr.licenseReport"
:has-issues=
"hasLicenseReportIssues"
:component=
"$options.componentNames.LicenseIssueBody"
class=
"js-license-report-widget mr-widget-border-top"
type=
"license"
/>
<div
class=
"mr-section-container"
>
<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 @@
import
ModalOpenName
from
'
~/vue_shared/components/reports/modal_open_name.vue
'
;
export
default
{
name
:
'
S
astIssueBody
'
,
name
:
'
D
astIssueBody
'
,
components
:
{
ModalOpenName
,
},
...
...
@@ -16,11 +16,6 @@ export default {
type
:
Object
,
required
:
true
,
},
issueIndex
:
{
type
:
Number
,
required
:
true
,
},
// failed || success
status
:
{
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';
import
ReportSection
from
'
~/vue_shared/components/reports/report_section.vue
'
;
import
SummaryRow
from
'
~/vue_shared/components/reports/summary_row.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
{
SAST
,
DAST
,
SAST_CONTAINER
}
from
'
./store/constants
'
;
import
securityReportsMixin
from
'
./mixins/security_report_mixin
'
;
import
createStore
from
'
./store
'
;
...
...
@@ -111,9 +111,7 @@ export default {
required
:
true
,
},
},
sast
:
SAST
,
dast
:
DAST
,
sastContainer
:
SAST_CONTAINER
,
componentNames
,
computed
:
{
...
mapState
([
'
sast
'
,
'
sastContainer
'
,
'
dast
'
,
'
dependencyScanning
'
,
'
summaryCounts
'
]),
...
mapGetters
([
...
...
@@ -229,7 +227,7 @@ export default {
:unresolved-issues=
"sast.newIssues"
:resolved-issues=
"sast.resolvedIssues"
:all-issues=
"sast.allIssues"
:
type=
"$options.sast
"
:
component=
"$options.componentNames.SastIssueBody
"
class=
"js-sast-issue-list report-block-group-list"
/>
</
template
>
...
...
@@ -248,7 +246,7 @@ export default {
:unresolved-issues=
"dependencyScanning.newIssues"
:resolved-issues=
"dependencyScanning.resolvedIssues"
:all-issues=
"dependencyScanning.allIssues"
:
type=
"$options.sast
"
:
component=
"$options.componentNames.SastIssueBody
"
class=
"js-dss-issue-list report-block-group-list"
/>
</
template
>
...
...
@@ -265,7 +263,7 @@ export default {
v-if=
"sastContainer.newIssues.length || sastContainer.resolvedIssues.length"
:unresolved-issues=
"sastContainer.newIssues"
:neutral-issues=
"sastContainer.resolvedIssues"
:
type=
"$options.sastContainer
"
:
component=
"$options.componentNames.SastContainerIssueBody
"
class=
"report-block-group-list"
/>
</
template
>
...
...
@@ -282,7 +280,7 @@ export default {
v-if=
"dast.newIssues.length || dast.resolvedIssues.length"
:unresolved-issues=
"dast.newIssues"
:resolved-issues=
"dast.resolvedIssues"
:
type=
"$options.dast
"
:
component=
"$options.componentNames.DastIssueBody
"
class=
"report-block-group-list"
/>
</
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';
import
{
s__
,
sprintf
,
n__
}
from
'
~/locale
'
;
import
createFlash
from
'
~/flash
'
;
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
mixin
from
'
./mixins/security_report_mixin
'
;
import
reportsMixin
from
'
./mixins/reports_mixin
'
;
...
...
@@ -88,9 +88,7 @@ export default {
required
:
true
,
},
},
sast
:
SAST
,
dast
:
DAST
,
sastContainer
:
SAST_CONTAINER
,
componentNames
,
computed
:
{
...
mapState
([
'
sast
'
,
'
dependencyScanning
'
,
'
sastContainer
'
,
'
dast
'
]),
...
...
@@ -216,7 +214,7 @@ export default {
<report-section
v-if=
"sastHeadPath"
:always-open=
"alwaysOpen"
:
type=
"$options.sast
"
:
component=
"$options.componentNames.SastIssueBody
"
:status=
"checkReportStatus(sast.isLoading, sast.hasError)"
:loading-text=
"translateText('SAST').loading"
:error-text=
"translateText('SAST').error"
...
...
@@ -230,7 +228,7 @@ export default {
<report-section
v-if=
"dependencyScanningHeadPath"
:always-open=
"alwaysOpen"
:
type=
"$options.sast
"
:
component=
"$options.componentNames.SastIssueBody
"
:status=
"checkReportStatus(dependencyScanning.isLoading, dependencyScanning.hasError)"
:loading-text=
"translateText('Dependency scanning').loading"
:error-text=
"translateText('Dependency scanning').error"
...
...
@@ -244,7 +242,7 @@ export default {
<report-section
v-if=
"sastContainerHeadPath"
:always-open=
"alwaysOpen"
:
type=
"$options.sastContainer
"
:
component=
"$options.componentNames.SastContainerIssueBody
"
:status=
"checkReportStatus(sastContainer.isLoading, sastContainer.hasError)"
:loading-text=
"translateText('Container scanning').loading"
:error-text=
"translateText('Container scanning').error"
...
...
@@ -258,7 +256,7 @@ export default {
<report-section
v-if=
"dastHeadPath"
:always-open=
"alwaysOpen"
:
type=
"$options.dast
"
:
component=
"$options.componentNames.DastIssueBody
"
:status=
"checkReportStatus(dast.isLoading, dast.hasError)"
:loading-text=
"translateText('DAST').loading"
:error-text=
"translateText('DAST').error"
...
...
ee/app/controllers/ee/projects/prometheus/metrics_controller.rb
View file @
238c8c3e
...
...
@@ -47,7 +47,7 @@ module EE
def
update
@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
redirect_to
edit_project_service_path
(
project
,
::
PrometheusService
),
...
...
@@ -63,7 +63,7 @@ module EE
def
destroy
metric
=
project
.
prometheus_metrics
.
find
(
params
[
:id
])
metric
.
destroy
destroy_metrics_service
(
metric
).
execute
respond_to
do
|
format
|
format
.
html
do
...
...
@@ -77,6 +77,14 @@ module EE
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
params
.
require
(
:prometheus_metric
).
permit
(
:title
,
:query
,
:y_label
,
:unit
,
:legend
,
:group
)
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
mail
(
to:
new_mirror_user
.
notification_email
,
subject:
subject
(
'Mirror user changed'
))
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
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
Environment
extend
ActiveSupport
::
Concern
prepended
do
has_many
:prometheus_alerts
,
inverse_of: :environment
end
def
pod_names
return
[]
unless
rollout_status
...
...
@@ -7,5 +13,13 @@ module EE
instance
[
:pod_name
]
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
ee/app/models/ee/project.rb
View file @
238c8c3e
...
...
@@ -39,6 +39,8 @@ module EE
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
:mirror
,
->
{
where
(
mirror:
true
)
}
...
...
@@ -99,6 +101,12 @@ module EE
pipelines
.
newest_first
(
default_branch
).
with_security_reports
.
first
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
return
if
external_webhook_token
.
present?
...
...
ee/app/models/license.rb
View file @
238c8c3e
...
...
@@ -75,6 +75,7 @@ class License < ActiveRecord::Base
chatops
pod_logs
pseudonymizer
prometheus_alerts
]
.
freeze
# 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
belongs_to
:project
,
required:
true
,
validate:
true
,
inverse_of: :prometheus_metrics
has_one
:prometheus_alert
,
inverse_of: :prometheus_metric
enum
group:
[
:business
,
:response
,
:system
]
validates
:title
,
presence:
true
...
...
@@ -19,7 +22,7 @@ class PrometheusMetric < ActiveRecord::Base
end
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
private
...
...
ee/app/policies/ee/project_policy.rb
View file @
238c8c3e
...
...
@@ -49,6 +49,10 @@ module EE
@subject
.
feature_available?
(
:pod_logs
,
@user
)
end
condition
(
:prometheus_alerts_enabled
)
do
@subject
.
feature_available?
(
:prometheus_alerts
,
@user
)
end
rule
{
admin
}.
enable
:change_repository_storage
rule
{
support_bot
}.
enable
:guest_access
...
...
@@ -97,6 +101,7 @@ module EE
end
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
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
mailer
.
project_mirror_user_changed_email
(
new_mirror_user
.
id
,
deleted_user_name
,
project
.
id
).
deliver_later
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
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';
import
GeoNodesService
from
'
ee/geo_nodes/service/geo_nodes_service
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
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
Component
=
Vue
.
extend
(
appComponent
);
...
...
@@ -86,187 +92,244 @@ describe('AppComponent', () => {
});
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
'
);
vm
.
fetchGeoNodes
();
setTimeout
(()
=>
{
expect
(
vm
.
store
.
setNodes
).
toHaveBeenCalledWith
(
mockNodes
);
expect
(
vm
.
isLoading
).
toBe
(
false
);
done
();
},
0
);
vm
.
fetchGeoNodes
()
.
then
(()
=>
{
expect
(
vm
.
store
.
setNodes
).
toHaveBeenCalledWith
(
mockNodes
);
expect
(
vm
.
isLoading
).
toBe
(
false
);
})
.
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
'
;
statusCode
=
500
;
vm
.
fetchGeoNodes
();
setTimeout
(()
=>
{
expect
(
vm
.
isLoading
).
toBe
(
false
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while fetching nodes
'
);
done
();
},
0
);
vm
.
fetchGeoNodes
()
.
then
(()
=>
{
expect
(
vm
.
isLoading
).
toBe
(
false
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while fetching nodes
'
,
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
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
);
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
vm
.
fetchNodeDetails
(
mockNode
);
setTimeout
(()
=>
{
expect
(
vm
.
service
.
getGeoNodeDetails
).
toHaveBeenCalled
();
expect
(
Object
.
keys
(
vm
.
store
.
state
.
nodeDetails
).
length
).
not
.
toBe
(
0
);
expect
(
vm
.
store
.
state
.
nodeDetails
[
'
1
'
]).
toBeDefined
();
done
();
},
0
);
vm
.
fetchNodeDetails
(
mockNode
)
.
then
(()
=>
{
expect
(
vm
.
service
.
getGeoNodeDetails
).
toHaveBeenCalled
();
expect
(
Object
.
keys
(
vm
.
store
.
state
.
nodeDetails
).
length
).
not
.
toBe
(
0
);
expect
(
vm
.
store
.
state
.
nodeDetails
[
'
1
'
]).
toBeDefined
();
})
.
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
'
);
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
404
,
{});
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
vm
.
fetchNodeDetails
(
mockNode
);
setTimeout
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoaded
'
,
jasmine
.
any
(
Object
));
const
nodeDetails
=
vm
.
store
.
state
.
nodeDetails
[
'
1
'
];
expect
(
nodeDetails
).
toBeDefined
();
expect
(
nodeDetails
.
syncStatusUnavailable
).
toBe
(
true
);
expect
(
nodeDetails
.
health
).
toBe
(
'
Request failed with status code 404
'
);
done
();
},
0
);
vm
.
fetchNodeDetails
(
mockNode
)
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoaded
'
,
jasmine
.
any
(
Object
));
const
nodeDetails
=
vm
.
store
.
state
.
nodeDetails
[
'
1
'
];
expect
(
nodeDetails
).
toBeDefined
();
expect
(
nodeDetails
.
syncStatusUnavailable
).
toBe
(
true
);
expect
(
nodeDetails
.
health
).
toBe
(
'
Request failed with status code 404
'
);
})
.
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
'
);
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
500
,
{});
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
vm
.
fetchNodeDetails
(
mockNode
);
setTimeout
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoaded
'
,
jasmine
.
any
(
Object
));
const
nodeDetails
=
vm
.
store
.
state
.
nodeDetails
[
'
1
'
];
expect
(
nodeDetails
).
toBeDefined
();
expect
(
nodeDetails
.
syncStatusUnavailable
).
toBe
(
true
);
expect
(
nodeDetails
.
health
).
toBe
(
'
Request failed with status code 500
'
);
done
();
},
0
);
vm
.
fetchNodeDetails
(
mockNode
)
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoaded
'
,
jasmine
.
any
(
Object
));
const
nodeDetails
=
vm
.
store
.
state
.
nodeDetails
[
'
1
'
];
expect
(
nodeDetails
).
toBeDefined
();
expect
(
nodeDetails
.
syncStatusUnavailable
).
toBe
(
true
);
expect
(
nodeDetails
.
health
).
toBe
(
'
Request failed with status code 500
'
);
})
.
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
'
);
mock
.
onGet
(
mockNode
.
statusPath
).
reply
(
500
,
null
);
spyOn
(
vm
.
service
,
'
getGeoNodeDetails
'
).
and
.
callThrough
();
vm
.
fetchNodeDetails
(
mockNode
);
setTimeout
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoadFailed
'
,
mockNode
.
id
,
jasmine
.
any
(
Object
));
done
();
},
0
);
vm
.
fetchNodeDetails
(
mockNode
)
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
nodeDetailsLoadFailed
'
,
mockNode
.
id
,
jasmine
.
any
(
Object
),
);
done
();
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
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
};
mock
.
onPost
(
node
.
repairPath
).
reply
(
200
);
mock
.
onPost
(
node
.
repairPath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
200
];
});
spyOn
(
vm
.
service
,
'
repairNode
'
).
and
.
callThrough
();
vm
.
repairNode
(
node
);
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
setTimeout
(()
=>
{
expect
(
vm
.
service
.
repairNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Node Authentication was successfully repaired.
'
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
done
();
},
0
);
vm
.
repairNode
(
node
)
.
then
(()
=>
{
expect
(
vm
.
service
.
repairNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Node Authentication was successfully repaired.
'
,
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
})
.
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
};
mock
.
onPost
(
node
.
repairPath
).
reply
(
500
);
mock
.
onPost
(
node
.
repairPath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
500
];
});
spyOn
(
vm
.
service
,
'
repairNode
'
).
and
.
callThrough
();
vm
.
repairNode
(
node
);
setTimeout
(()
=>
{
expect
(
vm
.
service
.
repairNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while repairing node
'
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
done
();
},
0
);
vm
.
repairNode
(
node
)
.
then
(()
=>
{
expect
(
vm
.
service
.
repairNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while repairing node
'
,
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
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
};
mock
.
onPut
(
node
.
basePath
).
reply
(
200
,
{
enabled
:
true
,
mock
.
onPut
(
node
.
basePath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
200
,
{
enabled
:
true
,
},
];
});
spyOn
(
vm
.
service
,
'
toggleNode
'
).
and
.
callThrough
();
node
.
enabled
=
false
;
vm
.
toggleNode
(
node
);
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
setTimeout
(()
=>
{
expect
(
vm
.
service
.
toggleNode
).
toHaveBeenCalledWith
(
node
);
expect
(
node
.
enabled
).
toBe
(
true
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
done
();
},
0
);
vm
.
toggleNode
(
node
)
.
then
(()
=>
{
expect
(
vm
.
service
.
toggleNode
).
toHaveBeenCalledWith
(
node
);
expect
(
node
.
enabled
).
toBe
(
true
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
})
.
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
};
mock
.
onPut
(
node
.
basePath
).
reply
(
500
);
mock
.
onPut
(
node
.
basePath
).
reply
(()
=>
{
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
return
[
500
];
});
spyOn
(
vm
.
service
,
'
toggleNode
'
).
and
.
callThrough
();
node
.
enabled
=
false
;
vm
.
toggleNode
(
node
);
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
setTimeout
(()
=>
{
expect
(
vm
.
service
.
toggleNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while changing node status
'
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
done
();
},
0
);
vm
.
toggleNode
(
node
)
.
then
(()
=>
{
expect
(
vm
.
service
.
toggleNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while changing node status
'
,
);
expect
(
node
.
nodeActionActive
).
toBe
(
false
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
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
};
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
.
store
,
'
removeNode
'
).
and
.
stub
();
vm
.
removeNode
(
node
);
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
setTimeout
(()
=>
{
expect
(
vm
.
service
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
store
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Node was successfully removed.
'
);
done
();
},
0
);
vm
.
removeNode
(
node
)
.
then
(()
=>
{
expect
(
vm
.
service
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
store
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Node was successfully removed.
'
,
);
})
.
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
};
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
.
store
,
'
removeNode
'
).
and
.
stub
();
vm
.
removeNode
(
node
);
expect
(
node
.
nodeActionActive
).
toBe
(
true
);
setTimeout
(()
=>
{
expect
(
vm
.
service
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
store
.
removeNode
).
not
.
toHaveBeenCalled
();
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while removing node
'
);
done
();
},
0
);
vm
.
removeNode
(
node
)
.
then
(()
=>
{
expect
(
vm
.
service
.
removeNode
).
toHaveBeenCalledWith
(
node
);
expect
(
vm
.
store
.
removeNode
).
not
.
toHaveBeenCalled
();
expect
(
document
.
querySelector
(
'
.flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Something went wrong while removing node
'
,
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
...
...
@@ -399,7 +462,9 @@ describe('AppComponent', () => {
it
(
'
renders loading animation when `isLoading` is 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
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
;
const
Component
=
Vue
.
extend
(
component
);
...
...
@@ -22,7 +27,7 @@ describe('sast issue body', () => {
it
(
'
renders fixed label
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
issue
:
codequalityIssue
,
isStatusSuccess
:
true
,
status
:
STATUS_SUCCESS
,
});
expect
(
vm
.
$el
.
textContent
.
trim
()).
toContain
(
'
Fixed
'
);
...
...
@@ -33,7 +38,7 @@ describe('sast issue body', () => {
it
(
'
renders fixed label
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
issue
:
codequalityIssue
,
isStatusSuccess
:
false
,
status
:
STATUS_FAILED
,
});
expect
(
vm
.
$el
.
textContent
.
trim
()).
not
.
toContain
(
'
Fixed
'
);
...
...
@@ -44,7 +49,7 @@ describe('sast issue body', () => {
it
(
'
renders name
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
issue
:
codequalityIssue
,
isStatusSuccess
:
false
,
status
:
STATUS_NEUTRAL
,
});
expect
(
vm
.
$el
.
textContent
.
trim
()).
toContain
(
codequalityIssue
.
name
);
...
...
@@ -55,15 +60,11 @@ describe('sast issue body', () => {
it
(
'
renders name
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
issue
:
codequalityIssue
,
isStatusSuccess
:
false
,
status
:
STATUS_NEUTRAL
,
});
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
getAttribute
(
'
href
'
)).
toEqual
(
codequalityIssue
.
urlPath
,
);
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
textContent
.
trim
()).
toEqual
(
codequalityIssue
.
path
,
);
expect
(
vm
.
$el
.
querySelector
(
'
a
'
).
getAttribute
(
'
href
'
)).
toEqual
(
codequalityIssue
.
urlPath
);
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
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
'
,
()
=>
{
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
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
mountComponent
,
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
codequalityParsedIssues
,
}
from
'
spec/vue_mr_widget/mock_data
'
;
import
{
codequalityParsedIssues
}
from
'
spec/vue_mr_widget/mock_data
'
;
import
{
sastParsedIssues
,
dockerReportParsed
,
...
...
@@ -28,8 +28,8 @@ describe('Report issues', () => {
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
codequalityParsedIssues
,
type
:
'
codequality
'
,
status
:
'
success
'
,
component
:
componentNames
.
CodequalityIssueBody
,
status
:
STATUS_SUCCESS
,
});
});
...
...
@@ -49,8 +49,8 @@ describe('Report issues', () => {
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
codequalityParsedIssues
,
type
:
'
codequality
'
,
status
:
'
failed
'
,
component
:
componentNames
.
CodequalityIssueBody
,
status
:
STATUS_FAILED
,
});
});
...
...
@@ -68,8 +68,8 @@ describe('Report issues', () => {
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
sastParsedIssues
,
type
:
'
SAST
'
,
status
:
'
failed
'
,
component
:
componentNames
.
SastIssueBody
,
status
:
STATUS_FAILED
,
});
});
...
...
@@ -82,8 +82,8 @@ describe('Report issues', () => {
it
(
'
should render location
'
,
()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
sastParsedIssues
,
type
:
'
SAST
'
,
status
:
'
failed
'
,
component
:
componentNames
.
SastIssueBody
,
status
:
STATUS_FAILED
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.report-block-list li
'
).
textContent
).
toContain
(
'
in
'
);
...
...
@@ -97,8 +97,8 @@ describe('Report issues', () => {
issues
:
[{
title
:
'
foo
'
,
}],
type
:
'
SAST
'
,
status
:
'
failed
'
,
component
:
componentNames
.
SastIssueBody
,
status
:
STATUS_SUCCESS
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.report-block-list li
'
).
textContent
).
not
.
toContain
(
'
in
'
);
...
...
@@ -110,8 +110,8 @@ describe('Report issues', () => {
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportIssues
,
{
issues
:
dockerReportParsed
.
unapproved
,
type
:
'
SAST_CONTAINER
'
,
status
:
'
failed
'
,
component
:
componentNames
.
SastContainerIssueBody
,
status
:
STATUS_FAILED
,
});
});
...
...
@@ -142,8 +142,8 @@ describe('Report issues', () => {
vm
=
mountComponentWithStore
(
ReportIssues
,
{
store
,
props
:
{
issues
:
parsedDast
,
type
:
'
DAST
'
,
status
:
'
failed
'
,
component
:
componentNames
.
DastIssueBody
,
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
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
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
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
around
do
|
example
|
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
Kubernetes
class
ConfigMap
def
initialize
(
name
,
values
)
def
initialize
(
name
,
values
=
""
)
@name
=
name
@values
=
values
end
...
...
@@ -13,6 +13,10 @@ module Gitlab
resource
end
def
config_map_name
"values-content-configuration-
#{
name
}
"
end
private
attr_reader
:name
,
:values
...
...
@@ -25,10 +29,6 @@ module Gitlab
}
end
def
config_map_name
"values-content-configuration-
#{
name
}
"
end
def
namespace
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
end
...
...
lib/gitlab/kubernetes/helm/api.rb
View file @
238c8c3e
...
...
@@ -2,15 +2,17 @@ module Gitlab
module
Kubernetes
module
Helm
class
Api
prepend
EE
::
Gitlab
::
Kubernetes
::
Helm
::
Api
def
initialize
(
kubeclient
)
@kubeclient
=
kubeclient
@namespace
=
Gitlab
::
Kubernetes
::
Namespace
.
new
(
Gitlab
::
Kubernetes
::
Helm
::
NAMESPACE
,
kubeclient
)
end
def
install
(
command
)
@
namespace
.
ensure_exists!
namespace
.
ensure_exists!
create_config_map
(
command
)
if
command
.
config_map?
@
kubeclient
.
create_pod
(
command
.
pod_resource
)
kubeclient
.
create_pod
(
command
.
pod_resource
)
end
##
...
...
@@ -20,23 +22,25 @@ module Gitlab
#
# values: "Pending", "Running", "Succeeded", "Failed", "Unknown"
#
def
installation_
status
(
pod_name
)
@kubeclient
.
get_pod
(
pod_name
,
@
namespace
.
name
).
status
.
phase
def
status
(
pod_name
)
kubeclient
.
get_pod
(
pod_name
,
namespace
.
name
).
status
.
phase
end
def
installation_
log
(
pod_name
)
@kubeclient
.
get_pod_log
(
pod_name
,
@
namespace
.
name
).
body
def
log
(
pod_name
)
kubeclient
.
get_pod_log
(
pod_name
,
namespace
.
name
).
body
end
def
delete_
installation_
pod!
(
pod_name
)
@kubeclient
.
delete_pod
(
pod_name
,
@
namespace
.
name
)
def
delete_pod!
(
pod_name
)
kubeclient
.
delete_pod
(
pod_name
,
namespace
.
name
)
end
private
attr_reader
:kubeclient
,
:namespace
def
create_config_map
(
command
)
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
...
...
lib/gitlab/prometheus/metric.rb
View file @
238c8c3e
...
...
@@ -3,7 +3,7 @@ module Gitlab
class
Metric
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
...
...
lib/gitlab/prometheus/queries/additional_metrics_deployment_query.rb
View file @
238c8c3e
...
...
@@ -8,6 +8,7 @@ module Gitlab
Deployment
.
find_by
(
id:
deployment_id
).
try
do
|
deployment
|
query_metrics
(
deployment
.
project
,
deployment
.
environment
,
common_query_context
(
deployment
.
environment
,
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
::
Environment
.
find_by
(
id:
environment_id
).
try
do
|
environment
|
query_metrics
(
environment
.
project
,
environment
,
common_query_context
(
environment
,
timeframe_start:
8
.
hours
.
ago
.
to_f
,
timeframe_end:
Time
.
now
.
to_f
)
)
end
...
...
lib/gitlab/prometheus/queries/query_additional_metrics.rb
View file @
238c8c3e
...
...
@@ -2,7 +2,9 @@ module Gitlab
module
Prometheus
module
Queries
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
))
.
select
(
&
method
(
:group_with_any_metrics
))
end
...
...
@@ -14,12 +16,16 @@ module Gitlab
lambda
do
|
group
|
metrics
=
group
.
metrics
.
map
do
|
metric
|
{
metric_hsh
=
{
title:
metric
.
title
,
weight:
metric
.
weight
,
y_label:
metric
.
y_label
,
queries:
metric
.
queries
.
map
(
&
query_processor
).
select
(
&
method
(
:query_with_result
))
}
metric_hsh
[
:id
]
=
metric
.
id
if
metric
.
id
metric_hsh
end
{
...
...
locale/gitlab.pot
View file @
238c8c3e
...
...
@@ -5060,6 +5060,36 @@ msgstr ""
msgid "ProjectsDropdown|Sorry, no projects matched your search"
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"
msgstr ""
...
...
@@ -6227,7 +6257,7 @@ msgstr ""
msgid "This application will be able to:"
msgstr ""
msgid "This board
\\
's scope is reduced"
msgid "This board's scope is reduced"
msgstr ""
msgid "This diff is collapsed."
...
...
spec/factories/clusters/applications/helm.rb
View file @
238c8c3e
...
...
@@ -22,11 +22,24 @@ FactoryBot.define do
status
3
end
trait
:updating
do
status
4
end
trait
:updated
do
status
5
end
trait
:errored
do
status
(
-
1
)
status_reason
'something went wrong'
end
trait
:update_errored
do
status
(
6
)
status_reason
'something went wrong'
end
trait
:timeouted
do
installing
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(() => {
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
();
// 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', () => {
describe
(
'
computed
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
type
:
'
codequality
'
,
component
:
'
'
,
status
:
'
SUCCESS
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
...
...
@@ -89,7 +89,7 @@ describe('Report section', () => {
describe
(
'
when it is loading
'
,
()
=>
{
it
(
'
should render loading indicator
'
,
()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
type
:
'
codequality
'
,
component
:
'
'
,
status
:
'
LOADING
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
...
...
@@ -103,7 +103,7 @@ describe('Report section', () => {
describe
(
'
with success status
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
type
:
'
codequality
'
,
component
:
'
'
,
status
:
'
SUCCESS
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
...
...
@@ -161,7 +161,7 @@ describe('Report section', () => {
describe
(
'
with failed request
'
,
()
=>
{
it
(
'
should render error indicator
'
,
()
=>
{
vm
=
mountComponent
(
ReportSection
,
{
type
:
'
codequality
'
,
component
:
'
'
,
status
:
'
ERROR
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
Failed to load codeclimate report
'
,
...
...
@@ -171,87 +171,4 @@ describe('Report section', () => {
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
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
'
,
()
=>
{
let
vm
;
...
...
@@ -23,7 +24,7 @@ describe('sast issue body', () => {
confidence
:
'
Low
'
,
};
const
status
=
'
failed
'
;
const
status
=
STATUS_FAILED
;
afterEach
(()
=>
{
vm
.
$destroy
();
...
...
spec/lib/gitlab/import_export/all_models.yml
View file @
238c8c3e
...
...
@@ -333,6 +333,7 @@ project:
-
ci_cd_settings
-
import_export_upload
-
vulnerability_feedback
-
prometheus_alerts
award_emoji
:
-
awardable
-
user
...
...
@@ -340,6 +341,7 @@ priorities:
-
label
prometheus_metrics
:
-
project
-
prometheus_alert
timelogs
:
-
issue
-
merge_request
...
...
spec/lib/gitlab/kubernetes/config_map_spec.rb
View file @
238c8c3e
...
...
@@ -22,4 +22,10 @@ describe Gitlab::Kubernetes::ConfigMap do
is_expected
.
to
eq
(
resource
)
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
spec/lib/gitlab/kubernetes/helm/api_spec.rb
View file @
238c8c3e
...
...
@@ -49,33 +49,33 @@ describe Gitlab::Kubernetes::Helm::Api do
end
end
describe
'#
installation_
status'
do
describe
'#status'
do
let
(
:phase
)
{
Gitlab
::
Kubernetes
::
Pod
::
RUNNING
}
let
(
:pod
)
{
Kubeclient
::
Resource
.
new
(
status:
{
phase:
phase
})
}
# partial representation
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
(
subject
.
installation_
status
(
command
.
pod_name
)).
to
eq
(
phase
)
expect
(
subject
.
status
(
command
.
pod_name
)).
to
eq
(
phase
)
end
end
describe
'#
installation_
log'
do
describe
'#log'
do
let
(
:log
)
{
'some output'
}
let
(
:response
)
{
RestClient
::
Response
.
new
(
log
)
}
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
(
subject
.
installation_
log
(
command
.
pod_name
)).
to
eq
(
log
)
expect
(
subject
.
log
(
command
.
pod_name
)).
to
eq
(
log
)
end
end
describe
'#delete_
installation_
pod!'
do
describe
'#delete_pod!'
do
it
'deletes the POD from kubernetes cluster'
do
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
spec/models/clusters/applications/prometheus_spec.rb
View file @
238c8c3e
...
...
@@ -34,6 +34,47 @@ describe Clusters::Applications::Prometheus do
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
context
'cluster is nil'
do
it
'returns nil'
do
...
...
@@ -102,15 +143,17 @@ describe Clusters::Applications::Prometheus do
let
(
:kubeclient
)
{
double
(
'kubernetes client'
)
}
let
(
:prometheus
)
{
create
(
:clusters_applications_prometheus
)
}
subject
{
prometheus
.
install_command
}
it
{
is_expected
.
to
be_an_instance_of
(
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
)
}
it
'returns an instance of Gitlab::Kubernetes::Helm::InstallCommand'
do
expect
(
prometheus
.
install_command
).
to
be_an_instance_of
(
Gitlab
::
Kubernetes
::
Helm
::
InstallCommand
)
end
it
'should be initialized with 3 arguments'
do
expect
(
subject
.
name
).
to
eq
(
'prometheus'
)
expect
(
subject
.
chart
).
to
eq
(
'stable/prometheus'
)
expect
(
subject
.
version
).
to
eq
(
'6.7.3'
)
expect
(
subject
.
values
).
to
eq
(
prometheus
.
values
)
command
=
prometheus
.
install_command
expect
(
command
.
name
).
to
eq
(
'prometheus'
)
expect
(
command
.
chart
).
to
eq
(
'stable/prometheus'
)
expect
(
command
.
version
).
to
eq
(
'6.7.3'
)
expect
(
command
.
values
).
to
eq
(
prometheus
.
values
)
end
end
...
...
spec/support/prometheus/additional_metrics_shared_examples.rb
View file @
238c8c3e
...
...
@@ -25,7 +25,7 @@ RSpec.shared_examples 'additional metrics query' do
shared_examples
'query context containing environment slug and filter'
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
)
end
...
...
@@ -33,6 +33,7 @@ RSpec.shared_examples 'additional metrics query' do
it
'contains environment filter'
do
expect
(
subject
).
to
receive
(
:query_metrics
).
with
(
project
,
environment
,
hash_including
(
environment_filter:
"container_name!=
\"
POD
\"
,environment=
\"
#{
environment
.
slug
}
\"
"
)
...
...
@@ -50,7 +51,7 @@ RSpec.shared_examples 'additional metrics query' do
it_behaves_like
'query context containing environment slug and filter'
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
)
end
...
...
@@ -74,7 +75,7 @@ RSpec.shared_examples 'additional metrics query' do
it_behaves_like
'query context containing environment slug and filter'
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
)
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