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
6b8040dc
Commit
6b8040dc
authored
Dec 11, 2019
by
GitLab Bot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add latest changes from gitlab-org/gitlab@master
parent
7b875aa3
Changes
76
Hide whitespace changes
Inline
Side-by-side
Showing
76 changed files
with
1207 additions
and
356 deletions
+1207
-356
app/assets/javascripts/error_tracking/components/error_tracking_list.vue
...scripts/error_tracking/components/error_tracking_list.vue
+137
-73
app/assets/javascripts/error_tracking/store/list/actions.js
app/assets/javascripts/error_tracking/store/list/actions.js
+16
-0
app/assets/javascripts/error_tracking/store/list/mutation_types.js
...s/javascripts/error_tracking/store/list/mutation_types.js
+4
-0
app/assets/javascripts/error_tracking/store/list/mutations.js
...assets/javascripts/error_tracking/store/list/mutations.js
+36
-0
app/assets/javascripts/error_tracking/store/list/state.js
app/assets/javascripts/error_tracking/store/list/state.js
+2
-0
app/assets/javascripts/monitoring/components/dashboard.vue
app/assets/javascripts/monitoring/components/dashboard.vue
+59
-40
app/assets/javascripts/monitoring/components/embed.vue
app/assets/javascripts/monitoring/components/embed.vue
+4
-3
app/assets/javascripts/monitoring/components/empty_state.vue
app/assets/javascripts/monitoring/components/empty_state.vue
+9
-0
app/assets/javascripts/monitoring/components/graph_group.vue
app/assets/javascripts/monitoring/components/graph_group.vue
+21
-8
app/assets/javascripts/monitoring/stores/actions.js
app/assets/javascripts/monitoring/stores/actions.js
+22
-16
app/assets/javascripts/monitoring/stores/getters.js
app/assets/javascripts/monitoring/stores/getters.js
+32
-0
app/assets/javascripts/monitoring/stores/index.js
app/assets/javascripts/monitoring/stores/index.js
+2
-0
app/assets/javascripts/monitoring/stores/mutations.js
app/assets/javascripts/monitoring/stores/mutations.js
+0
-1
app/assets/javascripts/monitoring/stores/state.js
app/assets/javascripts/monitoring/stores/state.js
+0
-1
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
...ue_merge_request_widget/components/mr_widget_pipeline.vue
+22
-0
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
...equest_widget/components/mr_widget_pipeline_container.vue
+1
-0
app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
...cripts/vue_merge_request_widget/stores/mr_widget_store.js
+1
-0
app/assets/stylesheets/framework/common.scss
app/assets/stylesheets/framework/common.scss
+6
-0
app/assets/stylesheets/framework/filters.scss
app/assets/stylesheets/framework/filters.scss
+1
-1
app/assets/stylesheets/pages/prometheus.scss
app/assets/stylesheets/pages/prometheus.scss
+0
-1
app/helpers/environments_helper.rb
app/helpers/environments_helper.rb
+1
-0
app/models/application_setting.rb
app/models/application_setting.rb
+0
-3
app/models/merge_request.rb
app/models/merge_request.rb
+6
-0
app/serializers/merge_request_poll_widget_entity.rb
app/serializers/merge_request_poll_widget_entity.rb
+4
-0
app/views/projects/merge_requests/_widget.html.haml
app/views/projects/merge_requests/_widget.html.haml
+1
-1
changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-workers.yml
...cidental-deletions-via-soft-delete-for-groups-workers.yml
+5
-0
changelogs/unreleased/34067-add-recent-searches-to-sentry-error-list-in-gitlab.yml
...67-add-recent-searches-to-sentry-error-list-in-gitlab.yml
+5
-0
changelogs/unreleased/34121-group-level-no-data-store.yml
changelogs/unreleased/34121-group-level-no-data-store.yml
+5
-0
changelogs/unreleased/34261-service-desk-to-api.yml
changelogs/unreleased/34261-service-desk-to-api.yml
+5
-0
changelogs/unreleased/feat-merge-request-coverage-delta.yml
changelogs/unreleased/feat-merge-request-coverage-delta.yml
+5
-0
config/initializers/1_settings.rb
config/initializers/1_settings.rb
+3
-0
db/migrate/20191203121729_update_group_deletion_schedules_foreign_keys.rb
...203121729_update_group_deletion_schedules_foreign_keys.rb
+29
-0
db/schema.rb
db/schema.rb
+1
-1
doc/api/projects.md
doc/api/projects.md
+52
-0
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/features/security/project/internal_access_spec.rb
spec/features/security/project/internal_access_spec.rb
+8
-8
spec/features/security/project/private_access_spec.rb
spec/features/security/project/private_access_spec.rb
+9
-9
spec/features/security/project/public_access_spec.rb
spec/features/security/project/public_access_spec.rb
+8
-8
spec/frontend/error_tracking/components/error_tracking_list_spec.js
...end/error_tracking/components/error_tracking_list_spec.js
+61
-7
spec/frontend/error_tracking/store/list/mutation_spec.js
spec/frontend/error_tracking/store/list/mutation_spec.js
+82
-0
spec/frontend/monitoring/charts/time_series_spec.js
spec/frontend/monitoring/charts/time_series_spec.js
+3
-2
spec/frontend/monitoring/dashboard_state_spec.js
spec/frontend/monitoring/dashboard_state_spec.js
+1
-0
spec/frontend/monitoring/embed/embed_spec.js
spec/frontend/monitoring/embed/embed_spec.js
+9
-2
spec/frontend/monitoring/embed/mock_data.js
spec/frontend/monitoring/embed/mock_data.js
+2
-4
spec/frontend/monitoring/mock_data.js
spec/frontend/monitoring/mock_data.js
+29
-0
spec/frontend/monitoring/store/actions_spec.js
spec/frontend/monitoring/store/actions_spec.js
+90
-43
spec/frontend/monitoring/store/getters_spec.js
spec/frontend/monitoring/store/getters_spec.js
+99
-0
spec/frontend/monitoring/store/mutations_spec.js
spec/frontend/monitoring/store/mutations_spec.js
+49
-34
spec/javascripts/monitoring/components/dashboard_spec.js
spec/javascripts/monitoring/components/dashboard_spec.js
+49
-29
spec/javascripts/monitoring/components/graph_group_spec.js
spec/javascripts/monitoring/components/graph_group_spec.js
+76
-31
spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
...pages/admin/jobs/index/components/stop_jobs_modal_spec.js
+1
-2
spec/javascripts/pages/labels/components/promote_label_modal_spec.js
...ripts/pages/labels/components/promote_label_modal_spec.js
+1
-1
spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js
...lestones/shared/components/delete_milestone_modal_spec.js
+1
-2
spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
...estones/shared/components/promote_milestone_modal_spec.js
+1
-1
spec/javascripts/pdf/index_spec.js
spec/javascripts/pdf/index_spec.js
+1
-1
spec/javascripts/pdf/page_spec.js
spec/javascripts/pdf/page_spec.js
+1
-1
spec/javascripts/performance_bar/index_spec.js
spec/javascripts/performance_bar/index_spec.js
+1
-2
spec/javascripts/persistent_user_callout_spec.js
spec/javascripts/persistent_user_callout_spec.js
+1
-1
spec/javascripts/pipelines/graph/job_group_dropdown_spec.js
spec/javascripts/pipelines/graph/job_group_dropdown_spec.js
+1
-1
spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js
...vascripts/pipelines/graph/linked_pipelines_column_spec.js
+1
-1
spec/javascripts/pipelines/graph/stage_column_component_spec.js
...avascripts/pipelines/graph/stage_column_component_spec.js
+1
-1
spec/javascripts/pipelines/pipelines_actions_spec.js
spec/javascripts/pipelines/pipelines_actions_spec.js
+2
-2
spec/javascripts/pipelines/pipelines_spec.js
spec/javascripts/pipelines/pipelines_spec.js
+1
-1
spec/javascripts/pipelines/stage_spec.js
spec/javascripts/pipelines/stage_spec.js
+1
-1
spec/javascripts/profile/account/components/delete_account_modal_spec.js
...s/profile/account/components/delete_account_modal_spec.js
+1
-2
spec/javascripts/profile/account/components/update_username_spec.js
...cripts/profile/account/components/update_username_spec.js
+2
-2
spec/javascripts/related_merge_requests/store/actions_spec.js
.../javascripts/related_merge_requests/store/actions_spec.js
+1
-1
spec/javascripts/releases/list/components/app_spec.js
spec/javascripts/releases/list/components/app_spec.js
+2
-2
spec/javascripts/releases/list/store/actions_spec.js
spec/javascripts/releases/list/store/actions_spec.js
+1
-1
spec/javascripts/reports/components/modal_open_name_spec.js
spec/javascripts/reports/components/modal_open_name_spec.js
+1
-1
spec/javascripts/reports/components/summary_row_spec.js
spec/javascripts/reports/components/summary_row_spec.js
+1
-1
spec/javascripts/reports/store/actions_spec.js
spec/javascripts/reports/store/actions_spec.js
+2
-2
spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
...mr_widget/components/mr_widget_pipeline_container_spec.js
+2
-0
spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
...ripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+40
-0
spec/javascripts/vue_mr_widget/mock_data.js
spec/javascripts/vue_mr_widget/mock_data.js
+1
-0
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+57
-0
No files found.
app/assets/javascripts/error_tracking/components/error_tracking_list.vue
View file @
6b8040dc
...
@@ -3,11 +3,17 @@ import { mapActions, mapState } from 'vuex';
...
@@ -3,11 +3,17 @@ import { mapActions, mapState } from 'vuex';
import
{
import
{
GlEmptyState
,
GlEmptyState
,
GlButton
,
GlButton
,
GlIcon
,
GlLink
,
GlLink
,
GlLoadingIcon
,
GlLoadingIcon
,
GlTable
,
GlTable
,
GlSearchBoxByClick
,
GlFormInput
,
GlDropdown
,
GlDropdownItem
,
GlDropdownDivider
,
GlTooltipDirective
,
}
from
'
@gitlab/ui
'
;
}
from
'
@gitlab/ui
'
;
import
AccessorUtils
from
'
~/lib/utils/accessor
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
TimeAgo
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
TimeAgo
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
...
@@ -24,14 +30,19 @@ export default {
...
@@ -24,14 +30,19 @@ export default {
components
:
{
components
:
{
GlEmptyState
,
GlEmptyState
,
GlButton
,
GlButton
,
GlDropdown
,
GlDropdownItem
,
GlDropdownDivider
,
GlIcon
,
GlLink
,
GlLink
,
GlLoadingIcon
,
GlLoadingIcon
,
GlTable
,
GlTable
,
Gl
SearchBoxByClick
,
Gl
FormInput
,
Icon
,
Icon
,
TimeAgo
,
TimeAgo
,
},
},
directives
:
{
directives
:
{
GlTooltip
:
GlTooltipDirective
,
TrackEvent
:
TrackEventDirective
,
TrackEvent
:
TrackEventDirective
,
},
},
props
:
{
props
:
{
...
@@ -56,13 +67,14 @@ export default {
...
@@ -56,13 +67,14 @@ export default {
required
:
true
,
required
:
true
,
},
},
},
},
hasLocalStorage
:
AccessorUtils
.
isLocalStorageAccessSafe
(),
data
()
{
data
()
{
return
{
return
{
errorSearchQuery
:
''
,
errorSearchQuery
:
''
,
};
};
},
},
computed
:
{
computed
:
{
...
mapState
(
'
list
'
,
[
'
errors
'
,
'
externalUrl
'
,
'
loading
'
]),
...
mapState
(
'
list
'
,
[
'
errors
'
,
'
externalUrl
'
,
'
loading
'
,
'
recentSearches
'
]),
},
},
created
()
{
created
()
{
if
(
this
.
errorTrackingEnabled
)
{
if
(
this
.
errorTrackingEnabled
)
{
...
@@ -70,9 +82,23 @@ export default {
...
@@ -70,9 +82,23 @@ export default {
}
}
},
},
methods
:
{
methods
:
{
...
mapActions
(
'
list
'
,
[
'
startPolling
'
,
'
restartPolling
'
]),
...
mapActions
(
'
list
'
,
[
'
startPolling
'
,
'
restartPolling
'
,
'
addRecentSearch
'
,
'
clearRecentSearches
'
,
'
loadRecentSearches
'
,
'
setIndexPath
'
,
]),
filterErrors
()
{
filterErrors
()
{
this
.
startPolling
(
`
${
this
.
indexPath
}
?search_term=
${
this
.
errorSearchQuery
}
`
);
const
searchTerm
=
this
.
errorSearchQuery
.
trim
();
this
.
addRecentSearch
(
searchTerm
);
this
.
startPolling
(
`
${
this
.
indexPath
}
?search_term=
${
searchTerm
}
`
);
},
setSearchText
(
text
)
{
this
.
errorSearchQuery
=
text
;
this
.
filterErrors
();
},
},
trackViewInSentryOptions
,
trackViewInSentryOptions
,
getDetailsLink
(
errorId
)
{
getDetailsLink
(
errorId
)
{
...
@@ -85,81 +111,119 @@ export default {
...
@@ -85,81 +111,119 @@ export default {
<
template
>
<
template
>
<div>
<div>
<div
v-if=
"errorTrackingEnabled"
>
<div
v-if=
"errorTrackingEnabled"
>
<div>
<div
class=
"d-flex flex-row justify-content-around bg-secondary border p-3"
>
<div
class=
"d-flex flex-row justify-content-around bg-secondary border"
>
<div
class=
"filtered-search-box"
>
<gl-search-box-by-click
<gl-dropdown
v-model=
"errorSearchQuery"
:text=
"__('Recent searches')"
class=
"col-lg-10 m-3 p-0"
class=
"filtered-search-history-dropdown-wrapper d-none d-md-block"
:placeholder=
"__('Search or filter results...')"
toggle-class=
"filtered-search-history-dropdown-toggle-button"
type=
"search"
:disabled=
"loading"
autofocus
@
submit=
"filterErrors"
/>
<gl-button
v-track-event=
"trackViewInSentryOptions(externalUrl)"
class=
"m-3"
variant=
"primary"
:href=
"externalUrl"
target=
"_blank"
>
>
{{
__
(
'
View in Sentry
'
)
}}
<div
v-if=
"!$options.hasLocalStorage"
class=
"px-3"
>
<icon
name=
"external-link"
class=
"flex-shrink-0"
/>
{{
__
(
'
This feature requires local storage to be enabled
'
)
}}
</gl-button>
</div>
</div>
<template
v-else-if=
"recentSearches.length > 0"
>
<gl-dropdown-item
<div
v-if=
"loading"
class=
"py-3"
>
v-for=
"searchQuery in recentSearches"
<gl-loading-icon
size=
"md"
/>
:key=
"searchQuery"
@
click=
"setSearchText(searchQuery)"
>
{{
searchQuery
}}
</gl-dropdown-item
>
<gl-dropdown-divider
/>
<gl-dropdown-item
ref=
"clearRecentSearches"
@
click=
"clearRecentSearches"
>
{{
__
(
'
Clear recent searches
'
)
}}
</gl-dropdown-item>
</
template
>
<div
v-else
class=
"px-3"
>
{{ __("You don't have any recent searches") }}
</div>
</gl-dropdown>
<div
class=
"filtered-search-input-container flex-fill"
>
<gl-form-input
v-model=
"errorSearchQuery"
class=
"pl-2 filtered-search"
:disabled=
"loading"
:placeholder=
"__('Search or filter results…')"
autofocus
@
keyup.enter.native=
"filterErrors"
/>
</div>
<div
class=
"gl-search-box-by-type-right-icons"
>
<gl-button
v-if=
"errorSearchQuery.length > 0"
v-gl-tooltip
.
hover
:title=
"__('Clear')"
class=
"clear-search text-secondary"
name=
"clear"
@
click=
"errorSearchQuery = ''"
>
<gl-icon
name=
"close"
:size=
"12"
/>
</gl-button>
</div>
</div>
</div>
<gl-table
<gl-button
v-else
v-track-event=
"trackViewInSentryOptions(externalUrl)"
class=
"mt-3"
class=
"ml-3"
:items=
"errors"
variant=
"primary"
:fields=
"$options.fields"
:href=
"externalUrl"
:show-empty=
"true"
target=
"_blank"
fixed
stacked=
"sm"
>
>
<template
slot=
"HEAD_events"
slot-scope=
"data"
>
{{ __('View in Sentry') }}
<div
class=
"text-md-right"
>
{{
data
.
label
}}
</div>
<icon
name=
"external-link"
class=
"flex-shrink-0"
/>
</
template
>
</gl-button>
<
template
slot=
"HEAD_users"
slot-scope=
"data"
>
</div>
<div
class=
"text-md-right"
>
{{
data
.
label
}}
</div>
</
template
>
<
template
slot=
"error"
slot-scope=
"errors"
>
<div
class=
"d-flex flex-column"
>
<gl-link
class=
"d-flex text-dark"
:href=
"getDetailsLink(errors.item.id)"
>
<strong
class=
"text-truncate"
>
{{
errors
.
item
.
title
.
trim
()
}}
</strong>
</gl-link>
<span
class=
"text-secondary text-truncate"
>
{{
errors
.
item
.
culprit
}}
</span>
</div>
</
template
>
<
template
slot=
"events"
slot-scope=
"errors
"
>
<div
v-if=
"loading"
class=
"py-3
"
>
<div
class=
"text-md-right"
>
{{
errors
.
item
.
count
}}
</div
>
<gl-loading-icon
size=
"md"
/
>
</
template
>
</div
>
<
template
slot=
"users"
slot-scope=
"errors"
>
<gl-table
<div
class=
"text-md-right"
>
{{
errors
.
item
.
userCount
}}
</div>
v-else
</
template
>
class=
"mt-3"
:items=
"errors"
:fields=
"$options.fields"
:show-empty=
"true"
fixed
stacked=
"sm"
>
<
template
slot=
"HEAD_events"
slot-scope=
"data"
>
<div
class=
"text-md-right"
>
{{
data
.
label
}}
</div>
</
template
>
<
template
slot=
"HEAD_users"
slot-scope=
"data"
>
<div
class=
"text-md-right"
>
{{
data
.
label
}}
</div>
</
template
>
<
template
slot=
"error"
slot-scope=
"errors"
>
<div
class=
"d-flex flex-column"
>
<gl-link
class=
"d-flex text-dark"
:href=
"getDetailsLink(errors.item.id)"
>
<strong
class=
"text-truncate"
>
{{
errors
.
item
.
title
.
trim
()
}}
</strong>
</gl-link>
<span
class=
"text-secondary text-truncate"
>
{{
errors
.
item
.
culprit
}}
</span>
</div>
</
template
>
<
template
slot=
"lastSeen"
slot-scope=
"errors"
>
<
template
slot=
"events"
slot-scope=
"errors"
>
<div
class=
"d-flex align-items-center"
>
<div
class=
"text-md-right"
>
{{
errors
.
item
.
count
}}
</div>
<time-ago
:time=
"errors.item.lastSeen"
class=
"text-secondary"
/>
</
template
>
</div>
</
template
>
<
template
slot=
"users"
slot-scope=
"errors"
>
<
template
slot=
"empty"
>
<div
class=
"text-md-right"
>
{{
errors
.
item
.
userCount
}}
</div>
<div
ref=
"empty"
>
</
template
>
{{
__
(
'
No errors to display.
'
)
}}
<gl-link
class=
"js-try-again"
@
click=
"restartPolling"
>
<
template
slot=
"lastSeen"
slot-scope=
"errors"
>
{{
__
(
'
Check again
'
)
}}
<div
class=
"d-flex align-items-center"
>
</gl-link>
<time-ago
:time=
"errors.item.lastSeen"
class=
"text-secondary"
/>
</div>
</div>
</
template
>
</
template
>
</gl-table>
<
template
slot=
"empty"
>
</div>
<div
ref=
"empty"
>
{{
__
(
'
No errors to display.
'
)
}}
<gl-link
class=
"js-try-again"
@
click=
"restartPolling"
>
{{
__
(
'
Check again
'
)
}}
</gl-link>
</div>
</
template
>
</gl-table>
</div>
</div>
<div
v-else-if=
"userCanEnableErrorTracking"
>
<div
v-else-if=
"userCanEnableErrorTracking"
>
<gl-empty-state
<gl-empty-state
...
...
app/assets/javascripts/error_tracking/store/list/actions.js
View file @
6b8040dc
...
@@ -51,4 +51,20 @@ export function restartPolling({ commit }) {
...
@@ -51,4 +51,20 @@ export function restartPolling({ commit }) {
if
(
eTagPoll
)
eTagPoll
.
restart
();
if
(
eTagPoll
)
eTagPoll
.
restart
();
}
}
export
function
setIndexPath
({
commit
},
path
)
{
commit
(
types
.
SET_INDEX_PATH
,
path
);
}
export
function
loadRecentSearches
({
commit
})
{
commit
(
types
.
LOAD_RECENT_SEARCHES
);
}
export
function
addRecentSearch
({
commit
},
searchQuery
)
{
commit
(
types
.
ADD_RECENT_SEARCH
,
searchQuery
);
}
export
function
clearRecentSearches
({
commit
})
{
commit
(
types
.
CLEAR_RECENT_SEARCHES
);
}
export
default
()
=>
{};
export
default
()
=>
{};
app/assets/javascripts/error_tracking/store/list/mutation_types.js
View file @
6b8040dc
export
const
SET_ERRORS
=
'
SET_ERRORS
'
;
export
const
SET_ERRORS
=
'
SET_ERRORS
'
;
export
const
SET_EXTERNAL_URL
=
'
SET_EXTERNAL_URL
'
;
export
const
SET_EXTERNAL_URL
=
'
SET_EXTERNAL_URL
'
;
export
const
SET_INDEX_PATH
=
'
SET_INDEX_PATH
'
;
export
const
SET_LOADING
=
'
SET_LOADING
'
;
export
const
SET_LOADING
=
'
SET_LOADING
'
;
export
const
ADD_RECENT_SEARCH
=
'
ADD_RECENT_SEARCH
'
;
export
const
CLEAR_RECENT_SEARCHES
=
'
CLEAR_RECENT_SEARCHES
'
;
export
const
LOAD_RECENT_SEARCHES
=
'
LOAD_RECENT_SEARCHES
'
;
app/assets/javascripts/error_tracking/store/list/mutations.js
View file @
6b8040dc
import
*
as
types
from
'
./mutation_types
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
AccessorUtils
from
'
~/lib/utils/accessor
'
;
export
default
{
export
default
{
[
types
.
SET_ERRORS
](
state
,
data
)
{
[
types
.
SET_ERRORS
](
state
,
data
)
{
...
@@ -11,4 +12,39 @@ export default {
...
@@ -11,4 +12,39 @@ export default {
[
types
.
SET_LOADING
](
state
,
loading
)
{
[
types
.
SET_LOADING
](
state
,
loading
)
{
state
.
loading
=
loading
;
state
.
loading
=
loading
;
},
},
[
types
.
SET_INDEX_PATH
](
state
,
path
)
{
state
.
indexPath
=
path
;
},
[
types
.
ADD_RECENT_SEARCH
](
state
,
searchTerm
)
{
if
(
searchTerm
.
length
===
0
)
{
return
;
}
// remove any existing item, then add it to the start of the list
const
recentSearches
=
state
.
recentSearches
.
filter
(
s
=>
s
!==
searchTerm
);
recentSearches
.
unshift
(
searchTerm
);
// only keep the last 5
state
.
recentSearches
=
recentSearches
.
slice
(
0
,
5
);
if
(
AccessorUtils
.
isLocalStorageAccessSafe
())
{
localStorage
.
setItem
(
`recent-searches
${
state
.
indexPath
}
`
,
JSON
.
stringify
(
state
.
recentSearches
),
);
}
},
[
types
.
CLEAR_RECENT_SEARCHES
](
state
)
{
state
.
recentSearches
=
[];
if
(
AccessorUtils
.
isLocalStorageAccessSafe
())
{
localStorage
.
removeItem
(
`recent-searches
${
state
.
indexPath
}
`
);
}
},
[
types
.
LOAD_RECENT_SEARCHES
](
state
)
{
const
recentSearches
=
localStorage
.
getItem
(
`recent-searches
${
state
.
indexPath
}
`
)
||
[];
try
{
state
.
recentSearches
=
JSON
.
parse
(
recentSearches
);
}
catch
(
e
)
{
state
.
recentSearches
=
[];
throw
e
;
}
},
};
};
app/assets/javascripts/error_tracking/store/list/state.js
View file @
6b8040dc
...
@@ -2,4 +2,6 @@ export default () => ({
...
@@ -2,4 +2,6 @@ export default () => ({
errors
:
[],
errors
:
[],
externalUrl
:
''
,
externalUrl
:
''
,
loading
:
true
,
loading
:
true
,
indexPath
:
''
,
recentSearches
:
[],
});
});
app/assets/javascripts/monitoring/components/dashboard.vue
View file @
6b8040dc
<
script
>
<
script
>
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
mapActions
,
mapState
,
mapGetters
}
from
'
vuex
'
;
import
VueDraggable
from
'
vuedraggable
'
;
import
VueDraggable
from
'
vuedraggable
'
;
import
{
import
{
GlButton
,
GlButton
,
...
@@ -99,6 +99,10 @@ export default {
...
@@ -99,6 +99,10 @@ export default {
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
emptyNoDataSmallSvgPath
:
{
type
:
String
,
required
:
true
,
},
emptyUnableToConnectSvgPath
:
{
emptyUnableToConnectSvgPath
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
...
@@ -176,11 +180,11 @@ export default {
...
@@ -176,11 +180,11 @@ export default {
'
showEmptyState
'
,
'
showEmptyState
'
,
'
environments
'
,
'
environments
'
,
'
deploymentData
'
,
'
deploymentData
'
,
'
metricsWithData
'
,
'
useDashboardEndpoint
'
,
'
useDashboardEndpoint
'
,
'
allDashboards
'
,
'
allDashboards
'
,
'
additionalPanelTypesEnabled
'
,
'
additionalPanelTypesEnabled
'
,
]),
]),
...
mapGetters
(
'
monitoringDashboard
'
,
[
'
metricsWithData
'
]),
firstDashboard
()
{
firstDashboard
()
{
return
this
.
environmentsEndpoint
.
length
>
0
&&
this
.
allDashboards
.
length
>
0
return
this
.
environmentsEndpoint
.
length
>
0
&&
this
.
allDashboards
.
length
>
0
?
this
.
allDashboards
[
0
]
?
this
.
allDashboards
[
0
]
...
@@ -280,13 +284,8 @@ export default {
...
@@ -280,13 +284,8 @@ export default {
submitCustomMetricsForm
()
{
submitCustomMetricsForm
()
{
this
.
$refs
.
customMetricsForm
.
submit
();
this
.
$refs
.
customMetricsForm
.
submit
();
},
},
chartsWithData
(
panels
)
{
return
panels
.
filter
(
panel
=>
panel
.
metrics
.
some
(
metric
=>
this
.
metricsWithData
.
includes
(
metric
.
metric_id
)),
);
},
groupHasData
(
group
)
{
groupHasData
(
group
)
{
return
this
.
chartsWithData
(
group
.
panels
).
length
>
0
;
return
this
.
metricsWithData
(
group
.
key
).
length
>
0
;
},
},
onDateTimePickerApply
(
timeWindowUrlParams
)
{
onDateTimePickerApply
(
timeWindowUrlParams
)
{
return
redirectTo
(
mergeUrlParams
(
timeWindowUrlParams
,
window
.
location
.
href
));
return
redirectTo
(
mergeUrlParams
(
timeWindowUrlParams
,
window
.
location
.
href
));
...
@@ -447,42 +446,61 @@ export default {
...
@@ -447,42 +446,61 @@ export default {
:key=
"`${groupData.group}.${groupData.priority}`"
:key=
"`${groupData.group}.${groupData.priority}`"
:name=
"groupData.group"
:name=
"groupData.group"
:show-panels=
"showPanels"
:show-panels=
"showPanels"
:collapse-group=
"groupHasData(groupData)"
:collapse-group=
"
!
groupHasData(groupData)"
>
>
<vue-draggable
<div
v-if=
"groupHasData(groupData)"
>
:value=
"groupData.panels"
<vue-draggable
group=
"metrics-dashboard"
:value=
"groupData.panels"
:component-data=
"{ attrs: { class: 'row mx-0 w-100' } }"
group=
"metrics-dashboard"
:disabled=
"!isRearrangingPanels"
:component-data=
"{ attrs: { class: 'row mx-0 w-100' } }"
@
input=
"updatePanels(groupData.key, $event)"
:disabled=
"!isRearrangingPanels"
>
@
input=
"updatePanels(groupData.key, $event)"
<div
v-for=
"(graphData, graphIndex) in groupData.panels"
:key=
"`panel-type-${graphIndex}`"
class=
"col-12 col-lg-6 px-2 mb-2 draggable"
:class=
"{ 'draggable-enabled': isRearrangingPanels }"
>
>
<div
class=
"position-relative draggable-panel js-draggable-panel"
>
<div
<div
v-for=
"(graphData, graphIndex) in groupData.panels"
v-if=
"isRearrangingPanels"
:key=
"`panel-type-${graphIndex}`"
class=
"draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end"
class=
"col-12 col-lg-6 px-2 mb-2 draggable"
@
click=
"removePanel(groupData.key, groupData.panels, graphIndex)"
:class=
"{ 'draggable-enabled': isRearrangingPanels }"
>
>
<a
class=
"mx-2 p-2 draggable-remove-link"
:aria-label=
"__('Remove')"
<div
class=
"position-relative draggable-panel js-draggable-panel"
>
><icon
name=
"close"
<div
/></a>
v-if=
"isRearrangingPanels"
</div>
class=
"draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end"
@
click=
"removePanel(groupData.key, groupData.panels, graphIndex)"
>
<a
class=
"mx-2 p-2 draggable-remove-link"
:aria-label=
"__('Remove')"
><icon
name=
"close"
/></a>
</div>
<panel-type
<panel-type
:clipboard-text=
"generateLink(groupData.group, graphData.title, graphData.y_label)"
:clipboard-text=
"
:graph-data=
"graphData"
generateLink(groupData.group, graphData.title, graphData.y_label)
:alerts-endpoint=
"alertsEndpoint"
"
:prometheus-alerts-available=
"prometheusAlertsAvailable"
:graph-data=
"graphData"
:index=
"`${index}-${graphIndex}`"
:alerts-endpoint=
"alertsEndpoint"
/>
:prometheus-alerts-available=
"prometheusAlertsAvailable"
:index=
"`${index}-${graphIndex}`"
/>
</div>
</div>
</div>
</div>
</vue-draggable>
</vue-draggable>
</div>
<div
v-else
class=
"py-5 col col-sm-10 col-md-8 col-lg-7 col-xl-6"
>
<empty-state
ref=
"empty-group"
selected-state=
"noDataGroup"
:documentation-path=
"documentationPath"
:settings-path=
"settingsPath"
:clusters-path=
"clustersPath"
:empty-getting-started-svg-path=
"emptyGettingStartedSvgPath"
:empty-loading-svg-path=
"emptyLoadingSvgPath"
:empty-no-data-svg-path=
"emptyNoDataSvgPath"
:empty-no-data-small-svg-path=
"emptyNoDataSmallSvgPath"
:empty-unable-to-connect-svg-path=
"emptyUnableToConnectSvgPath"
:compact=
"true"
/>
</div>
</graph-group>
</graph-group>
</div>
</div>
<empty-state
<empty-state
...
@@ -494,6 +512,7 @@ export default {
...
@@ -494,6 +512,7 @@ export default {
:empty-getting-started-svg-path=
"emptyGettingStartedSvgPath"
:empty-getting-started-svg-path=
"emptyGettingStartedSvgPath"
:empty-loading-svg-path=
"emptyLoadingSvgPath"
:empty-loading-svg-path=
"emptyLoadingSvgPath"
:empty-no-data-svg-path=
"emptyNoDataSvgPath"
:empty-no-data-svg-path=
"emptyNoDataSvgPath"
:empty-no-data-small-svg-path=
"emptyNoDataSmallSvgPath"
:empty-unable-to-connect-svg-path=
"emptyUnableToConnectSvgPath"
:empty-unable-to-connect-svg-path=
"emptyUnableToConnectSvgPath"
:compact=
"smallEmptyState"
:compact=
"smallEmptyState"
/>
/>
...
...
app/assets/javascripts/monitoring/components/embed.vue
View file @
6b8040dc
<
script
>
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
mapActions
,
mapState
,
mapGetters
}
from
'
vuex
'
;
import
{
getParameterValues
,
removeParams
}
from
'
~/lib/utils/url_utility
'
;
import
{
getParameterValues
,
removeParams
}
from
'
~/lib/utils/url_utility
'
;
import
PanelType
from
'
ee_else_ce/monitoring/components/panel_type.vue
'
;
import
PanelType
from
'
ee_else_ce/monitoring/components/panel_type.vue
'
;
import
GraphGroup
from
'
./graph_group.vue
'
;
import
GraphGroup
from
'
./graph_group.vue
'
;
...
@@ -35,7 +35,8 @@ export default {
...
@@ -35,7 +35,8 @@ export default {
};
};
},
},
computed
:
{
computed
:
{
...
mapState
(
'
monitoringDashboard
'
,
[
'
dashboard
'
,
'
metricsWithData
'
]),
...
mapState
(
'
monitoringDashboard
'
,
[
'
dashboard
'
]),
...
mapGetters
(
'
monitoringDashboard
'
,
[
'
metricsWithData
'
]),
charts
()
{
charts
()
{
if
(
!
this
.
dashboard
||
!
this
.
dashboard
.
panel_groups
)
{
if
(
!
this
.
dashboard
||
!
this
.
dashboard
.
panel_groups
)
{
return
[];
return
[];
...
@@ -73,7 +74,7 @@ export default {
...
@@ -73,7 +74,7 @@ export default {
'
setShowErrorBanner
'
,
'
setShowErrorBanner
'
,
]),
]),
chartHasData
(
chart
)
{
chartHasData
(
chart
)
{
return
chart
.
metrics
.
some
(
metric
=>
this
.
metricsWithData
.
includes
(
metric
.
metric_id
));
return
chart
.
metrics
.
some
(
metric
=>
this
.
metricsWithData
()
.
includes
(
metric
.
metric_id
));
},
},
onSidebarMutation
()
{
onSidebarMutation
()
{
setTimeout
(()
=>
{
setTimeout
(()
=>
{
...
...
app/assets/javascripts/monitoring/components/empty_state.vue
View file @
6b8040dc
...
@@ -37,6 +37,10 @@ export default {
...
@@ -37,6 +37,10 @@ export default {
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
emptyNoDataSmallSvgPath
:
{
type
:
String
,
required
:
true
,
},
emptyUnableToConnectSvgPath
:
{
emptyUnableToConnectSvgPath
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
...
@@ -80,6 +84,11 @@ export default {
...
@@ -80,6 +84,11 @@ export default {
secondaryButtonText
:
''
,
secondaryButtonText
:
''
,
secondaryButtonPath
:
''
,
secondaryButtonPath
:
''
,
},
},
noDataGroup
:
{
svgUrl
:
this
.
emptyNoDataSmallSvgPath
,
title
:
__
(
'
No data to display
'
),
description
:
__
(
'
The data source is connected, but there is no data to display.
'
),
},
unableToConnect
:
{
unableToConnect
:
{
svgUrl
:
this
.
emptyUnableToConnectSvgPath
,
svgUrl
:
this
.
emptyUnableToConnectSvgPath
,
title
:
__
(
'
Unable to connect to Prometheus server
'
),
title
:
__
(
'
Unable to connect to Prometheus server
'
),
...
...
app/assets/javascripts/monitoring/components/graph_group.vue
View file @
6b8040dc
...
@@ -15,31 +15,44 @@ export default {
...
@@ -15,31 +15,44 @@ export default {
required
:
false
,
required
:
false
,
default
:
true
,
default
:
true
,
},
},
/**
* Initial value of collapse on mount.
*/
collapseGroup
:
{
collapseGroup
:
{
type
:
Boolean
,
type
:
Boolean
,
required
:
true
,
required
:
false
,
default
:
false
,
},
},
},
},
data
()
{
data
()
{
return
{
return
{
showGroup
:
true
,
isCollapsed
:
this
.
collapseGroup
,
};
};
},
},
computed
:
{
computed
:
{
caretIcon
()
{
caretIcon
()
{
return
this
.
collapseGroup
&&
this
.
showGroup
?
'
angle-down
'
:
'
angle-right
'
;
return
this
.
isCollapsed
?
'
angle-right
'
:
'
angle-down
'
;
},
},
watch
:
{
collapseGroup
(
val
)
{
// Respond to changes in collapseGroup but do not
// collapse it once was opened by the user.
if
(
this
.
showPanels
&&
!
val
)
{
this
.
isCollapsed
=
false
;
}
},
},
},
},
methods
:
{
methods
:
{
collapse
()
{
collapse
()
{
this
.
showGroup
=
!
this
.
showGroup
;
this
.
isCollapsed
=
!
this
.
isCollapsed
;
},
},
},
},
};
};
</
script
>
</
script
>
<
template
>
<
template
>
<div
v-if=
"showPanels"
class=
"card prometheus-panel"
>
<div
v-if=
"showPanels"
ref=
"graph-group"
class=
"card prometheus-panel"
>
<div
class=
"card-header d-flex align-items-center"
>
<div
class=
"card-header d-flex align-items-center"
>
<h4
class=
"flex-grow-1"
>
{{
name
}}
</h4>
<h4
class=
"flex-grow-1"
>
{{
name
}}
</h4>
<a
role=
"button"
class=
"js-graph-group-toggle"
@
click=
"collapse"
>
<a
role=
"button"
class=
"js-graph-group-toggle"
@
click=
"collapse"
>
...
@@ -47,12 +60,12 @@ export default {
...
@@ -47,12 +60,12 @@ export default {
</a>
</a>
</div>
</div>
<div
<div
v-
if=
"collapseGroup
"
v-
show=
"!isCollapsed
"
v-show=
"collapseGroup && showGroup
"
ref=
"graph-group-content
"
class=
"card-body prometheus-graph-group p-0"
class=
"card-body prometheus-graph-group p-0"
>
>
<slot></slot>
<slot></slot>
</div>
</div>
</div>
</div>
<div
v-else
class=
"prometheus-graph-group"
><slot></slot></div>
<div
v-else
ref=
"graph-group-content"
class=
"prometheus-graph-group"
><slot></slot></div>
</
template
>
</
template
>
app/assets/javascripts/monitoring/stores/actions.js
View file @
6b8040dc
...
@@ -4,7 +4,7 @@ import createFlash from '~/flash';
...
@@ -4,7 +4,7 @@ import createFlash from '~/flash';
import
trackDashboardLoad
from
'
../monitoring_tracking_helper
'
;
import
trackDashboardLoad
from
'
../monitoring_tracking_helper
'
;
import
statusCodes
from
'
../../lib/utils/http_status
'
;
import
statusCodes
from
'
../../lib/utils/http_status
'
;
import
{
backOff
}
from
'
../../lib/utils/common_utils
'
;
import
{
backOff
}
from
'
../../lib/utils/common_utils
'
;
import
{
s__
}
from
'
../../locale
'
;
import
{
s__
,
sprintf
}
from
'
../../locale
'
;
const
TWO_MINUTES
=
120000
;
const
TWO_MINUTES
=
120000
;
...
@@ -74,17 +74,21 @@ export const fetchDashboard = ({ state, dispatch }, params) => {
...
@@ -74,17 +74,21 @@ export const fetchDashboard = ({ state, dispatch }, params) => {
return
backOffRequest
(()
=>
axios
.
get
(
state
.
dashboardEndpoint
,
{
params
}))
return
backOffRequest
(()
=>
axios
.
get
(
state
.
dashboardEndpoint
,
{
params
}))
.
then
(
resp
=>
resp
.
data
)
.
then
(
resp
=>
resp
.
data
)
.
then
(
response
=>
dispatch
(
'
receiveMetricsDashboardSuccess
'
,
{
response
,
params
}))
.
then
(
response
=>
dispatch
(
'
receiveMetricsDashboardSuccess
'
,
{
response
,
params
}))
.
then
(()
=>
{
.
catch
(
e
=>
{
const
dashboardType
=
state
.
currentDashboard
===
''
?
'
default
'
:
'
custom
'
;
dispatch
(
'
receiveMetricsDashboardFailure
'
,
e
);
return
trackDashboardLoad
({
if
(
state
.
showErrorBanner
)
{
label
:
`
${
dashboardType
}
_metrics_dashboard`
,
if
(
e
.
response
.
data
&&
e
.
response
.
data
.
message
)
{
value
:
state
.
metricsWithData
.
length
,
const
{
message
}
=
e
.
response
.
data
;
});
createFlash
(
})
sprintf
(
.
catch
(
error
=>
{
s__
(
'
Metrics|There was an error while retrieving metrics. %{message}
'
),
dispatch
(
'
receiveMetricsDashboardFailure
'
,
error
);
{
message
},
if
(
state
.
setShowErrorBanner
)
{
false
,
createFlash
(
s__
(
'
Metrics|There was an error while retrieving metrics
'
));
),
);
}
else
{
createFlash
(
s__
(
'
Metrics|There was an error while retrieving metrics
'
));
}
}
}
});
});
};
};
...
@@ -126,7 +130,7 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
...
@@ -126,7 +130,7 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
});
});
};
};
export
const
fetchPrometheusMetrics
=
({
state
,
commit
,
dispatch
},
params
)
=>
{
export
const
fetchPrometheusMetrics
=
({
state
,
commit
,
dispatch
,
getters
},
params
)
=>
{
commit
(
types
.
REQUEST_METRICS_DATA
);
commit
(
types
.
REQUEST_METRICS_DATA
);
const
promises
=
[];
const
promises
=
[];
...
@@ -140,9 +144,11 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
...
@@ -140,9 +144,11 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
return
Promise
.
all
(
promises
)
return
Promise
.
all
(
promises
)
.
then
(()
=>
{
.
then
(()
=>
{
if
(
state
.
metricsWithData
.
length
===
0
)
{
const
dashboardType
=
state
.
currentDashboard
===
''
?
'
default
'
:
'
custom
'
;
commit
(
types
.
SET_NO_DATA_EMPTY_STATE
);
trackDashboardLoad
({
}
label
:
`
${
dashboardType
}
_metrics_dashboard`
,
value
:
getters
.
metricsWithData
().
length
,
});
})
})
.
catch
(()
=>
{
.
catch
(()
=>
{
createFlash
(
s__
(
`Metrics|There was an error while retrieving metrics`
),
'
warning
'
);
createFlash
(
s__
(
`Metrics|There was an error while retrieving metrics`
),
'
warning
'
);
...
...
app/assets/javascripts/monitoring/stores/getters.js
0 → 100644
View file @
6b8040dc
const
metricsIdsInPanel
=
panel
=>
panel
.
metrics
.
filter
(
metric
=>
metric
.
metricId
&&
metric
.
result
).
map
(
metric
=>
metric
.
metricId
);
/**
* Getter to obtain the list of metric ids that have data
*
* Useful to understand which parts of the dashboard should
* be displayed. It is a Vuex Method-Style Access getter.
*
* @param {Object} state
* @returns {Function} A function that returns an array of
* metrics in the dashboard that contain results, optionally
* filtered by group key.
*/
export
const
metricsWithData
=
state
=>
groupKey
=>
{
let
groups
=
state
.
dashboard
.
panel_groups
;
if
(
groupKey
)
{
groups
=
groups
.
filter
(
group
=>
group
.
key
===
groupKey
);
}
const
res
=
[];
groups
.
forEach
(
group
=>
{
group
.
panels
.
forEach
(
panel
=>
{
res
.
push
(...
metricsIdsInPanel
(
panel
));
});
});
return
res
;
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export
default
()
=>
{};
app/assets/javascripts/monitoring/stores/index.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
Vuex
from
'
vuex
'
;
import
*
as
actions
from
'
./actions
'
;
import
*
as
actions
from
'
./actions
'
;
import
*
as
getters
from
'
./getters
'
;
import
mutations
from
'
./mutations
'
;
import
mutations
from
'
./mutations
'
;
import
state
from
'
./state
'
;
import
state
from
'
./state
'
;
...
@@ -12,6 +13,7 @@ export const createStore = () =>
...
@@ -12,6 +13,7 @@ export const createStore = () =>
monitoringDashboard
:
{
monitoringDashboard
:
{
namespaced
:
true
,
namespaced
:
true
,
actions
,
actions
,
getters
,
mutations
,
mutations
,
state
,
state
,
},
},
...
...
app/assets/javascripts/monitoring/stores/mutations.js
View file @
6b8040dc
...
@@ -67,7 +67,6 @@ export default {
...
@@ -67,7 +67,6 @@ export default {
group
.
panels
.
forEach
(
panel
=>
{
group
.
panels
.
forEach
(
panel
=>
{
panel
.
metrics
.
forEach
(
metric
=>
{
panel
.
metrics
.
forEach
(
metric
=>
{
if
(
metric
.
metric_id
===
metricId
)
{
if
(
metric
.
metric_id
===
metricId
)
{
state
.
metricsWithData
.
push
(
metricId
);
// ensure dates/numbers are correctly formatted for charts
// ensure dates/numbers are correctly formatted for charts
const
normalizedResults
=
result
.
map
(
normalizeQueryResult
);
const
normalizedResults
=
result
.
map
(
normalizeQueryResult
);
Vue
.
set
(
metric
,
'
result
'
,
Object
.
freeze
(
normalizedResults
));
Vue
.
set
(
metric
,
'
result
'
,
Object
.
freeze
(
normalizedResults
));
...
...
app/assets/javascripts/monitoring/stores/state.js
View file @
6b8040dc
...
@@ -13,7 +13,6 @@ export default () => ({
...
@@ -13,7 +13,6 @@ export default () => ({
},
},
deploymentData
:
[],
deploymentData
:
[],
environments
:
[],
environments
:
[],
metricsWithData
:
[],
allDashboards
:
[],
allDashboards
:
[],
currentDashboard
:
null
,
currentDashboard
:
null
,
projectPath
:
null
,
projectPath
:
null
,
...
...
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
View file @
6b8040dc
...
@@ -28,6 +28,10 @@ export default {
...
@@ -28,6 +28,10 @@ export default {
type
:
Object
,
type
:
Object
,
required
:
true
,
required
:
true
,
},
},
pipelineCoverageDelta
:
{
type
:
String
,
required
:
false
,
},
// This prop needs to be camelCase, html attributes are case insensive
// This prop needs to be camelCase, html attributes are case insensive
// https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
// https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
hasCi
:
{
hasCi
:
{
...
@@ -92,6 +96,16 @@ export default {
...
@@ -92,6 +96,16 @@ export default {
showSourceBranch
()
{
showSourceBranch
()
{
return
Boolean
(
this
.
pipeline
.
ref
.
branch
);
return
Boolean
(
this
.
pipeline
.
ref
.
branch
);
},
},
coverageDeltaClass
()
{
const
delta
=
this
.
pipelineCoverageDelta
;
if
(
delta
&&
parseFloat
(
delta
)
>
0
)
{
return
'
text-success
'
;
}
if
(
delta
&&
parseFloat
(
delta
)
<
0
)
{
return
'
text-danger
'
;
}
return
''
;
},
},
},
};
};
</
script
>
</
script
>
...
@@ -142,6 +156,14 @@ export default {
...
@@ -142,6 +156,14 @@ export default {
</div>
</div>
<div
v-if=
"pipeline.coverage"
class=
"coverage"
>
<div
v-if=
"pipeline.coverage"
class=
"coverage"
>
{{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}%
{{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}%
<span
v-if=
"pipelineCoverageDelta"
class=
"js-pipeline-coverage-delta"
:class=
"coverageDeltaClass"
>
({{ pipelineCoverageDelta }}%)
</span>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
View file @
6b8040dc
...
@@ -76,6 +76,7 @@ export default {
...
@@ -76,6 +76,7 @@ export default {
<mr-widget-container>
<mr-widget-container>
<mr-widget-pipeline
<mr-widget-pipeline
:pipeline=
"pipeline"
:pipeline=
"pipeline"
:pipeline-coverage-delta=
"mr.pipelineCoverageDelta"
:ci-status=
"mr.ciStatus"
:ci-status=
"mr.ciStatus"
:has-ci=
"mr.hasCI"
:has-ci=
"mr.hasCI"
:source-branch=
"branch"
:source-branch=
"branch"
...
...
app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
View file @
6b8040dc
...
@@ -42,6 +42,7 @@ export default class MergeRequestStore {
...
@@ -42,6 +42,7 @@ export default class MergeRequestStore {
this
.
commitsCount
=
data
.
commits_count
;
this
.
commitsCount
=
data
.
commits_count
;
this
.
divergedCommitsCount
=
data
.
diverged_commits_count
;
this
.
divergedCommitsCount
=
data
.
diverged_commits_count
;
this
.
pipeline
=
data
.
pipeline
||
{};
this
.
pipeline
=
data
.
pipeline
||
{};
this
.
pipelineCoverageDelta
=
data
.
pipeline_coverage_delta
;
this
.
mergePipeline
=
data
.
merge_pipeline
||
{};
this
.
mergePipeline
=
data
.
merge_pipeline
||
{};
this
.
deployments
=
this
.
deployments
||
data
.
deployments
||
[];
this
.
deployments
=
this
.
deployments
||
data
.
deployments
||
[];
this
.
postMergeDeployments
=
this
.
postMergeDeployments
||
[];
this
.
postMergeDeployments
=
this
.
postMergeDeployments
||
[];
...
...
app/assets/stylesheets/framework/common.scss
View file @
6b8040dc
...
@@ -515,6 +515,12 @@ img.emoji {
...
@@ -515,6 +515,12 @@ img.emoji {
cursor
:
pointer
;
cursor
:
pointer
;
}
}
// this needs to use "!important" due to some very specific styles
// around buttons
.cursor-default
{
cursor
:
default
!
important
;
}
// Make buttons/dropdowns full-width on mobile
// Make buttons/dropdowns full-width on mobile
.full-width-mobile
{
.full-width-mobile
{
@include
media-breakpoint-down
(
xs
)
{
@include
media-breakpoint-down
(
xs
)
{
...
...
app/assets/stylesheets/framework/filters.scss
View file @
6b8040dc
...
@@ -214,8 +214,8 @@
...
@@ -214,8 +214,8 @@
padding-left
:
0
;
padding-left
:
0
;
height
:
$input-height
-
2
;
height
:
$input-height
-
2
;
line-height
:
inherit
;
line-height
:
inherit
;
border-color
:
transparent
;
&
,
&
:focus
,
&
:focus
,
&
:hover
{
&
:hover
{
outline
:
none
;
outline
:
none
;
...
...
app/assets/stylesheets/pages/prometheus.scss
View file @
6b8040dc
...
@@ -67,7 +67,6 @@
...
@@ -67,7 +67,6 @@
.prometheus-graph-group
{
.prometheus-graph-group
{
display
:
flex
;
display
:
flex
;
flex-wrap
:
wrap
;
flex-wrap
:
wrap
;
margin-top
:
$gl-padding-8
;
}
}
.prometheus-graph
{
.prometheus-graph
{
...
...
app/helpers/environments_helper.rb
View file @
6b8040dc
...
@@ -26,6 +26,7 @@ module EnvironmentsHelper
...
@@ -26,6 +26,7 @@ module EnvironmentsHelper
"empty-getting-started-svg-path"
=>
image_path
(
'illustrations/monitoring/getting_started.svg'
),
"empty-getting-started-svg-path"
=>
image_path
(
'illustrations/monitoring/getting_started.svg'
),
"empty-loading-svg-path"
=>
image_path
(
'illustrations/monitoring/loading.svg'
),
"empty-loading-svg-path"
=>
image_path
(
'illustrations/monitoring/loading.svg'
),
"empty-no-data-svg-path"
=>
image_path
(
'illustrations/monitoring/no_data.svg'
),
"empty-no-data-svg-path"
=>
image_path
(
'illustrations/monitoring/no_data.svg'
),
"empty-no-data-small-svg-path"
=>
image_path
(
'illustrations/chart-empty-state-small.svg'
),
"empty-unable-to-connect-svg-path"
=>
image_path
(
'illustrations/monitoring/unable_to_connect.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
),
"metrics-endpoint"
=>
additional_metrics_project_environment_path
(
project
,
environment
,
format: :json
),
"dashboard-endpoint"
=>
metrics_dashboard_project_environment_path
(
project
,
environment
,
format: :json
),
"dashboard-endpoint"
=>
metrics_dashboard_project_environment_path
(
project
,
environment
,
format: :json
),
...
...
app/models/application_setting.rb
View file @
6b8040dc
...
@@ -5,9 +5,6 @@ class ApplicationSetting < ApplicationRecord
...
@@ -5,9 +5,6 @@ class ApplicationSetting < ApplicationRecord
include
CacheMarkdownField
include
CacheMarkdownField
include
TokenAuthenticatable
include
TokenAuthenticatable
include
ChronicDurationAttribute
include
ChronicDurationAttribute
include
IgnorableColumns
ignore_columns
:pendo_enabled
,
:pendo_url
,
remove_after:
'2019-12-01'
,
remove_with:
'12.6'
add_authentication_token_field
:runners_registration_token
,
encrypted:
->
{
Feature
.
enabled?
(
:application_settings_tokens_optional_encryption
,
default_enabled:
true
)
?
:optional
:
:required
}
add_authentication_token_field
:runners_registration_token
,
encrypted:
->
{
Feature
.
enabled?
(
:application_settings_tokens_optional_encryption
,
default_enabled:
true
)
?
:optional
:
:required
}
add_authentication_token_field
:health_check_access_token
add_authentication_token_field
:health_check_access_token
...
...
app/models/merge_request.rb
View file @
6b8040dc
...
@@ -1423,6 +1423,12 @@ class MergeRequest < ApplicationRecord
...
@@ -1423,6 +1423,12 @@ class MergeRequest < ApplicationRecord
true
true
end
end
def
pipeline_coverage_delta
if
base_pipeline
&
.
coverage
&&
head_pipeline
&
.
coverage
'%.2f'
%
(
head_pipeline
.
coverage
.
to_f
-
base_pipeline
.
coverage
.
to_f
)
end
end
def
base_pipeline
def
base_pipeline
@base_pipeline
||=
project
.
ci_pipelines
@base_pipeline
||=
project
.
ci_pipelines
.
order
(
id: :desc
)
.
order
(
id: :desc
)
...
...
app/serializers/merge_request_poll_widget_entity.rb
View file @
6b8040dc
...
@@ -57,6 +57,10 @@ class MergeRequestPollWidgetEntity < Grape::Entity
...
@@ -57,6 +57,10 @@ class MergeRequestPollWidgetEntity < Grape::Entity
presenter
(
merge_request
).
ci_status
presenter
(
merge_request
).
ci_status
end
end
expose
:pipeline_coverage_delta
do
|
merge_request
|
presenter
(
merge_request
).
pipeline_coverage_delta
end
expose
:cancel_auto_merge_path
do
|
merge_request
|
expose
:cancel_auto_merge_path
do
|
merge_request
|
presenter
(
merge_request
).
cancel_auto_merge_path
presenter
(
merge_request
).
cancel_auto_merge_path
end
end
...
...
app/views/projects/merge_requests/_widget.html.haml
View file @
6b8040dc
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
window.gl.mrWidgetData =
#{
serialize_issuable
(
@merge_request
,
serializer:
'widget'
,
issues_links:
true
)
}
window.gl.mrWidgetData =
#{
serialize_issuable
(
@merge_request
,
serializer:
'widget'
,
issues_links:
true
)
}
window.gl.mrWidgetData.squash_before_merge_help_path = '
#{
help_page_path
(
"user/project/merge_requests/squash_and_merge"
)
}
';
window.gl.mrWidgetData.squash_before_merge_help_path = '
#{
help_page_path
(
"user/project/merge_requests/squash_and_merge"
)
}
';
window.gl.mrWidgetData.troubleshooting_docs_path = '
#{
help_page_path
(
'user/project/merge_requests/
index
.md'
,
anchor:
'troubleshooting'
)
}
';
window.gl.mrWidgetData.troubleshooting_docs_path = '
#{
help_page_path
(
'user/project/merge_requests/
reviewing_and_managing_merge_requests
.md'
,
anchor:
'troubleshooting'
)
}
';
window.gl.mrWidgetData.security_approvals_help_page_path = '
#{
help_page_path
(
'user/application_security/index.html'
,
anchor:
'security-approvals-in-merge-requests-ultimate'
)
}
';
window.gl.mrWidgetData.security_approvals_help_page_path = '
#{
help_page_path
(
'user/application_security/index.html'
,
anchor:
'security-approvals-in-merge-requests-ultimate'
)
}
';
window.gl.mrWidgetData.eligible_approvers_docs_path = '
#{
help_page_path
(
'user/project/merge_requests/merge_request_approvals'
,
anchor:
'eligible-approvers'
)
}
';
window.gl.mrWidgetData.eligible_approvers_docs_path = '
#{
help_page_path
(
'user/project/merge_requests/merge_request_approvals'
,
anchor:
'eligible-approvers'
)
}
';
...
...
changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-workers.yml
0 → 100644
View file @
6b8040dc
---
title
:
Add workers for 'soft-delete for groups' feature
merge_request
:
19679
author
:
type
:
added
changelogs/unreleased/34067-add-recent-searches-to-sentry-error-list-in-gitlab.yml
0 → 100644
View file @
6b8040dc
---
title
:
Add recent search to error tracking
merge_request
:
19301
author
:
type
:
added
changelogs/unreleased/34121-group-level-no-data-store.yml
0 → 100644
View file @
6b8040dc
---
title
:
Add empty region when group metrics are missing
merge_request
:
20900
author
:
type
:
fixed
changelogs/unreleased/34261-service-desk-to-api.yml
0 → 100644
View file @
6b8040dc
---
title
:
Add service desk information to projects API endpoint
merge_request
:
20913
author
:
type
:
changed
changelogs/unreleased/feat-merge-request-coverage-delta.yml
0 → 100644
View file @
6b8040dc
---
title
:
Add coverage difference visualization to merge request page
merge_request
:
20676
author
:
Fabio Huser
type
:
added
config/initializers/1_settings.rb
View file @
6b8040dc
...
@@ -469,6 +469,9 @@ Settings.cron_jobs['namespaces_prune_aggregation_schedules_worker']['cron'] ||=
...
@@ -469,6 +469,9 @@ Settings.cron_jobs['namespaces_prune_aggregation_schedules_worker']['cron'] ||=
Settings
.
cron_jobs
[
'namespaces_prune_aggregation_schedules_worker'
][
'job_class'
]
=
'Namespaces::PruneAggregationSchedulesWorker'
Settings
.
cron_jobs
[
'namespaces_prune_aggregation_schedules_worker'
][
'job_class'
]
=
'Namespaces::PruneAggregationSchedulesWorker'
Gitlab
.
ee
do
Gitlab
.
ee
do
Settings
.
cron_jobs
[
'adjourned_group_deletion_worker'
]
||=
Settingslogic
.
new
({})
Settings
.
cron_jobs
[
'adjourned_group_deletion_worker'
][
'cron'
]
||=
'0 3 * * *'
Settings
.
cron_jobs
[
'adjourned_group_deletion_worker'
][
'job_class'
]
=
'AdjournedGroupDeletionWorker'
Settings
.
cron_jobs
[
'clear_shared_runners_minutes_worker'
]
||=
Settingslogic
.
new
({})
Settings
.
cron_jobs
[
'clear_shared_runners_minutes_worker'
]
||=
Settingslogic
.
new
({})
Settings
.
cron_jobs
[
'clear_shared_runners_minutes_worker'
][
'cron'
]
||=
'0 0 1 * *'
Settings
.
cron_jobs
[
'clear_shared_runners_minutes_worker'
][
'cron'
]
||=
'0 0 1 * *'
Settings
.
cron_jobs
[
'clear_shared_runners_minutes_worker'
][
'job_class'
]
=
'ClearSharedRunnersMinutesWorker'
Settings
.
cron_jobs
[
'clear_shared_runners_minutes_worker'
][
'job_class'
]
=
'ClearSharedRunnersMinutesWorker'
...
...
db/migrate/20191203121729_update_group_deletion_schedules_foreign_keys.rb
0 → 100644
View file @
6b8040dc
# frozen_string_literal: true
class
UpdateGroupDeletionSchedulesForeignKeys
<
ActiveRecord
::
Migration
[
5.2
]
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
disable_ddl_transaction!
def
up
add_concurrent_foreign_key
(
:group_deletion_schedules
,
:users
,
column: :user_id
,
on_delete: :cascade
,
name:
new_foreign_key_name
)
remove_foreign_key_if_exists
(
:group_deletion_schedules
,
column: :user_id
,
on_delete: :nullify
)
end
def
down
add_concurrent_foreign_key
(
:group_deletion_schedules
,
:users
,
column: :user_id
,
on_delete: :nullify
,
name:
existing_foreign_key_name
)
remove_foreign_key_if_exists
(
:group_deletion_schedules
,
column: :user_id
,
on_delete: :cascade
)
end
private
def
new_foreign_key_name
concurrent_foreign_key_name
(
:group_deletion_schedules
,
:user_id
)
end
def
existing_foreign_key_name
'fk_group_deletion_schedules_users_user_id'
end
end
db/schema.rb
View file @
6b8040dc
...
@@ -4490,7 +4490,7 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
...
@@ -4490,7 +4490,7 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
add_foreign_key
"grafana_integrations"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"grafana_integrations"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"group_custom_attributes"
,
"namespaces"
,
column:
"group_id"
,
on_delete: :cascade
add_foreign_key
"group_custom_attributes"
,
"namespaces"
,
column:
"group_id"
,
on_delete: :cascade
add_foreign_key
"group_deletion_schedules"
,
"namespaces"
,
column:
"group_id"
,
on_delete: :cascade
add_foreign_key
"group_deletion_schedules"
,
"namespaces"
,
column:
"group_id"
,
on_delete: :cascade
add_foreign_key
"group_deletion_schedules"
,
"users"
,
on_delete: :nullify
add_foreign_key
"group_deletion_schedules"
,
"users"
,
name:
"fk_11e3ebfcdd"
,
on_delete: :cascade
add_foreign_key
"group_group_links"
,
"namespaces"
,
column:
"shared_group_id"
,
on_delete: :cascade
add_foreign_key
"group_group_links"
,
"namespaces"
,
column:
"shared_group_id"
,
on_delete: :cascade
add_foreign_key
"group_group_links"
,
"namespaces"
,
column:
"shared_with_group_id"
,
on_delete: :cascade
add_foreign_key
"group_group_links"
,
"namespaces"
,
column:
"shared_with_group_id"
,
on_delete: :cascade
add_foreign_key
"identities"
,
"saml_providers"
,
name:
"fk_aade90f0fc"
,
on_delete: :cascade
add_foreign_key
"identities"
,
"saml_providers"
,
name:
"fk_aade90f0fc"
,
on_delete: :cascade
...
...
doc/api/projects.md
View file @
6b8040dc
...
@@ -241,6 +241,19 @@ When the user is authenticated and `simple` is not set this returns something li
...
@@ -241,6 +241,19 @@ When the user is authenticated and `simple` is not set this returns something li
"remove_source_branch_after_merge"
:
false
,
"remove_source_branch_after_merge"
:
false
,
"request_access_enabled"
:
false
,
"request_access_enabled"
:
false
,
"merge_method"
:
"merge"
,
"merge_method"
:
"merge"
,
"auto_devops_enabled"
:
true
,
"auto_devops_deploy_strategy"
:
"continuous"
,
"repository_storage"
:
"default"
,
"approvals_before_merge"
:
0
,
"mirror"
:
false
,
"mirror_user_id"
:
45
,
"mirror_trigger_builds"
:
false
,
"only_mirror_protected_branches"
:
false
,
"mirror_overwrites_diverged_branches"
:
false
,
"external_authorization_classification_label"
:
null
,
"packages_enabled"
:
true
,
"service_desk_enabled"
:
false
,
"service_desk_address"
:
null
,
"statistics"
:
{
"statistics"
:
{
"commit_count"
:
12
,
"commit_count"
:
12
,
"storage_size"
:
2066080
,
"storage_size"
:
2066080
,
...
@@ -457,6 +470,19 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
...
@@ -457,6 +470,19 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
"remove_source_branch_after_merge"
:
false
,
"remove_source_branch_after_merge"
:
false
,
"request_access_enabled"
:
false
,
"request_access_enabled"
:
false
,
"merge_method"
:
"merge"
,
"merge_method"
:
"merge"
,
"auto_devops_enabled"
:
true
,
"auto_devops_deploy_strategy"
:
"continuous"
,
"repository_storage"
:
"default"
,
"approvals_before_merge"
:
0
,
"mirror"
:
false
,
"mirror_user_id"
:
45
,
"mirror_trigger_builds"
:
false
,
"only_mirror_protected_branches"
:
false
,
"mirror_overwrites_diverged_branches"
:
false
,
"external_authorization_classification_label"
:
null
,
"packages_enabled"
:
true
,
"service_desk_enabled"
:
false
,
"service_desk_address"
:
null
,
"statistics"
:
{
"statistics"
:
{
"commit_count"
:
12
,
"commit_count"
:
12
,
"storage_size"
:
2066080
,
"storage_size"
:
2066080
,
...
@@ -649,6 +675,19 @@ Example response:
...
@@ -649,6 +675,19 @@ Example response:
"remove_source_branch_after_merge"
:
false
,
"remove_source_branch_after_merge"
:
false
,
"request_access_enabled"
:
false
,
"request_access_enabled"
:
false
,
"merge_method"
:
"merge"
,
"merge_method"
:
"merge"
,
"auto_devops_enabled"
:
true
,
"auto_devops_deploy_strategy"
:
"continuous"
,
"repository_storage"
:
"default"
,
"approvals_before_merge"
:
0
,
"mirror"
:
false
,
"mirror_user_id"
:
45
,
"mirror_trigger_builds"
:
false
,
"only_mirror_protected_branches"
:
false
,
"mirror_overwrites_diverged_branches"
:
false
,
"external_authorization_classification_label"
:
null
,
"packages_enabled"
:
true
,
"service_desk_enabled"
:
false
,
"service_desk_address"
:
null
,
"statistics"
:
{
"statistics"
:
{
"commit_count"
:
12
,
"commit_count"
:
12
,
"storage_size"
:
2066080
,
"storage_size"
:
2066080
,
...
@@ -777,6 +816,19 @@ GET /projects/:id
...
@@ -777,6 +816,19 @@ GET /projects/:id
"printing_merge_requests_link_enabled"
:
true
,
"printing_merge_requests_link_enabled"
:
true
,
"request_access_enabled"
:
false
,
"request_access_enabled"
:
false
,
"merge_method"
:
"merge"
,
"merge_method"
:
"merge"
,
"auto_devops_enabled"
:
true
,
"auto_devops_deploy_strategy"
:
"continuous"
,
"repository_storage"
:
"default"
,
"approvals_before_merge"
:
0
,
"mirror"
:
false
,
"mirror_user_id"
:
45
,
"mirror_trigger_builds"
:
false
,
"only_mirror_protected_branches"
:
false
,
"mirror_overwrites_diverged_branches"
:
false
,
"external_authorization_classification_label"
:
null
,
"packages_enabled"
:
true
,
"service_desk_enabled"
:
false
,
"service_desk_address"
:
null
,
"statistics"
:
{
"statistics"
:
{
"commit_count"
:
37
,
"commit_count"
:
37
,
"storage_size"
:
1038090
,
"storage_size"
:
1038090
,
...
...
locale/gitlab.pot
View file @
6b8040dc
...
@@ -11157,6 +11157,9 @@ msgstr ""
...
@@ -11157,6 +11157,9 @@ msgstr ""
msgid "Metrics|There was an error while retrieving metrics"
msgid "Metrics|There was an error while retrieving metrics"
msgstr ""
msgstr ""
msgid "Metrics|There was an error while retrieving metrics. %{message}"
msgstr ""
msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
msgstr ""
msgstr ""
...
@@ -15346,6 +15349,9 @@ msgstr ""
...
@@ -15346,6 +15349,9 @@ msgstr ""
msgid "Search or filter results..."
msgid "Search or filter results..."
msgstr ""
msgstr ""
msgid "Search or filter results…"
msgstr ""
msgid "Search or jump to…"
msgid "Search or jump to…"
msgstr ""
msgstr ""
...
@@ -17478,6 +17484,9 @@ msgstr ""
...
@@ -17478,6 +17484,9 @@ msgstr ""
msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
msgstr ""
msgstr ""
msgid "The data source is connected, but there is no data to display."
msgstr ""
msgid "The default CI configuration path for new projects."
msgid "The default CI configuration path for new projects."
msgstr ""
msgstr ""
...
...
spec/features/security/project/internal_access_spec.rb
View file @
6b8040dc
...
@@ -89,7 +89,7 @@ describe "Internal Project Access" do
...
@@ -89,7 +89,7 @@ describe "Internal Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/settings/members"
do
describe
"GET /:project_path/
-/
settings/members"
do
subject
{
project_settings_members_path
(
project
)
}
subject
{
project_settings_members_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -103,7 +103,7 @@ describe "Internal Project Access" do
...
@@ -103,7 +103,7 @@ describe "Internal Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
end
end
describe
"GET /:project_path/settings/ci_cd"
do
describe
"GET /:project_path/
-/
settings/ci_cd"
do
subject
{
project_settings_ci_cd_path
(
project
)
}
subject
{
project_settings_ci_cd_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -117,7 +117,7 @@ describe "Internal Project Access" do
...
@@ -117,7 +117,7 @@ describe "Internal Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
end
end
describe
"GET /:project_path/settings/repository"
do
describe
"GET /:project_path/
-/
settings/repository"
do
subject
{
project_settings_repository_path
(
project
)
}
subject
{
project_settings_repository_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -301,7 +301,7 @@ describe "Internal Project Access" do
...
@@ -301,7 +301,7 @@ describe "Internal Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/settings/integrations"
do
describe
"GET /:project_path/
-/
settings/integrations"
do
subject
{
project_settings_integrations_path
(
project
)
}
subject
{
project_settings_integrations_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -470,7 +470,7 @@ describe "Internal Project Access" do
...
@@ -470,7 +470,7 @@ describe "Internal Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments"
do
describe
"GET /:project_path/
-/
environments"
do
subject
{
project_environments_path
(
project
)
}
subject
{
project_environments_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -484,7 +484,7 @@ describe "Internal Project Access" do
...
@@ -484,7 +484,7 @@ describe "Internal Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/:id"
do
describe
"GET /:project_path/
-/
environments/:id"
do
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
subject
{
project_environment_path
(
project
,
environment
)
}
subject
{
project_environment_path
(
project
,
environment
)
}
...
@@ -499,7 +499,7 @@ describe "Internal Project Access" do
...
@@ -499,7 +499,7 @@ describe "Internal Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/:id/deployments"
do
describe
"GET /:project_path/
-/
environments/:id/deployments"
do
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
subject
{
project_environment_deployments_path
(
project
,
environment
)
}
subject
{
project_environment_deployments_path
(
project
,
environment
)
}
...
@@ -514,7 +514,7 @@ describe "Internal Project Access" do
...
@@ -514,7 +514,7 @@ describe "Internal Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/new"
do
describe
"GET /:project_path/
-/
environments/new"
do
subject
{
new_project_environment_path
(
project
)
}
subject
{
new_project_environment_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
...
spec/features/security/project/private_access_spec.rb
View file @
6b8040dc
...
@@ -89,7 +89,7 @@ describe "Private Project Access" do
...
@@ -89,7 +89,7 @@ describe "Private Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/settings/members"
do
describe
"GET /:project_path/
-/
settings/members"
do
subject
{
project_settings_members_path
(
project
)
}
subject
{
project_settings_members_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -103,7 +103,7 @@ describe "Private Project Access" do
...
@@ -103,7 +103,7 @@ describe "Private Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
end
end
describe
"GET /:project_path/settings/ci_cd"
do
describe
"GET /:project_path/
-/
settings/ci_cd"
do
subject
{
project_settings_ci_cd_path
(
project
)
}
subject
{
project_settings_ci_cd_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -117,7 +117,7 @@ describe "Private Project Access" do
...
@@ -117,7 +117,7 @@ describe "Private Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
end
end
describe
"GET /:project_path/settings/repository"
do
describe
"GET /:project_path/
-/
settings/repository"
do
subject
{
project_settings_repository_path
(
project
)
}
subject
{
project_settings_repository_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -273,7 +273,7 @@ describe "Private Project Access" do
...
@@ -273,7 +273,7 @@ describe "Private Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/
namespace/hook
s"
do
describe
"GET /:project_path/
-/settings/integration
s"
do
subject
{
project_settings_integrations_path
(
project
)
}
subject
{
project_settings_integrations_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -431,7 +431,7 @@ describe "Private Project Access" do
...
@@ -431,7 +431,7 @@ describe "Private Project Access" do
end
end
end
end
describe
"GET /:project_path/environments"
do
describe
"GET /:project_path/
-/
environments"
do
subject
{
project_environments_path
(
project
)
}
subject
{
project_environments_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -445,7 +445,7 @@ describe "Private Project Access" do
...
@@ -445,7 +445,7 @@ describe "Private Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/:id"
do
describe
"GET /:project_path/
-/
environments/:id"
do
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
subject
{
project_environment_path
(
project
,
environment
)
}
subject
{
project_environment_path
(
project
,
environment
)
}
...
@@ -460,7 +460,7 @@ describe "Private Project Access" do
...
@@ -460,7 +460,7 @@ describe "Private Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/:id/deployments"
do
describe
"GET /:project_path/
-/
environments/:id/deployments"
do
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
subject
{
project_environment_deployments_path
(
project
,
environment
)
}
subject
{
project_environment_deployments_path
(
project
,
environment
)
}
...
@@ -475,7 +475,7 @@ describe "Private Project Access" do
...
@@ -475,7 +475,7 @@ describe "Private Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/new"
do
describe
"GET /:project_path/
-/
environments/new"
do
subject
{
new_project_environment_path
(
project
)
}
subject
{
new_project_environment_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -517,7 +517,7 @@ describe "Private Project Access" do
...
@@ -517,7 +517,7 @@ describe "Private Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/new"
do
describe
"GET /:project_path/
-/
environments/new"
do
subject
{
new_project_pipeline_schedule_path
(
project
)
}
subject
{
new_project_pipeline_schedule_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
...
spec/features/security/project/public_access_spec.rb
View file @
6b8040dc
...
@@ -89,7 +89,7 @@ describe "Public Project Access" do
...
@@ -89,7 +89,7 @@ describe "Public Project Access" do
it
{
is_expected
.
to
be_allowed_for
(
:visitor
)
}
it
{
is_expected
.
to
be_allowed_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/settings/members"
do
describe
"GET /:project_path/
-/
settings/members"
do
subject
{
project_settings_members_path
(
project
)
}
subject
{
project_settings_members_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -103,7 +103,7 @@ describe "Public Project Access" do
...
@@ -103,7 +103,7 @@ describe "Public Project Access" do
it
{
is_expected
.
to
be_allowed_for
(
:external
)
}
it
{
is_expected
.
to
be_allowed_for
(
:external
)
}
end
end
describe
"GET /:project_path/settings/ci_cd"
do
describe
"GET /:project_path/
-/
settings/ci_cd"
do
subject
{
project_settings_ci_cd_path
(
project
)
}
subject
{
project_settings_ci_cd_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -117,7 +117,7 @@ describe "Public Project Access" do
...
@@ -117,7 +117,7 @@ describe "Public Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
it
{
is_expected
.
to
be_denied_for
(
:external
)
}
end
end
describe
"GET /:project_path/settings/repository"
do
describe
"GET /:project_path/
-/
settings/repository"
do
subject
{
project_settings_repository_path
(
project
)
}
subject
{
project_settings_repository_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -286,7 +286,7 @@ describe "Public Project Access" do
...
@@ -286,7 +286,7 @@ describe "Public Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments"
do
describe
"GET /:project_path/
-/
environments"
do
subject
{
project_environments_path
(
project
)
}
subject
{
project_environments_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -300,7 +300,7 @@ describe "Public Project Access" do
...
@@ -300,7 +300,7 @@ describe "Public Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/:id"
do
describe
"GET /:project_path/
-/
environments/:id"
do
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
subject
{
project_environment_path
(
project
,
environment
)
}
subject
{
project_environment_path
(
project
,
environment
)
}
...
@@ -315,7 +315,7 @@ describe "Public Project Access" do
...
@@ -315,7 +315,7 @@ describe "Public Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/:id/deployments"
do
describe
"GET /:project_path/
-/
environments/:id/deployments"
do
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
let
(
:environment
)
{
create
(
:environment
,
project:
project
)
}
subject
{
project_environment_deployments_path
(
project
,
environment
)
}
subject
{
project_environment_deployments_path
(
project
,
environment
)
}
...
@@ -330,7 +330,7 @@ describe "Public Project Access" do
...
@@ -330,7 +330,7 @@ describe "Public Project Access" do
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
it
{
is_expected
.
to
be_denied_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/environments/new"
do
describe
"GET /:project_path/
-/
environments/new"
do
subject
{
new_project_environment_path
(
project
)
}
subject
{
new_project_environment_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
@@ -514,7 +514,7 @@ describe "Public Project Access" do
...
@@ -514,7 +514,7 @@ describe "Public Project Access" do
it
{
is_expected
.
to
be_allowed_for
(
:visitor
)
}
it
{
is_expected
.
to
be_allowed_for
(
:visitor
)
}
end
end
describe
"GET /:project_path/settings/integrations"
do
describe
"GET /:project_path/
-/
settings/integrations"
do
subject
{
project_settings_integrations_path
(
project
)
}
subject
{
project_settings_integrations_path
(
project
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
it
{
is_expected
.
to
be_allowed_for
(
:admin
)
}
...
...
spec/frontend/error_tracking/components/error_tracking_list_spec.js
View file @
6b8040dc
...
@@ -6,8 +6,11 @@ import {
...
@@ -6,8 +6,11 @@ import {
GlLoadingIcon
,
GlLoadingIcon
,
GlTable
,
GlTable
,
GlLink
,
GlLink
,
GlSearchBoxByClick
,
GlFormInput
,
GlDropdown
,
GlDropdownItem
,
}
from
'
@gitlab/ui
'
;
}
from
'
@gitlab/ui
'
;
import
createListState
from
'
~/error_tracking/store/list/state
'
;
import
ErrorTrackingList
from
'
~/error_tracking/components/error_tracking_list.vue
'
;
import
ErrorTrackingList
from
'
~/error_tracking/components/error_tracking_list.vue
'
;
import
errorsList
from
'
./list_mock.json
'
;
import
errorsList
from
'
./list_mock.json
'
;
...
@@ -51,12 +54,13 @@ describe('ErrorTrackingList', () => {
...
@@ -51,12 +54,13 @@ describe('ErrorTrackingList', () => {
getErrorList
:
()
=>
{},
getErrorList
:
()
=>
{},
startPolling
:
jest
.
fn
(),
startPolling
:
jest
.
fn
(),
restartPolling
:
jest
.
fn
().
mockName
(
'
restartPolling
'
),
restartPolling
:
jest
.
fn
().
mockName
(
'
restartPolling
'
),
addRecentSearch
:
jest
.
fn
(),
loadRecentSearches
:
jest
.
fn
(),
setIndexPath
:
jest
.
fn
(),
clearRecentSearches
:
jest
.
fn
(),
};
};
const
state
=
{
const
state
=
createListState
();
errors
:
errorsList
,
loading
:
true
,
};
store
=
new
Vuex
.
Store
({
store
=
new
Vuex
.
Store
({
modules
:
{
modules
:
{
...
@@ -90,6 +94,7 @@ describe('ErrorTrackingList', () => {
...
@@ -90,6 +94,7 @@ describe('ErrorTrackingList', () => {
describe
(
'
results
'
,
()
=>
{
describe
(
'
results
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
store
.
state
.
list
.
loading
=
false
;
store
.
state
.
list
.
loading
=
false
;
store
.
state
.
list
.
errors
=
errorsList
;
mountComponent
();
mountComponent
();
});
});
...
@@ -114,7 +119,7 @@ describe('ErrorTrackingList', () => {
...
@@ -114,7 +119,7 @@ describe('ErrorTrackingList', () => {
});
});
describe
(
'
filtering
'
,
()
=>
{
describe
(
'
filtering
'
,
()
=>
{
const
findSearchBox
=
()
=>
wrapper
.
find
(
Gl
SearchBoxByClick
);
const
findSearchBox
=
()
=>
wrapper
.
find
(
Gl
FormInput
);
it
(
'
shows search box
'
,
()
=>
{
it
(
'
shows search box
'
,
()
=>
{
expect
(
findSearchBox
().
exists
()).
toBe
(
true
);
expect
(
findSearchBox
().
exists
()).
toBe
(
true
);
...
@@ -122,7 +127,9 @@ describe('ErrorTrackingList', () => {
...
@@ -122,7 +127,9 @@ describe('ErrorTrackingList', () => {
it
(
'
makes network request on submit
'
,
()
=>
{
it
(
'
makes network request on submit
'
,
()
=>
{
expect
(
actions
.
startPolling
).
toHaveBeenCalledTimes
(
1
);
expect
(
actions
.
startPolling
).
toHaveBeenCalledTimes
(
1
);
findSearchBox
().
vm
.
$emit
(
'
submit
'
);
findSearchBox
().
trigger
(
'
keyup.enter
'
);
expect
(
actions
.
startPolling
).
toHaveBeenCalledTimes
(
2
);
expect
(
actions
.
startPolling
).
toHaveBeenCalledTimes
(
2
);
});
});
});
});
...
@@ -185,4 +192,51 @@ describe('ErrorTrackingList', () => {
...
@@ -185,4 +192,51 @@ describe('ErrorTrackingList', () => {
);
);
});
});
});
});
describe
(
'
recent searches
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
();
});
it
(
'
shows empty message
'
,
()
=>
{
store
.
state
.
list
.
recentSearches
=
[];
expect
(
wrapper
.
find
(
GlDropdown
).
text
()).
toBe
(
"
You don't have any recent searches
"
);
});
it
(
'
shows items
'
,
()
=>
{
store
.
state
.
list
.
recentSearches
=
[
'
great
'
,
'
search
'
];
const
dropdownItems
=
wrapper
.
findAll
(
GlDropdownItem
);
expect
(
dropdownItems
.
length
).
toBe
(
3
);
expect
(
dropdownItems
.
at
(
0
).
text
()).
toBe
(
'
great
'
);
expect
(
dropdownItems
.
at
(
1
).
text
()).
toBe
(
'
search
'
);
});
describe
(
'
clear
'
,
()
=>
{
const
clearRecentButton
=
()
=>
wrapper
.
find
({
ref
:
'
clearRecentSearches
'
});
it
(
'
is hidden when list empty
'
,
()
=>
{
store
.
state
.
list
.
recentSearches
=
[];
expect
(
clearRecentButton
().
exists
()).
toBe
(
false
);
});
it
(
'
is visible when list has items
'
,
()
=>
{
store
.
state
.
list
.
recentSearches
=
[
'
some
'
,
'
searches
'
];
expect
(
clearRecentButton
().
exists
()).
toBe
(
true
);
expect
(
clearRecentButton
().
text
()).
toBe
(
'
Clear recent searches
'
);
});
it
(
'
clears items on click
'
,
()
=>
{
store
.
state
.
list
.
recentSearches
=
[
'
some
'
,
'
searches
'
];
clearRecentButton
().
vm
.
$emit
(
'
click
'
);
expect
(
actions
.
clearRecentSearches
).
toHaveBeenCalledTimes
(
1
);
});
});
});
});
});
spec/frontend/error_tracking/store/list/mutation_spec.js
View file @
6b8040dc
import
mutations
from
'
~/error_tracking/store/list/mutations
'
;
import
mutations
from
'
~/error_tracking/store/list/mutations
'
;
import
*
as
types
from
'
~/error_tracking/store/list/mutation_types
'
;
import
*
as
types
from
'
~/error_tracking/store/list/mutation_types
'
;
import
{
useLocalStorageSpy
}
from
'
helpers/local_storage_helper
'
;
const
ADD_RECENT_SEARCH
=
mutations
[
types
.
ADD_RECENT_SEARCH
];
const
CLEAR_RECENT_SEARCHES
=
mutations
[
types
.
CLEAR_RECENT_SEARCHES
];
const
LOAD_RECENT_SEARCHES
=
mutations
[
types
.
LOAD_RECENT_SEARCHES
];
describe
(
'
Error tracking mutations
'
,
()
=>
{
describe
(
'
Error tracking mutations
'
,
()
=>
{
describe
(
'
SET_ERRORS
'
,
()
=>
{
describe
(
'
SET_ERRORS
'
,
()
=>
{
...
@@ -33,4 +38,81 @@ describe('Error tracking mutations', () => {
...
@@ -33,4 +38,81 @@ describe('Error tracking mutations', () => {
});
});
});
});
});
});
describe
(
'
recent searches
'
,
()
=>
{
useLocalStorageSpy
();
let
state
;
beforeEach
(()
=>
{
state
=
{
indexPath
:
'
/project/errors.json
'
,
recentSearches
:
[],
};
});
describe
(
'
ADD_RECENT_SEARCH
'
,
()
=>
{
it
(
'
adds search queries to recentSearches and localStorage
'
,
()
=>
{
ADD_RECENT_SEARCH
(
state
,
'
my issue
'
);
expect
(
state
.
recentSearches
).
toEqual
([
'
my issue
'
]);
expect
(
localStorage
.
setItem
).
toHaveBeenCalledWith
(
'
recent-searches/project/errors.json
'
,
'
["my issue"]
'
,
);
});
it
(
'
does not add empty searches
'
,
()
=>
{
ADD_RECENT_SEARCH
(
state
,
''
);
expect
(
state
.
recentSearches
).
toEqual
([]);
expect
(
localStorage
.
setItem
).
not
.
toHaveBeenCalled
();
});
it
(
'
adds new queries to start of the list
'
,
()
=>
{
state
.
recentSearches
=
[
'
previous
'
,
'
searches
'
];
ADD_RECENT_SEARCH
(
state
,
'
new search
'
);
expect
(
state
.
recentSearches
).
toEqual
([
'
new search
'
,
'
previous
'
,
'
searches
'
]);
});
it
(
'
limits recentSearches to 5 items
'
,
()
=>
{
state
.
recentSearches
=
[
1
,
2
,
3
,
4
,
5
];
ADD_RECENT_SEARCH
(
state
,
'
new search
'
);
expect
(
state
.
recentSearches
).
toEqual
([
'
new search
'
,
1
,
2
,
3
,
4
]);
});
it
(
'
does not add same search query twice
'
,
()
=>
{
state
.
recentSearches
=
[
'
already
'
,
'
searched
'
];
ADD_RECENT_SEARCH
(
state
,
'
searched
'
);
expect
(
state
.
recentSearches
).
toEqual
([
'
searched
'
,
'
already
'
]);
});
});
describe
(
'
CLEAR_RECENT_SEARCHES
'
,
()
=>
{
it
(
'
clears recentSearches and localStorage
'
,
()
=>
{
state
.
recentSearches
=
[
'
first
'
,
'
second
'
];
CLEAR_RECENT_SEARCHES
(
state
);
expect
(
state
.
recentSearches
).
toEqual
([]);
expect
(
localStorage
.
removeItem
).
toHaveBeenCalledWith
(
'
recent-searches/project/errors.json
'
);
});
});
describe
(
'
LOAD_RECENT_SEARCHES
'
,
()
=>
{
it
(
'
loads recent searches from localStorage
'
,
()
=>
{
jest
.
spyOn
(
window
.
localStorage
,
'
getItem
'
).
mockReturnValue
(
'
["first", "second"]
'
);
LOAD_RECENT_SEARCHES
(
state
);
expect
(
state
.
recentSearches
).
toEqual
([
'
first
'
,
'
second
'
]);
expect
(
localStorage
.
getItem
).
toHaveBeenCalledWith
(
'
recent-searches/project/errors.json
'
);
});
});
});
});
});
spec/frontend/monitoring/charts/time_series_spec.js
View file @
6b8040dc
...
@@ -45,10 +45,11 @@ describe('Time series component', () => {
...
@@ -45,10 +45,11 @@ describe('Time series component', () => {
store
.
commit
(
`monitoringDashboard/
${
types
.
RECEIVE_DEPLOYMENTS_DATA_SUCCESS
}
`
,
deploymentData
);
store
.
commit
(
`monitoringDashboard/
${
types
.
RECEIVE_DEPLOYMENTS_DATA_SUCCESS
}
`
,
deploymentData
);
// Mock data contains 2 panel
s, pick the first one
// Mock data contains 2 panel
groups, with 1 and 2 panels respectively
store
.
commit
(
`monitoringDashboard/
${
types
.
SET_QUERY_RESULT
}
`
,
mockedQueryResultPayload
);
store
.
commit
(
`monitoringDashboard/
${
types
.
SET_QUERY_RESULT
}
`
,
mockedQueryResultPayload
);
[
mockGraphData
]
=
store
.
state
.
monitoringDashboard
.
dashboard
.
panel_groups
[
0
].
panels
;
// Pick the second panel group and the first panel in it
[
mockGraphData
]
=
store
.
state
.
monitoringDashboard
.
dashboard
.
panel_groups
[
1
].
panels
;
makeTimeSeriesChart
=
(
graphData
,
type
)
=>
makeTimeSeriesChart
=
(
graphData
,
type
)
=>
shallowMount
(
TimeSeries
,
{
shallowMount
(
TimeSeries
,
{
...
...
spec/frontend/monitoring/dashboard_state_spec.js
View file @
6b8040dc
...
@@ -11,6 +11,7 @@ function createComponent(props) {
...
@@ -11,6 +11,7 @@ function createComponent(props) {
emptyGettingStartedSvgPath
:
'
/path/to/getting-started.svg
'
,
emptyGettingStartedSvgPath
:
'
/path/to/getting-started.svg
'
,
emptyLoadingSvgPath
:
'
/path/to/loading.svg
'
,
emptyLoadingSvgPath
:
'
/path/to/loading.svg
'
,
emptyNoDataSvgPath
:
'
/path/to/no-data.svg
'
,
emptyNoDataSvgPath
:
'
/path/to/no-data.svg
'
,
emptyNoDataSmallSvgPath
:
'
/path/to/no-data-small.svg
'
,
emptyUnableToConnectSvgPath
:
'
/path/to/unable-to-connect.svg
'
,
emptyUnableToConnectSvgPath
:
'
/path/to/unable-to-connect.svg
'
,
},
},
});
});
...
...
spec/frontend/monitoring/embed/embed_spec.js
View file @
6b8040dc
...
@@ -12,6 +12,7 @@ describe('Embed', () => {
...
@@ -12,6 +12,7 @@ describe('Embed', () => {
let
wrapper
;
let
wrapper
;
let
store
;
let
store
;
let
actions
;
let
actions
;
let
metricsWithDataGetter
;
function
mountComponent
()
{
function
mountComponent
()
{
wrapper
=
shallowMount
(
Embed
,
{
wrapper
=
shallowMount
(
Embed
,
{
...
@@ -31,11 +32,16 @@ describe('Embed', () => {
...
@@ -31,11 +32,16 @@ describe('Embed', () => {
fetchMetricsData
:
()
=>
{},
fetchMetricsData
:
()
=>
{},
};
};
metricsWithDataGetter
=
jest
.
fn
();
store
=
new
Vuex
.
Store
({
store
=
new
Vuex
.
Store
({
modules
:
{
modules
:
{
monitoringDashboard
:
{
monitoringDashboard
:
{
namespaced
:
true
,
namespaced
:
true
,
actions
,
actions
,
getters
:
{
metricsWithData
:
()
=>
metricsWithDataGetter
,
},
state
:
initialState
,
state
:
initialState
,
},
},
},
},
...
@@ -43,6 +49,7 @@ describe('Embed', () => {
...
@@ -43,6 +49,7 @@ describe('Embed', () => {
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
metricsWithDataGetter
.
mockClear
();
if
(
wrapper
)
{
if
(
wrapper
)
{
wrapper
.
destroy
();
wrapper
.
destroy
();
}
}
...
@@ -63,13 +70,13 @@ describe('Embed', () => {
...
@@ -63,13 +70,13 @@ describe('Embed', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
store
.
state
.
monitoringDashboard
.
dashboard
.
panel_groups
=
groups
;
store
.
state
.
monitoringDashboard
.
dashboard
.
panel_groups
=
groups
;
store
.
state
.
monitoringDashboard
.
dashboard
.
panel_groups
[
0
].
panels
=
metricsData
;
store
.
state
.
monitoringDashboard
.
dashboard
.
panel_groups
[
0
].
panels
=
metricsData
;
store
.
state
.
monitoringDashboard
.
metricsWithData
=
metricsWithData
;
metricsWithDataGetter
.
mockReturnValue
(
metricsWithData
);
mountComponent
();
mountComponent
();
});
});
it
(
'
shows a chart when metrics are present
'
,
()
=>
{
it
(
'
shows a chart when metrics are present
'
,
()
=>
{
wrapper
.
setProps
({});
expect
(
wrapper
.
find
(
'
.metrics-embed
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
.metrics-embed
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
PanelType
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
PanelType
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
findAll
(
PanelType
).
length
).
toBe
(
2
);
expect
(
wrapper
.
findAll
(
PanelType
).
length
).
toBe
(
2
);
...
...
spec/frontend/monitoring/embed/mock_data.js
View file @
6b8040dc
...
@@ -75,11 +75,9 @@ export const metricsData = [
...
@@ -75,11 +75,9 @@ export const metricsData = [
},
},
];
];
export
const
initialState
=
{
export
const
initialState
=
()
=>
({
monitoringDashboard
:
{},
dashboard
:
{
dashboard
:
{
panel_groups
:
[],
panel_groups
:
[],
},
},
metricsWithData
:
[],
useDashboardEndpoint
:
true
,
useDashboardEndpoint
:
true
,
};
}
)
;
spec/frontend/monitoring/mock_data.js
View file @
6b8040dc
...
@@ -240,6 +240,11 @@ export const metricsNewGroupsAPIResponse = [
...
@@ -240,6 +240,11 @@ export const metricsNewGroupsAPIResponse = [
},
},
];
];
export
const
mockedEmptyResult
=
{
metricId
:
'
1_response_metrics_nginx_ingress_throughput_status_code
'
,
result
:
[],
};
export
const
mockedQueryResultPayload
=
{
export
const
mockedQueryResultPayload
=
{
metricId
:
'
17_system_metrics_kubernetes_container_memory_average
'
,
metricId
:
'
17_system_metrics_kubernetes_container_memory_average
'
,
result
:
[
result
:
[
...
@@ -327,6 +332,30 @@ export const mockedQueryResultPayloadCoresTotal = {
...
@@ -327,6 +332,30 @@ export const mockedQueryResultPayloadCoresTotal = {
};
};
export
const
metricsGroupsAPIResponse
=
[
export
const
metricsGroupsAPIResponse
=
[
{
group
:
'
Response metrics (NGINX Ingress VTS)
'
,
priority
:
10
,
panels
:
[
{
metrics
:
[
{
id
:
'
response_metrics_nginx_ingress_throughput_status_code
'
,
label
:
'
Status Code
'
,
metric_id
:
1
,
prometheus_endpoint_path
:
'
/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29
'
,
query_range
:
'
sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)
'
,
unit
:
'
req / sec
'
,
},
],
title
:
'
Throughput
'
,
type
:
'
area-chart
'
,
weight
:
1
,
y_label
:
'
Requests / Sec
'
,
},
],
},
{
{
group
:
'
System metrics (Kubernetes)
'
,
group
:
'
System metrics (Kubernetes)
'
,
priority
:
5
,
priority
:
5
,
...
...
spec/frontend/monitoring/store/actions_spec.js
View file @
6b8040dc
...
@@ -191,12 +191,11 @@ describe('Monitoring store actions', () => {
...
@@ -191,12 +191,11 @@ describe('Monitoring store actions', () => {
let
state
;
let
state
;
const
response
=
metricsDashboardResponse
;
const
response
=
metricsDashboardResponse
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
Tracking
,
'
event
'
);
dispatch
=
jest
.
fn
();
dispatch
=
jest
.
fn
();
state
=
storeState
();
state
=
storeState
();
state
.
dashboardEndpoint
=
'
/dashboard
'
;
state
.
dashboardEndpoint
=
'
/dashboard
'
;
});
});
it
(
'
dispatches receive and success actions
'
,
done
=>
{
it
(
'
on success,
dispatches receive and success actions
'
,
done
=>
{
const
params
=
{};
const
params
=
{};
document
.
body
.
dataset
.
page
=
'
projects:environments:metrics
'
;
document
.
body
.
dataset
.
page
=
'
projects:environments:metrics
'
;
mock
.
onGet
(
state
.
dashboardEndpoint
).
reply
(
200
,
response
);
mock
.
onGet
(
state
.
dashboardEndpoint
).
reply
(
200
,
response
);
...
@@ -213,39 +212,65 @@ describe('Monitoring store actions', () => {
...
@@ -213,39 +212,65 @@ describe('Monitoring store actions', () => {
response
,
response
,
params
,
params
,
});
});
})
.
then
(()
=>
{
expect
(
Tracking
.
event
).
toHaveBeenCalledWith
(
document
.
body
.
dataset
.
page
,
'
dashboard_fetch
'
,
{
label
:
'
custom_metrics_dashboard
'
,
property
:
'
count
'
,
value
:
0
,
},
);
done
();
done
();
})
})
.
catch
(
done
.
fail
);
.
catch
(
done
.
fail
);
});
});
it
(
'
dispatches failure action
'
,
done
=>
{
const
params
=
{};
describe
(
'
on failure
'
,
()
=>
{
mock
.
onGet
(
state
.
dashboardEndpoint
).
reply
(
500
);
let
result
;
fetchDashboard
(
let
errorResponse
;
{
beforeEach
(()
=>
{
state
,
const
params
=
{};
dispatch
,
result
=
()
=>
{
},
mock
.
onGet
(
state
.
dashboardEndpoint
).
replyOnce
(
500
,
errorResponse
);
params
,
return
fetchDashboard
({
state
,
dispatch
},
params
);
)
};
.
then
(()
=>
{
});
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
receiveMetricsDashboardFailure
'
,
it
(
'
dispatches a failure action
'
,
done
=>
{
new
Error
(
'
Request failed with status code 500
'
),
errorResponse
=
{};
);
result
()
done
();
.
then
(()
=>
{
})
expect
(
dispatch
).
toHaveBeenCalledWith
(
.
catch
(
done
.
fail
);
'
receiveMetricsDashboardFailure
'
,
new
Error
(
'
Request failed with status code 500
'
),
);
expect
(
createFlash
).
toHaveBeenCalled
();
done
();
})
.
catch
(
done
.
fail
);
});
it
(
'
dispatches a failure action when a message is returned
'
,
done
=>
{
const
message
=
'
Something went wrong with Prometheus!
'
;
errorResponse
=
{
message
};
result
()
.
then
(()
=>
{
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
receiveMetricsDashboardFailure
'
,
new
Error
(
'
Request failed with status code 500
'
),
);
expect
(
createFlash
).
toHaveBeenCalledWith
(
expect
.
stringContaining
(
message
));
done
();
})
.
catch
(
done
.
fail
);
});
it
(
'
does not show a flash error when showErrorBanner is disabled
'
,
done
=>
{
state
.
showErrorBanner
=
false
;
result
()
.
then
(()
=>
{
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
receiveMetricsDashboardFailure
'
,
new
Error
(
'
Request failed with status code 500
'
),
);
expect
(
createFlash
).
not
.
toHaveBeenCalled
();
done
();
})
.
catch
(
done
.
fail
);
});
});
});
});
});
describe
(
'
receiveMetricsDashboardSuccess
'
,
()
=>
{
describe
(
'
receiveMetricsDashboardSuccess
'
,
()
=>
{
...
@@ -317,18 +342,33 @@ describe('Monitoring store actions', () => {
...
@@ -317,18 +342,33 @@ describe('Monitoring store actions', () => {
});
});
});
});
describe
(
'
fetchPrometheusMetrics
'
,
()
=>
{
describe
(
'
fetchPrometheusMetrics
'
,
()
=>
{
const
params
=
{};
let
commit
;
let
commit
;
let
dispatch
;
let
dispatch
;
let
state
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
Tracking
,
'
event
'
);
commit
=
jest
.
fn
();
commit
=
jest
.
fn
();
dispatch
=
jest
.
fn
();
dispatch
=
jest
.
fn
();
state
=
storeState
();
});
});
it
(
'
commits empty state when state.groups is empty
'
,
done
=>
{
it
(
'
commits empty state when state.groups is empty
'
,
done
=>
{
const
state
=
storeState
();
const
getters
=
{
const
params
=
{};
metricsWithData
:
()
=>
[],
fetchPrometheusMetrics
({
state
,
commit
,
dispatch
},
params
)
};
fetchPrometheusMetrics
({
state
,
commit
,
dispatch
,
getters
},
params
)
.
then
(()
=>
{
.
then
(()
=>
{
expect
(
commit
).
toHaveBeenCalledWith
(
types
.
SET_NO_DATA_EMPTY_STATE
);
expect
(
Tracking
.
event
).
toHaveBeenCalledWith
(
document
.
body
.
dataset
.
page
,
'
dashboard_fetch
'
,
{
label
:
'
custom_metrics_dashboard
'
,
property
:
'
count
'
,
value
:
0
,
},
);
expect
(
dispatch
).
not
.
toHaveBeenCalled
();
expect
(
dispatch
).
not
.
toHaveBeenCalled
();
expect
(
createFlash
).
not
.
toHaveBeenCalled
();
expect
(
createFlash
).
not
.
toHaveBeenCalled
();
done
();
done
();
...
@@ -336,19 +376,28 @@ describe('Monitoring store actions', () => {
...
@@ -336,19 +376,28 @@ describe('Monitoring store actions', () => {
.
catch
(
done
.
fail
);
.
catch
(
done
.
fail
);
});
});
it
(
'
dispatches fetchPrometheusMetric for each panel query
'
,
done
=>
{
it
(
'
dispatches fetchPrometheusMetric for each panel query
'
,
done
=>
{
const
params
=
{};
const
state
=
storeState
();
state
.
dashboard
.
panel_groups
=
metricsDashboardResponse
.
dashboard
.
panel_groups
;
state
.
dashboard
.
panel_groups
=
metricsDashboardResponse
.
dashboard
.
panel_groups
;
const
metric
=
state
.
dashboard
.
panel_groups
[
0
].
panels
[
0
].
metrics
[
0
];
const
[
metric
]
=
state
.
dashboard
.
panel_groups
[
0
].
panels
[
0
].
metrics
;
fetchPrometheusMetrics
({
state
,
commit
,
dispatch
},
params
)
const
getters
=
{
metricsWithData
:
()
=>
[
metric
.
id
],
};
fetchPrometheusMetrics
({
state
,
commit
,
dispatch
,
getters
},
params
)
.
then
(()
=>
{
.
then
(()
=>
{
expect
(
dispatch
).
toHaveBeenCalledTimes
(
3
);
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
fetchPrometheusMetric
'
,
{
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
fetchPrometheusMetric
'
,
{
metric
,
metric
,
params
,
params
,
});
});
expect
(
createFlash
).
not
.
toHaveBeenCalled
();
expect
(
Tracking
.
event
).
toHaveBeenCalledWith
(
document
.
body
.
dataset
.
page
,
'
dashboard_fetch
'
,
{
label
:
'
custom_metrics_dashboard
'
,
property
:
'
count
'
,
value
:
1
,
},
);
done
();
done
();
})
})
...
@@ -357,8 +406,6 @@ describe('Monitoring store actions', () => {
...
@@ -357,8 +406,6 @@ describe('Monitoring store actions', () => {
});
});
it
(
'
dispatches fetchPrometheusMetric for each panel query, handles an error
'
,
done
=>
{
it
(
'
dispatches fetchPrometheusMetric for each panel query, handles an error
'
,
done
=>
{
const
params
=
{};
const
state
=
storeState
();
state
.
dashboard
.
panel_groups
=
metricsDashboardResponse
.
dashboard
.
panel_groups
;
state
.
dashboard
.
panel_groups
=
metricsDashboardResponse
.
dashboard
.
panel_groups
;
const
metric
=
state
.
dashboard
.
panel_groups
[
0
].
panels
[
0
].
metrics
[
0
];
const
metric
=
state
.
dashboard
.
panel_groups
[
0
].
panels
[
0
].
metrics
[
0
];
...
...
spec/frontend/monitoring/store/getters_spec.js
0 → 100644
View file @
6b8040dc
import
*
as
getters
from
'
~/monitoring/stores/getters
'
;
import
mutations
from
'
~/monitoring/stores/mutations
'
;
import
*
as
types
from
'
~/monitoring/stores/mutation_types
'
;
import
{
metricsGroupsAPIResponse
,
mockedEmptyResult
,
mockedQueryResultPayload
,
mockedQueryResultPayloadCoresTotal
,
}
from
'
../mock_data
'
;
describe
(
'
Monitoring store Getters
'
,
()
=>
{
describe
(
'
metricsWithData
'
,
()
=>
{
let
metricsWithData
;
let
setupState
;
let
state
;
beforeEach
(()
=>
{
setupState
=
(
initState
=
{})
=>
{
state
=
initState
;
metricsWithData
=
getters
.
metricsWithData
(
state
);
};
});
afterEach
(()
=>
{
state
=
null
;
});
it
(
'
has method-style access
'
,
()
=>
{
setupState
();
expect
(
metricsWithData
).
toEqual
(
expect
.
any
(
Function
));
});
it
(
'
when dashboard has no panel groups, returns empty
'
,
()
=>
{
setupState
({
dashboard
:
{
panel_groups
:
[],
},
});
expect
(
metricsWithData
()).
toEqual
([]);
});
describe
(
'
when the dashboard is set
'
,
()
=>
{
beforeEach
(()
=>
{
setupState
({
dashboard
:
{
panel_groups
:
[]
},
});
});
it
(
'
no loaded metric returns empty
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
state
,
metricsGroupsAPIResponse
);
expect
(
metricsWithData
()).
toEqual
([]);
});
it
(
'
an empty metric, returns empty
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
state
,
metricsGroupsAPIResponse
);
mutations
[
types
.
SET_QUERY_RESULT
](
state
,
mockedEmptyResult
);
expect
(
metricsWithData
()).
toEqual
([]);
});
it
(
'
a metric with results, it returns a metric
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
state
,
metricsGroupsAPIResponse
);
mutations
[
types
.
SET_QUERY_RESULT
](
state
,
mockedQueryResultPayload
);
expect
(
metricsWithData
()).
toEqual
([
mockedQueryResultPayload
.
metricId
]);
});
it
(
'
multiple metrics with results, it return multiple metrics
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
state
,
metricsGroupsAPIResponse
);
mutations
[
types
.
SET_QUERY_RESULT
](
state
,
mockedQueryResultPayload
);
mutations
[
types
.
SET_QUERY_RESULT
](
state
,
mockedQueryResultPayloadCoresTotal
);
expect
(
metricsWithData
()).
toEqual
([
mockedQueryResultPayload
.
metricId
,
mockedQueryResultPayloadCoresTotal
.
metricId
,
]);
});
it
(
'
multiple metrics with results, it returns metrics filtered by group
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
state
,
metricsGroupsAPIResponse
);
mutations
[
types
.
SET_QUERY_RESULT
](
state
,
mockedQueryResultPayload
);
mutations
[
types
.
SET_QUERY_RESULT
](
state
,
mockedQueryResultPayloadCoresTotal
);
// First group has no metrics
expect
(
metricsWithData
(
state
.
dashboard
.
panel_groups
[
0
].
key
)).
toEqual
([]);
// Second group has metrics
expect
(
metricsWithData
(
state
.
dashboard
.
panel_groups
[
1
].
key
)).
toEqual
([
mockedQueryResultPayload
.
metricId
,
mockedQueryResultPayloadCoresTotal
.
metricId
,
]);
});
});
});
});
spec/frontend/monitoring/store/mutations_spec.js
View file @
6b8040dc
...
@@ -7,41 +7,59 @@ import {
...
@@ -7,41 +7,59 @@ import {
metricsDashboardResponse
,
metricsDashboardResponse
,
dashboardGitResponse
,
dashboardGitResponse
,
}
from
'
../mock_data
'
;
}
from
'
../mock_data
'
;
import
{
uniqMetricsId
}
from
'
~/monitoring/stores/utils
'
;
describe
(
'
Monitoring mutations
'
,
()
=>
{
describe
(
'
Monitoring mutations
'
,
()
=>
{
let
stateCopy
;
let
stateCopy
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
stateCopy
=
state
();
stateCopy
=
state
();
});
});
describe
(
'
RECEIVE_METRICS_DATA_SUCCESS
'
,
()
=>
{
describe
(
'
RECEIVE_METRICS_DATA_SUCCESS
'
,
()
=>
{
let
groups
;
let
payload
;
const
getGroups
=
()
=>
stateCopy
.
dashboard
.
panel_groups
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
stateCopy
.
dashboard
.
panel_groups
=
[];
stateCopy
.
dashboard
.
panel_groups
=
[];
groups
=
metricsGroupsAPIResponse
;
payload
=
metricsGroupsAPIResponse
;
});
});
it
(
'
adds a key to the group
'
,
()
=>
{
it
(
'
adds a key to the group
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
groups
);
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
payload
);
expect
(
stateCopy
.
dashboard
.
panel_groups
[
0
].
key
).
toBe
(
'
system-metrics-kubernetes--0
'
);
const
groups
=
getGroups
();
expect
(
groups
[
0
].
key
).
toBe
(
'
response-metrics-nginx-ingress-vts--0
'
);
expect
(
groups
[
1
].
key
).
toBe
(
'
system-metrics-kubernetes--1
'
);
});
});
it
(
'
normalizes values
'
,
()
=>
{
it
(
'
normalizes values
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
groups
);
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
payload
);
const
expectedLabel
=
'
Pod average
'
;
const
expectedLabel
=
'
Pod average
'
;
const
{
label
,
query_range
}
=
stateCopy
.
dashboard
.
panel_groups
[
0
].
panels
[
0
].
metrics
[
0
];
const
{
label
,
query_range
}
=
getGroups
()[
1
].
panels
[
0
].
metrics
[
0
];
expect
(
label
).
toEqual
(
expectedLabel
);
expect
(
label
).
toEqual
(
expectedLabel
);
expect
(
query_range
.
length
).
toBeGreaterThan
(
0
);
expect
(
query_range
.
length
).
toBeGreaterThan
(
0
);
});
});
it
(
'
contains one group, which it has two panels and one metrics property
'
,
()
=>
{
it
(
'
contains two groups, with panels with a metric each
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
groups
);
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
payload
);
expect
(
stateCopy
.
dashboard
.
panel_groups
).
toBeDefined
();
expect
(
stateCopy
.
dashboard
.
panel_groups
.
length
).
toEqual
(
1
);
const
groups
=
getGroups
();
expect
(
stateCopy
.
dashboard
.
panel_groups
[
0
].
panels
.
length
).
toEqual
(
2
);
expect
(
stateCopy
.
dashboard
.
panel_groups
[
0
].
panels
[
0
].
metrics
.
length
).
toEqual
(
1
);
expect
(
groups
).
toBeDefined
();
expect
(
stateCopy
.
dashboard
.
panel_groups
[
0
].
panels
[
1
].
metrics
.
length
).
toEqual
(
1
);
expect
(
groups
).
toHaveLength
(
2
);
expect
(
groups
[
0
].
panels
).
toHaveLength
(
1
);
expect
(
groups
[
0
].
panels
[
0
].
metrics
).
toHaveLength
(
1
);
expect
(
groups
[
1
].
panels
).
toHaveLength
(
2
);
expect
(
groups
[
1
].
panels
[
0
].
metrics
).
toHaveLength
(
1
);
expect
(
groups
[
1
].
panels
[
1
].
metrics
).
toHaveLength
(
1
);
});
});
it
(
'
assigns metrics a metric id
'
,
()
=>
{
it
(
'
assigns metrics a metric id
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
groups
);
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
payload
);
expect
(
stateCopy
.
dashboard
.
panel_groups
[
0
].
panels
[
0
].
metrics
[
0
].
metricId
).
toEqual
(
const
groups
=
getGroups
();
expect
(
groups
[
0
].
panels
[
0
].
metrics
[
0
].
metricId
).
toEqual
(
'
1_response_metrics_nginx_ingress_throughput_status_code
'
,
);
expect
(
groups
[
1
].
panels
[
0
].
metrics
[
0
].
metricId
).
toEqual
(
'
17_system_metrics_kubernetes_container_memory_average
'
,
'
17_system_metrics_kubernetes_container_memory_average
'
,
);
);
});
});
...
@@ -52,7 +70,7 @@ describe('Monitoring mutations', () => {
...
@@ -52,7 +70,7 @@ describe('Monitoring mutations', () => {
stateCopy
.
deploymentData
=
[];
stateCopy
.
deploymentData
=
[];
mutations
[
types
.
RECEIVE_DEPLOYMENTS_DATA_SUCCESS
](
stateCopy
,
deploymentData
);
mutations
[
types
.
RECEIVE_DEPLOYMENTS_DATA_SUCCESS
](
stateCopy
,
deploymentData
);
expect
(
stateCopy
.
deploymentData
).
toBeDefined
();
expect
(
stateCopy
.
deploymentData
).
toBeDefined
();
expect
(
stateCopy
.
deploymentData
.
length
).
toEqual
(
3
);
expect
(
stateCopy
.
deploymentData
).
toHaveLength
(
3
);
expect
(
typeof
stateCopy
.
deploymentData
[
0
]).
toEqual
(
'
object
'
);
expect
(
typeof
stateCopy
.
deploymentData
[
0
]).
toEqual
(
'
object
'
);
});
});
});
});
...
@@ -73,41 +91,38 @@ describe('Monitoring mutations', () => {
...
@@ -73,41 +91,38 @@ describe('Monitoring mutations', () => {
});
});
});
});
describe
(
'
SET_QUERY_RESULT
'
,
()
=>
{
describe
(
'
SET_QUERY_RESULT
'
,
()
=>
{
const
metricId
=
12
;
const
metricId
=
'
12_system_metrics_kubernetes_container_memory_total
'
;
const
id
=
'
system_metrics_kubernetes_container_memory_total
'
;
const
result
=
[
const
result
=
[
{
{
values
:
[[
0
,
1
],
[
1
,
1
],
[
1
,
3
]],
values
:
[[
0
,
1
],
[
1
,
1
],
[
1
,
3
]],
},
},
];
];
const
dashboardGroups
=
metricsDashboardResponse
.
dashboard
.
panel_groups
;
const
getMetrics
=
()
=>
stateCopy
.
dashboard
.
panel_groups
[
0
].
panels
[
0
].
metrics
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
const
dashboardGroups
=
metricsDashboardResponse
.
dashboard
.
panel_groups
;
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
dashboardGroups
);
mutations
[
types
.
RECEIVE_METRICS_DATA_SUCCESS
](
stateCopy
,
dashboardGroups
);
});
});
it
(
'
clears empty state
'
,
()
=>
{
it
(
'
clears empty state
'
,
()
=>
{
expect
(
stateCopy
.
showEmptyState
).
toBe
(
true
);
mutations
[
types
.
SET_QUERY_RESULT
](
stateCopy
,
{
mutations
[
types
.
SET_QUERY_RESULT
](
stateCopy
,
{
metricId
,
metricId
,
result
,
result
,
});
});
expect
(
stateCopy
.
showEmptyState
).
toBe
(
false
);
expect
(
stateCopy
.
showEmptyState
).
toBe
(
false
);
});
});
it
(
'
sets metricsWithData value
'
,
()
=>
{
const
uniqId
=
uniqMetricsId
({
it
(
'
adds results to the store
'
,
()
=>
{
metric_id
:
metricId
,
expect
(
getMetrics
()[
0
].
result
).
toBe
(
undefined
);
id
,
});
mutations
[
types
.
SET_QUERY_RESULT
](
stateCopy
,
{
metricId
:
uniqId
,
result
,
});
expect
(
stateCopy
.
metricsWithData
).
toEqual
([
uniqId
]);
});
it
(
'
does not store empty results
'
,
()
=>
{
mutations
[
types
.
SET_QUERY_RESULT
](
stateCopy
,
{
mutations
[
types
.
SET_QUERY_RESULT
](
stateCopy
,
{
metricId
,
metricId
,
result
:
[]
,
result
,
});
});
expect
(
stateCopy
.
metricsWithData
).
toEqual
([]);
expect
(
getMetrics
()[
0
].
result
).
toHaveLength
(
result
.
length
);
});
});
});
});
describe
(
'
SET_ALL_DASHBOARDS
'
,
()
=>
{
describe
(
'
SET_ALL_DASHBOARDS
'
,
()
=>
{
...
...
spec/javascripts/monitoring/components/dashboard_spec.js
View file @
6b8040dc
...
@@ -4,11 +4,13 @@ import { GlToast } from '@gitlab/ui';
...
@@ -4,11 +4,13 @@ import { GlToast } from '@gitlab/ui';
import
VueDraggable
from
'
vuedraggable
'
;
import
VueDraggable
from
'
vuedraggable
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
Dashboard
from
'
~/monitoring/components/dashboard.vue
'
;
import
Dashboard
from
'
~/monitoring/components/dashboard.vue
'
;
import
EmptyState
from
'
~/monitoring/components/empty_state.vue
'
;
import
*
as
types
from
'
~/monitoring/stores/mutation_types
'
;
import
*
as
types
from
'
~/monitoring/stores/mutation_types
'
;
import
{
createStore
}
from
'
~/monitoring/stores
'
;
import
{
createStore
}
from
'
~/monitoring/stores
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
import
{
metricsGroupsAPIResponse
,
metricsGroupsAPIResponse
,
mockedEmptyResult
,
mockedQueryResultPayload
,
mockedQueryResultPayload
,
mockedQueryResultPayloadCoresTotal
,
mockedQueryResultPayloadCoresTotal
,
mockApiEndpoint
,
mockApiEndpoint
,
...
@@ -29,6 +31,7 @@ const propsData = {
...
@@ -29,6 +31,7 @@ const propsData = {
emptyGettingStartedSvgPath
:
'
/path/to/getting-started.svg
'
,
emptyGettingStartedSvgPath
:
'
/path/to/getting-started.svg
'
,
emptyLoadingSvgPath
:
'
/path/to/loading.svg
'
,
emptyLoadingSvgPath
:
'
/path/to/loading.svg
'
,
emptyNoDataSvgPath
:
'
/path/to/no-data.svg
'
,
emptyNoDataSvgPath
:
'
/path/to/no-data.svg
'
,
emptyNoDataSmallSvgPath
:
'
/path/to/no-data-small.svg
'
,
emptyUnableToConnectSvgPath
:
'
/path/to/unable-to-connect.svg
'
,
emptyUnableToConnectSvgPath
:
'
/path/to/unable-to-connect.svg
'
,
environmentsEndpoint
:
'
/root/hello-prometheus/environments/35
'
,
environmentsEndpoint
:
'
/root/hello-prometheus/environments/35
'
,
currentEnvironmentName
:
'
production
'
,
currentEnvironmentName
:
'
production
'
,
...
@@ -43,15 +46,17 @@ const resetSpy = spy => {
...
@@ -43,15 +46,17 @@ const resetSpy = spy => {
}
}
};
};
export
default
propsData
;
let
expectedPanelCount
;
function
setupComponentStore
(
component
)
{
function
setupComponentStore
(
component
)
{
// Load 2 panel groups
component
.
$store
.
commit
(
component
.
$store
.
commit
(
`monitoringDashboard/
${
types
.
RECEIVE_METRICS_DATA_SUCCESS
}
`
,
`monitoringDashboard/
${
types
.
RECEIVE_METRICS_DATA_SUCCESS
}
`
,
metricsGroupsAPIResponse
,
metricsGroupsAPIResponse
,
);
);
// Load 2 panels to the dashboard
// Load 3 panels to the dashboard, one with an empty result
component
.
$store
.
commit
(
`monitoringDashboard/
${
types
.
SET_QUERY_RESULT
}
`
,
mockedEmptyResult
);
component
.
$store
.
commit
(
component
.
$store
.
commit
(
`monitoringDashboard/
${
types
.
SET_QUERY_RESULT
}
`
,
`monitoringDashboard/
${
types
.
SET_QUERY_RESULT
}
`
,
mockedQueryResultPayload
,
mockedQueryResultPayload
,
...
@@ -61,6 +66,8 @@ function setupComponentStore(component) {
...
@@ -61,6 +66,8 @@ function setupComponentStore(component) {
mockedQueryResultPayloadCoresTotal
,
mockedQueryResultPayloadCoresTotal
,
);
);
expectedPanelCount
=
2
;
component
.
$store
.
commit
(
component
.
$store
.
commit
(
`monitoringDashboard/
${
types
.
RECEIVE_ENVIRONMENTS_DATA_SUCCESS
}
`
,
`monitoringDashboard/
${
types
.
RECEIVE_ENVIRONMENTS_DATA_SUCCESS
}
`
,
environmentData
,
environmentData
,
...
@@ -126,13 +133,9 @@ describe('Dashboard', () => {
...
@@ -126,13 +133,9 @@ describe('Dashboard', () => {
describe
(
'
no data found
'
,
()
=>
{
describe
(
'
no data found
'
,
()
=>
{
it
(
'
shows the environment selector dropdown
'
,
()
=>
{
it
(
'
shows the environment selector dropdown
'
,
()
=>
{
component
=
new
DashboardComponent
({
createComponentWrapper
();
el
:
document
.
querySelector
(
'
.prometheus-graphs
'
),
propsData
:
{
...
propsData
,
showEmptyState
:
true
},
store
,
});
expect
(
component
.
$el
.
querySelector
(
'
.js-environments-dropdown
'
)).
toBeTruthy
();
expect
(
wrapper
.
find
(
'
.js-environments-dropdown
'
).
exists
(
)).
toBeTruthy
();
});
});
});
});
...
@@ -389,9 +392,36 @@ describe('Dashboard', () => {
...
@@ -389,9 +392,36 @@ describe('Dashboard', () => {
});
});
});
});
describe
(
'
drag and drop function
'
,
()
=>
{
describe
(
'
when one of the metrics is missing
'
,
()
=>
{
let
expectedPanelCount
;
// also called metrics, naming to be improved: https://gitlab.com/gitlab-org/gitlab/issues/31565
beforeEach
(()
=>
{
mock
.
onGet
(
mockApiEndpoint
).
reply
(
200
,
metricsGroupsAPIResponse
);
});
beforeEach
(
done
=>
{
createComponentWrapper
({
hasMetrics
:
true
},
{
attachToDocument
:
true
});
setupComponentStore
(
wrapper
.
vm
);
wrapper
.
vm
.
$nextTick
(
done
);
});
it
(
'
shows a group empty area
'
,
()
=>
{
const
emptyGroup
=
wrapper
.
findAll
({
ref
:
'
empty-group
'
});
expect
(
emptyGroup
).
toHaveLength
(
1
);
expect
(
emptyGroup
.
is
(
EmptyState
)).
toBe
(
true
);
});
it
(
'
group empty area displays a "noDataGroup"
'
,
()
=>
{
expect
(
wrapper
.
findAll
({
ref
:
'
empty-group
'
})
.
at
(
0
)
.
props
(
'
selectedState
'
),
).
toEqual
(
'
noDataGroup
'
);
});
});
describe
(
'
drag and drop function
'
,
()
=>
{
const
findDraggables
=
()
=>
wrapper
.
findAll
(
VueDraggable
);
const
findDraggables
=
()
=>
wrapper
.
findAll
(
VueDraggable
);
const
findEnabledDraggables
=
()
=>
findDraggables
().
filter
(
f
=>
!
f
.
attributes
(
'
disabled
'
));
const
findEnabledDraggables
=
()
=>
findDraggables
().
filter
(
f
=>
!
f
.
attributes
(
'
disabled
'
));
const
findDraggablePanels
=
()
=>
wrapper
.
findAll
(
'
.js-draggable-panel
'
);
const
findDraggablePanels
=
()
=>
wrapper
.
findAll
(
'
.js-draggable-panel
'
);
...
@@ -399,10 +429,6 @@ describe('Dashboard', () => {
...
@@ -399,10 +429,6 @@ describe('Dashboard', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
mockApiEndpoint
).
reply
(
200
,
metricsGroupsAPIResponse
);
mock
.
onGet
(
mockApiEndpoint
).
reply
(
200
,
metricsGroupsAPIResponse
);
expectedPanelCount
=
metricsGroupsAPIResponse
.
reduce
(
(
acc
,
group
)
=>
group
.
panels
.
length
+
acc
,
0
,
);
});
});
beforeEach
(
done
=>
{
beforeEach
(
done
=>
{
...
@@ -417,10 +443,6 @@ describe('Dashboard', () => {
...
@@ -417,10 +443,6 @@ describe('Dashboard', () => {
wrapper
.
destroy
();
wrapper
.
destroy
();
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
wraps vuedraggable
'
,
()
=>
{
it
(
'
wraps vuedraggable
'
,
()
=>
{
expect
(
findDraggablePanels
().
exists
()).
toBe
(
true
);
expect
(
findDraggablePanels
().
exists
()).
toBe
(
true
);
expect
(
findDraggablePanels
().
length
).
toEqual
(
expectedPanelCount
);
expect
(
findDraggablePanels
().
length
).
toEqual
(
expectedPanelCount
);
...
@@ -459,22 +481,20 @@ describe('Dashboard', () => {
...
@@ -459,22 +481,20 @@ describe('Dashboard', () => {
it
(
'
metrics can be swapped
'
,
done
=>
{
it
(
'
metrics can be swapped
'
,
done
=>
{
const
firstDraggable
=
findDraggables
().
at
(
0
);
const
firstDraggable
=
findDraggables
().
at
(
0
);
const
mockMetrics
=
[...
metricsGroupsAPIResponse
[
0
].
panels
];
const
mockMetrics
=
[...
metricsGroupsAPIResponse
[
1
].
panels
];
const
value
=
()
=>
firstDraggable
.
props
(
'
value
'
);
expect
(
value
().
length
).
toBe
(
mockMetrics
.
length
);
const
firstTitle
=
mockMetrics
[
0
].
title
;
value
().
forEach
((
metric
,
i
)
=>
{
const
secondTitle
=
mockMetrics
[
1
].
title
;
expect
(
metric
.
title
).
toBe
(
mockMetrics
[
i
].
title
);
});
// swap two elements and `input` them
// swap two elements and `input` them
[
mockMetrics
[
0
],
mockMetrics
[
1
]]
=
[
mockMetrics
[
1
],
mockMetrics
[
0
]];
[
mockMetrics
[
0
],
mockMetrics
[
1
]]
=
[
mockMetrics
[
1
],
mockMetrics
[
0
]];
firstDraggable
.
vm
.
$emit
(
'
input
'
,
mockMetrics
);
firstDraggable
.
vm
.
$emit
(
'
input
'
,
mockMetrics
);
firstDraggable
.
vm
.
$nextTick
(()
=>
{
wrapper
.
vm
.
$nextTick
(()
=>
{
value
().
forEach
((
metric
,
i
)
=>
{
const
{
panels
}
=
wrapper
.
vm
.
dashboard
.
panel_groups
[
1
];
expect
(
metric
.
title
).
toBe
(
mockMetrics
[
i
].
title
);
});
expect
(
panels
[
1
].
title
).
toEqual
(
firstTitle
);
expect
(
panels
[
0
].
title
).
toEqual
(
secondTitle
);
done
();
done
();
});
});
});
});
...
@@ -584,7 +604,7 @@ describe('Dashboard', () => {
...
@@ -584,7 +604,7 @@ describe('Dashboard', () => {
setupComponentStore
(
component
);
setupComponentStore
(
component
);
return
Vue
.
nextTick
().
then
(()
=>
{
return
Vue
.
nextTick
().
then
(()
=>
{
promPanel
=
component
.
$el
.
querySelector
(
'
.prometheus-panel
'
);
[,
promPanel
]
=
component
.
$el
.
querySelectorAll
(
'
.prometheus-panel
'
);
promGroup
=
promPanel
.
querySelector
(
'
.prometheus-graph-group
'
);
promGroup
=
promPanel
.
querySelector
(
'
.prometheus-graph-group
'
);
panelToggle
=
promPanel
.
querySelector
(
'
.js-graph-group-toggle
'
);
panelToggle
=
promPanel
.
querySelector
(
'
.js-graph-group-toggle
'
);
chart
=
promGroup
.
querySelector
(
'
.position-relative svg
'
);
chart
=
promGroup
.
querySelector
(
'
.position-relative svg
'
);
...
...
spec/javascripts/monitoring/components/graph_group_spec.js
View file @
6b8040dc
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
GraphGroup
from
'
~/monitoring/components/graph_group.vue
'
;
import
GraphGroup
from
'
~/monitoring/components/graph_group.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
const
localVue
=
createLocalVue
();
const
localVue
=
createLocalVue
();
describe
(
'
Graph group component
'
,
()
=>
{
describe
(
'
Graph group component
'
,
()
=>
{
let
graphGroup
;
let
wrapper
;
const
findPrometheusGroup
=
()
=>
graphGroup
.
find
(
'
.prometheus-graph-group
'
);
const
findGroup
=
()
=>
wrapper
.
find
({
ref
:
'
graph-group
'
});
const
findPrometheusPanel
=
()
=>
graphGroup
.
find
(
'
.prometheus-panel
'
);
const
findContent
=
()
=>
wrapper
.
find
({
ref
:
'
graph-group-content
'
});
const
findCaretIcon
=
()
=>
wrapper
.
find
(
Icon
);
const
createComponent
=
propsData
=>
{
const
createComponent
=
propsData
=>
{
graphGroup
=
shallowMount
(
localVue
.
extend
(
GraphGroup
),
{
wrapper
=
shallowMount
(
localVue
.
extend
(
GraphGroup
),
{
propsData
,
propsData
,
sync
:
false
,
sync
:
false
,
localVue
,
localVue
,
...
@@ -18,57 +20,100 @@ describe('Graph group component', () => {
...
@@ -18,57 +20,100 @@ describe('Graph group component', () => {
};
};
afterEach
(()
=>
{
afterEach
(()
=>
{
graphGroup
.
destroy
();
wrapper
.
destroy
();
});
});
describe
(
'
When group
s can be
collapsed
'
,
()
=>
{
describe
(
'
When group
is not
collapsed
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
createComponent
({
createComponent
({
name
:
'
panel
'
,
name
:
'
panel
'
,
collapseGroup
:
tru
e
,
collapseGroup
:
fals
e
,
});
});
});
});
it
(
'
should show the angle-down caret icon when collapseGroup is true
'
,
()
=>
{
it
(
'
should show the angle-down caret icon
'
,
()
=>
{
expect
(
graphGroup
.
vm
.
caretIcon
).
toBe
(
'
angle-down
'
);
expect
(
findContent
().
isVisible
()).
toBe
(
true
);
expect
(
findCaretIcon
().
props
(
'
name
'
)).
toBe
(
'
angle-down
'
);
});
});
it
(
'
should show the angle-right caret icon when
collapseGroup is false
'
,
()
=>
{
it
(
'
should show the angle-right caret icon when
the user collapses the group
'
,
done
=>
{
graphGroup
.
vm
.
collapse
();
wrapper
.
vm
.
collapse
();
expect
(
graphGroup
.
vm
.
caretIcon
).
toBe
(
'
angle-right
'
);
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
findContent
().
isVisible
()).
toBe
(
false
);
expect
(
findCaretIcon
().
props
(
'
name
'
)).
toBe
(
'
angle-right
'
);
done
();
});
});
});
});
describe
(
'
When groups can not be collapsed
'
,
()
=>
{
it
(
'
should show the open the group when collapseGroup is set to true
'
,
done
=>
{
beforeEach
(()
=>
{
wrapper
.
setProps
({
createComponent
({
name
:
'
panel
'
,
collapseGroup
:
true
,
collapseGroup
:
true
,
showPanels
:
false
,
});
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
findContent
().
isVisible
()).
toBe
(
true
);
expect
(
findCaretIcon
().
props
(
'
name
'
)).
toBe
(
'
angle-down
'
);
done
();
});
});
});
});
it
(
'
should not contain a prometheus-panel container when showPanels is false
'
,
()
=>
{
describe
(
'
When group is collapsed
'
,
()
=>
{
expect
(
findPrometheusPanel
().
exists
()).
toBe
(
false
);
beforeEach
(()
=>
{
createComponent
({
name
:
'
panel
'
,
collapseGroup
:
true
,
});
});
it
(
'
should show the angle-down caret icon when collapseGroup is true
'
,
()
=>
{
expect
(
wrapper
.
vm
.
caretIcon
).
toBe
(
'
angle-right
'
);
});
it
(
'
should show the angle-right caret icon when collapseGroup is false
'
,
()
=>
{
wrapper
.
vm
.
collapse
();
expect
(
wrapper
.
vm
.
caretIcon
).
toBe
(
'
angle-down
'
);
});
});
});
});
describe
(
'
When collapseGroup prop is updated
'
,
()
=>
{
describe
(
'
When groups can not be collapsed
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
createComponent
({
name
:
'
panel
'
,
collapseGroup
:
false
});
createComponent
({
name
:
'
panel
'
,
showPanels
:
false
,
collapseGroup
:
false
,
});
});
it
(
'
should not have a container when showPanels is false
'
,
()
=>
{
expect
(
findGroup
().
exists
()).
toBe
(
false
);
expect
(
findContent
().
exists
()).
toBe
(
true
);
});
});
});
it
(
'
previously collapsed group should respond to the prop change
'
,
done
=>
{
describe
(
'
When group does not show a panel heading
'
,
()
=>
{
expect
(
findPrometheusGroup
().
exists
()).
toBe
(
false
);
beforeEach
(()
=>
{
createComponent
({
name
:
'
panel
'
,
showPanels
:
false
,
collapseGroup
:
false
,
});
});
graphGroup
.
setProps
({
it
(
'
should collapse the panel content
'
,
()
=>
{
collapseGroup
:
true
,
expect
(
findContent
().
isVisible
()).
toBe
(
true
);
expect
(
findCaretIcon
().
exists
()).
toBe
(
false
);
});
});
graphGroup
.
vm
.
$nextTick
(()
=>
{
it
(
'
should show the panel content when clicked
'
,
done
=>
{
expect
(
findPrometheusGroup
().
exists
()).
toBe
(
true
);
wrapper
.
vm
.
collapse
();
done
();
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
findContent
().
isVisible
()).
toBe
(
true
);
expect
(
findCaretIcon
().
exists
()).
toBe
(
false
);
done
();
});
});
});
});
});
});
});
...
...
spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
stopJobsModal
from
'
~/pages/admin/jobs/index/components/stop_jobs_modal.vue
'
;
import
stopJobsModal
from
'
~/pages/admin/jobs/index/components/stop_jobs_modal.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
describe
(
'
stop_jobs_modal.vue
'
,
()
=>
{
describe
(
'
stop_jobs_modal.vue
'
,
()
=>
{
const
props
=
{
const
props
=
{
url
:
`
${
gl
.
TEST_HOST
}
/stop_jobs_modal.vue/stopAll`
,
url
:
`
${
gl
.
TEST_HOST
}
/stop_jobs_modal.vue/stopAll`
,
...
...
spec/javascripts/pages/labels/components/promote_label_modal_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
promoteLabelModal
from
'
~/pages/projects/labels/components/promote_label_modal.vue
'
;
import
promoteLabelModal
from
'
~/pages/projects/labels/components/promote_label_modal.vue
'
;
import
eventHub
from
'
~/pages/projects/labels/event_hub
'
;
import
eventHub
from
'
~/pages/projects/labels/event_hub
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
describe
(
'
Promote label modal
'
,
()
=>
{
describe
(
'
Promote label modal
'
,
()
=>
{
let
vm
;
let
vm
;
...
...
spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
deleteMilestoneModal
from
'
~/pages/milestones/shared/components/delete_milestone_modal.vue
'
;
import
deleteMilestoneModal
from
'
~/pages/milestones/shared/components/delete_milestone_modal.vue
'
;
import
eventHub
from
'
~/pages/milestones/shared/event_hub
'
;
import
eventHub
from
'
~/pages/milestones/shared/event_hub
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
describe
(
'
delete_milestone_modal.vue
'
,
()
=>
{
describe
(
'
delete_milestone_modal.vue
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
deleteMilestoneModal
);
const
Component
=
Vue
.
extend
(
deleteMilestoneModal
);
const
props
=
{
const
props
=
{
...
...
spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
promoteMilestoneModal
from
'
~/pages/milestones/shared/components/promote_milestone_modal.vue
'
;
import
promoteMilestoneModal
from
'
~/pages/milestones/shared/components/promote_milestone_modal.vue
'
;
import
eventHub
from
'
~/pages/milestones/shared/event_hub
'
;
import
eventHub
from
'
~/pages/milestones/shared/event_hub
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
describe
(
'
Promote milestone modal
'
,
()
=>
{
describe
(
'
Promote milestone modal
'
,
()
=>
{
let
vm
;
let
vm
;
...
...
spec/javascripts/pdf/index_spec.js
View file @
6b8040dc
...
@@ -2,8 +2,8 @@ import Vue from 'vue';
...
@@ -2,8 +2,8 @@ import Vue from 'vue';
import
{
GlobalWorkerOptions
}
from
'
pdfjs-dist/build/pdf
'
;
import
{
GlobalWorkerOptions
}
from
'
pdfjs-dist/build/pdf
'
;
import
workerSrc
from
'
pdfjs-dist/build/pdf.worker.min
'
;
import
workerSrc
from
'
pdfjs-dist/build/pdf.worker.min
'
;
import
PDFLab
from
'
~/pdf/index.vue
'
;
import
{
FIXTURES_PATH
}
from
'
spec/test_constants
'
;
import
{
FIXTURES_PATH
}
from
'
spec/test_constants
'
;
import
PDFLab
from
'
~/pdf/index.vue
'
;
const
pdf
=
`
${
FIXTURES_PATH
}
/blob/pdf/test.pdf`
;
const
pdf
=
`
${
FIXTURES_PATH
}
/blob/pdf/test.pdf`
;
...
...
spec/javascripts/pdf/page_spec.js
View file @
6b8040dc
...
@@ -2,9 +2,9 @@ import Vue from 'vue';
...
@@ -2,9 +2,9 @@ import Vue from 'vue';
import
pdfjsLib
from
'
pdfjs-dist/build/pdf
'
;
import
pdfjsLib
from
'
pdfjs-dist/build/pdf
'
;
import
workerSrc
from
'
pdfjs-dist/build/pdf.worker.min
'
;
import
workerSrc
from
'
pdfjs-dist/build/pdf.worker.min
'
;
import
PageComponent
from
'
~/pdf/page/index.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
FIXTURES_PATH
}
from
'
spec/test_constants
'
;
import
{
FIXTURES_PATH
}
from
'
spec/test_constants
'
;
import
PageComponent
from
'
~/pdf/page/index.vue
'
;
const
testPDF
=
`
${
FIXTURES_PATH
}
/blob/pdf/test.pdf`
;
const
testPDF
=
`
${
FIXTURES_PATH
}
/blob/pdf/test.pdf`
;
...
...
spec/javascripts/performance_bar/index_spec.js
View file @
6b8040dc
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
'
~/performance_bar/components/performance_bar_app.vue
'
;
import
'
~/performance_bar/components/performance_bar_app.vue
'
;
import
performanceBar
from
'
~/performance_bar
'
;
import
performanceBar
from
'
~/performance_bar
'
;
import
PerformanceBarService
from
'
~/performance_bar/services/performance_bar_service
'
;
import
PerformanceBarService
from
'
~/performance_bar/services/performance_bar_service
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
describe
(
'
performance bar wrapper
'
,
()
=>
{
describe
(
'
performance bar wrapper
'
,
()
=>
{
let
mock
;
let
mock
;
let
vm
;
let
vm
;
...
...
spec/javascripts/persistent_user_callout_spec.js
View file @
6b8040dc
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
setTimeoutPromise
from
'
spec/helpers/set_timeout_promise_helper
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
PersistentUserCallout
from
'
~/persistent_user_callout
'
;
import
PersistentUserCallout
from
'
~/persistent_user_callout
'
;
import
setTimeoutPromise
from
'
spec/helpers/set_timeout_promise_helper
'
;
describe
(
'
PersistentUserCallout
'
,
()
=>
{
describe
(
'
PersistentUserCallout
'
,
()
=>
{
const
dismissEndpoint
=
'
/dismiss
'
;
const
dismissEndpoint
=
'
/dismiss
'
;
...
...
spec/javascripts/pipelines/graph/job_group_dropdown_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
JobGroupDropdown
from
'
~/pipelines/components/graph/job_group_dropdown.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
JobGroupDropdown
from
'
~/pipelines/components/graph/job_group_dropdown.vue
'
;
describe
(
'
job group dropdown component
'
,
()
=>
{
describe
(
'
job group dropdown component
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
JobGroupDropdown
);
const
Component
=
Vue
.
extend
(
JobGroupDropdown
);
...
...
spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
LinkedPipelinesColumn
from
'
~/pipelines/components/graph/linked_pipelines_column.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
LinkedPipelinesColumn
from
'
~/pipelines/components/graph/linked_pipelines_column.vue
'
;
import
mockData
from
'
./linked_pipelines_mock_data
'
;
import
mockData
from
'
./linked_pipelines_mock_data
'
;
describe
(
'
Linked Pipelines Column
'
,
()
=>
{
describe
(
'
Linked Pipelines Column
'
,
()
=>
{
...
...
spec/javascripts/pipelines/graph/stage_column_component_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
stageColumnComponent
from
'
~/pipelines/components/graph/stage_column_component.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
stageColumnComponent
from
'
~/pipelines/components/graph/stage_column_component.vue
'
;
describe
(
'
stage column component
'
,
()
=>
{
describe
(
'
stage column component
'
,
()
=>
{
let
component
;
let
component
;
...
...
spec/javascripts/pipelines/pipelines_actions_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
PipelinesActions
from
'
~/pipelines/components/pipelines_actions.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
PipelinesActions
from
'
~/pipelines/components/pipelines_actions.vue
'
;
describe
(
'
Pipelines Actions dropdown
'
,
()
=>
{
describe
(
'
Pipelines Actions dropdown
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
PipelinesActions
);
const
Component
=
Vue
.
extend
(
PipelinesActions
);
...
...
spec/javascripts/pipelines/pipelines_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
pipelinesComp
from
'
~/pipelines/components/pipelines.vue
'
;
import
pipelinesComp
from
'
~/pipelines/components/pipelines.vue
'
;
import
Store
from
'
~/pipelines/stores/pipelines_store
'
;
import
Store
from
'
~/pipelines/stores/pipelines_store
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
pipelineWithStages
,
stageReply
}
from
'
./mock_data
'
;
import
{
pipelineWithStages
,
stageReply
}
from
'
./mock_data
'
;
describe
(
'
Pipelines
'
,
()
=>
{
describe
(
'
Pipelines
'
,
()
=>
{
...
...
spec/javascripts/pipelines/stage_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
stage
from
'
~/pipelines/components/stage.vue
'
;
import
stage
from
'
~/pipelines/components/stage.vue
'
;
import
eventHub
from
'
~/pipelines/event_hub
'
;
import
eventHub
from
'
~/pipelines/event_hub
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
stageReply
}
from
'
./mock_data
'
;
import
{
stageReply
}
from
'
./mock_data
'
;
describe
(
'
Pipelines stage component
'
,
()
=>
{
describe
(
'
Pipelines stage component
'
,
()
=>
{
...
...
spec/javascripts/profile/account/components/delete_account_modal_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
deleteAccountModal
from
'
~/profile/account/components/delete_account_modal.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
deleteAccountModal
from
'
~/profile/account/components/delete_account_modal.vue
'
;
describe
(
'
DeleteAccountModal component
'
,
()
=>
{
describe
(
'
DeleteAccountModal component
'
,
()
=>
{
const
actionUrl
=
`
${
gl
.
TEST_HOST
}
/delete/user`
;
const
actionUrl
=
`
${
gl
.
TEST_HOST
}
/delete/user`
;
...
...
spec/javascripts/profile/account/components/update_username_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
updateUsername
from
'
~/profile/account/components/update_username.vue
'
;
import
updateUsername
from
'
~/profile/account/components/update_username.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
describe
(
'
UpdateUsername component
'
,
()
=>
{
describe
(
'
UpdateUsername component
'
,
()
=>
{
const
rootUrl
=
gl
.
TEST_HOST
;
const
rootUrl
=
gl
.
TEST_HOST
;
...
...
spec/javascripts/related_merge_requests/store/actions_spec.js
View file @
6b8040dc
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
*
as
types
from
'
~/related_merge_requests/store/mutation_types
'
;
import
*
as
types
from
'
~/related_merge_requests/store/mutation_types
'
;
import
actionsModule
,
*
as
actions
from
'
~/related_merge_requests/store/actions
'
;
import
actionsModule
,
*
as
actions
from
'
~/related_merge_requests/store/actions
'
;
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
describe
(
'
RelatedMergeRequest store actions
'
,
()
=>
{
describe
(
'
RelatedMergeRequest store actions
'
,
()
=>
{
let
state
;
let
state
;
...
...
spec/javascripts/releases/list/components/app_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
Vue
from
'
vue
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
app
from
'
~/releases/list/components/app.vue
'
;
import
app
from
'
~/releases/list/components/app.vue
'
;
import
createStore
from
'
~/releases/list/store
'
;
import
createStore
from
'
~/releases/list/store
'
;
import
api
from
'
~/api
'
;
import
api
from
'
~/api
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
resetStore
}
from
'
../store/helpers
'
;
import
{
resetStore
}
from
'
../store/helpers
'
;
import
{
import
{
pageInfoHeadersWithoutPagination
,
pageInfoHeadersWithoutPagination
,
...
...
spec/javascripts/releases/list/store/actions_spec.js
View file @
6b8040dc
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
import
{
import
{
requestReleases
,
requestReleases
,
fetchReleases
,
fetchReleases
,
...
@@ -8,7 +9,6 @@ import state from '~/releases/list/store/state';
...
@@ -8,7 +9,6 @@ import state from '~/releases/list/store/state';
import
*
as
types
from
'
~/releases/list/store/mutation_types
'
;
import
*
as
types
from
'
~/releases/list/store/mutation_types
'
;
import
api
from
'
~/api
'
;
import
api
from
'
~/api
'
;
import
{
parseIntPagination
}
from
'
~/lib/utils/common_utils
'
;
import
{
parseIntPagination
}
from
'
~/lib/utils/common_utils
'
;
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
import
{
pageInfoHeadersWithoutPagination
,
releases
}
from
'
../../mock_data
'
;
import
{
pageInfoHeadersWithoutPagination
,
releases
}
from
'
../../mock_data
'
;
describe
(
'
Releases State actions
'
,
()
=>
{
describe
(
'
Releases State actions
'
,
()
=>
{
...
...
spec/javascripts/reports/components/modal_open_name_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
Vuex
from
'
vuex
'
;
import
component
from
'
~/reports/components/modal_open_name.vue
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
component
from
'
~/reports/components/modal_open_name.vue
'
;
Vue
.
use
(
Vuex
);
Vue
.
use
(
Vuex
);
...
...
spec/javascripts/reports/components/summary_row_spec.js
View file @
6b8040dc
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
component
from
'
~/reports/components/summary_row.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
component
from
'
~/reports/components/summary_row.vue
'
;
describe
(
'
Summary row
'
,
()
=>
{
describe
(
'
Summary row
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
component
);
const
Component
=
Vue
.
extend
(
component
);
...
...
spec/javascripts/reports/store/actions_spec.js
View file @
6b8040dc
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
import
{
setEndpoint
,
setEndpoint
,
...
@@ -13,8 +15,6 @@ import {
...
@@ -13,8 +15,6 @@ import {
}
from
'
~/reports/store/actions
'
;
}
from
'
~/reports/store/actions
'
;
import
state
from
'
~/reports/store/state
'
;
import
state
from
'
~/reports/store/state
'
;
import
*
as
types
from
'
~/reports/store/mutation_types
'
;
import
*
as
types
from
'
~/reports/store/mutation_types
'
;
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
describe
(
'
Reports Store Actions
'
,
()
=>
{
describe
(
'
Reports Store Actions
'
,
()
=>
{
let
mockedState
;
let
mockedState
;
...
...
spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
View file @
6b8040dc
...
@@ -34,6 +34,7 @@ describe('MrWidgetPipelineContainer', () => {
...
@@ -34,6 +34,7 @@ describe('MrWidgetPipelineContainer', () => {
expect
(
wrapper
.
find
(
MrWidgetPipeline
).
props
()).
toEqual
(
expect
(
wrapper
.
find
(
MrWidgetPipeline
).
props
()).
toEqual
(
jasmine
.
objectContaining
({
jasmine
.
objectContaining
({
pipeline
:
mockStore
.
pipeline
,
pipeline
:
mockStore
.
pipeline
,
pipelineCoverageDelta
:
mockStore
.
pipelineCoverageDelta
,
ciStatus
:
mockStore
.
ciStatus
,
ciStatus
:
mockStore
.
ciStatus
,
hasCi
:
mockStore
.
hasCI
,
hasCi
:
mockStore
.
hasCI
,
sourceBranch
:
mockStore
.
sourceBranch
,
sourceBranch
:
mockStore
.
sourceBranch
,
...
@@ -68,6 +69,7 @@ describe('MrWidgetPipelineContainer', () => {
...
@@ -68,6 +69,7 @@ describe('MrWidgetPipelineContainer', () => {
expect
(
wrapper
.
find
(
MrWidgetPipeline
).
props
()).
toEqual
(
expect
(
wrapper
.
find
(
MrWidgetPipeline
).
props
()).
toEqual
(
jasmine
.
objectContaining
({
jasmine
.
objectContaining
({
pipeline
:
mockStore
.
mergePipeline
,
pipeline
:
mockStore
.
mergePipeline
,
pipelineCoverageDelta
:
mockStore
.
pipelineCoverageDelta
,
ciStatus
:
mockStore
.
ciStatus
,
ciStatus
:
mockStore
.
ciStatus
,
hasCi
:
mockStore
.
hasCI
,
hasCi
:
mockStore
.
hasCI
,
sourceBranch
:
mockStore
.
targetBranch
,
sourceBranch
:
mockStore
.
targetBranch
,
...
...
spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
View file @
6b8040dc
...
@@ -62,6 +62,38 @@ describe('MRWidgetPipeline', () => {
...
@@ -62,6 +62,38 @@ describe('MRWidgetPipeline', () => {
expect
(
vm
.
hasCIError
).
toEqual
(
true
);
expect
(
vm
.
hasCIError
).
toEqual
(
true
);
});
});
});
});
describe
(
'
coverageDeltaClass
'
,
()
=>
{
it
(
'
should return no class if there is no coverage change
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
pipeline
:
mockData
.
pipeline
,
pipelineCoverageDelta
:
'
0
'
,
troubleshootingDocsPath
:
'
help
'
,
});
expect
(
vm
.
coverageDeltaClass
).
toEqual
(
''
);
});
it
(
'
should return text-success if the coverage increased
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
pipeline
:
mockData
.
pipeline
,
pipelineCoverageDelta
:
'
10
'
,
troubleshootingDocsPath
:
'
help
'
,
});
expect
(
vm
.
coverageDeltaClass
).
toEqual
(
'
text-success
'
);
});
it
(
'
should return text-danger if the coverage decreased
'
,
()
=>
{
vm
=
mountComponent
(
Component
,
{
pipeline
:
mockData
.
pipeline
,
pipelineCoverageDelta
:
'
-12
'
,
troubleshootingDocsPath
:
'
help
'
,
});
expect
(
vm
.
coverageDeltaClass
).
toEqual
(
'
text-danger
'
);
});
});
});
});
describe
(
'
rendered output
'
,
()
=>
{
describe
(
'
rendered output
'
,
()
=>
{
...
@@ -96,6 +128,7 @@ describe('MRWidgetPipeline', () => {
...
@@ -96,6 +128,7 @@ describe('MRWidgetPipeline', () => {
pipeline
:
mockData
.
pipeline
,
pipeline
:
mockData
.
pipeline
,
hasCi
:
true
,
hasCi
:
true
,
ciStatus
:
'
success
'
,
ciStatus
:
'
success
'
,
pipelineCoverageDelta
:
mockData
.
pipelineCoverageDelta
,
troubleshootingDocsPath
:
'
help
'
,
troubleshootingDocsPath
:
'
help
'
,
});
});
});
});
...
@@ -132,6 +165,13 @@ describe('MRWidgetPipeline', () => {
...
@@ -132,6 +165,13 @@ describe('MRWidgetPipeline', () => {
`Coverage
${
mockData
.
pipeline
.
coverage
}
`
,
`Coverage
${
mockData
.
pipeline
.
coverage
}
`
,
);
);
});
});
it
(
'
should render pipeline coverage delta information
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipeline-coverage-delta.text-danger
'
)).
toBeDefined
();
expect
(
vm
.
$el
.
querySelector
(
'
.js-pipeline-coverage-delta
'
).
textContent
).
toContain
(
`(
${
mockData
.
pipelineCoverageDelta
}
%)`
,
);
});
});
});
describe
(
'
without commit path
'
,
()
=>
{
describe
(
'
without commit path
'
,
()
=>
{
...
...
spec/javascripts/vue_mr_widget/mock_data.js
View file @
6b8040dc
...
@@ -185,6 +185,7 @@ export default {
...
@@ -185,6 +185,7 @@ export default {
created_at
:
'
2017-04-07T12:27:19.520Z
'
,
created_at
:
'
2017-04-07T12:27:19.520Z
'
,
updated_at
:
'
2017-04-07T15:28:44.800Z
'
,
updated_at
:
'
2017-04-07T15:28:44.800Z
'
,
},
},
pipelineCoverageDelta
:
'
15.25
'
,
work_in_progress
:
false
,
work_in_progress
:
false
,
source_branch_exists
:
false
,
source_branch_exists
:
false
,
mergeable_discussions_state
:
true
,
mergeable_discussions_state
:
true
,
...
...
spec/models/merge_request_spec.rb
View file @
6b8040dc
...
@@ -2821,6 +2821,63 @@ describe MergeRequest do
...
@@ -2821,6 +2821,63 @@ describe MergeRequest do
end
end
end
end
describe
'#pipeline_coverage_delta'
do
let!
(
:project
)
{
create
(
:project
,
:repository
)
}
let!
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
let!
(
:source_pipeline
)
do
create
(
:ci_pipeline
,
project:
project
,
ref:
merge_request
.
source_branch
,
sha:
merge_request
.
diff_head_sha
)
end
let!
(
:target_pipeline
)
do
create
(
:ci_pipeline
,
project:
project
,
ref:
merge_request
.
target_branch
,
sha:
merge_request
.
diff_base_sha
)
end
def
create_build
(
pipeline
,
coverage
,
name
)
create
(
:ci_build
,
:success
,
pipeline:
pipeline
,
coverage:
coverage
,
name:
name
)
merge_request
.
update_head_pipeline
end
context
'when both source and target branches have coverage information'
do
it
'returns the appropriate coverage delta'
do
create_build
(
source_pipeline
,
60.2
,
'test:1'
)
create_build
(
target_pipeline
,
50
,
'test:2'
)
expect
(
merge_request
.
pipeline_coverage_delta
).
to
eq
(
'10.20'
)
end
end
context
'when target branch does not have coverage information'
do
it
'returns nil'
do
create_build
(
source_pipeline
,
50
,
'test:1'
)
expect
(
merge_request
.
pipeline_coverage_delta
).
to
be_nil
end
end
context
'when source branch does not have coverage information'
do
it
'returns nil for coverage_delta'
do
create_build
(
target_pipeline
,
50
,
'test:1'
)
expect
(
merge_request
.
pipeline_coverage_delta
).
to
be_nil
end
end
context
'neither source nor target branch has coverage information'
do
it
'returns nil for coverage_delta'
do
expect
(
merge_request
.
pipeline_coverage_delta
).
to
be_nil
end
end
end
describe
'#base_pipeline'
do
describe
'#base_pipeline'
do
let
(
:pipeline_arguments
)
do
let
(
:pipeline_arguments
)
do
{
{
...
...
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