Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
heptapod
heptapod
Commits
372c1e5067bb
Commit
438a7f52
authored
Jan 16, 2020
by
GitLab Bot
Browse files
Add latest changes from gitlab-org/gitlab@master
parent
ac0a6366d831
Changes
125
Hide whitespace changes
Inline
Side-by-side
app/assets/javascripts/monitoring/components/dashboard.vue
View file @
372c1e50
...
...
@@ -17,10 +17,13 @@ import createFlash from '~/flash';
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
{
getParameterValues
,
mergeUrlParams
,
redirectTo
}
from
'
~/lib/utils/url_utility
'
;
import
invalidUrl
from
'
~/lib/utils/invalid_url
'
;
import
DateTimePicker
from
'
./date_time_picker/date_time_picker.vue
'
;
import
GraphGroup
from
'
./graph_group.vue
'
;
import
EmptyState
from
'
./empty_state.vue
'
;
import
GroupEmptyState
from
'
./group_empty_state.vue
'
;
import
DashboardsDropdown
from
'
./dashboards_dropdown.vue
'
;
import
TrackEventDirective
from
'
~/vue_shared/directives/track_event
'
;
import
{
getTimeDiff
,
getAddMetricTrackingOptions
}
from
'
../utils
'
;
import
{
metricStates
}
from
'
../constants
'
;
...
...
@@ -31,16 +34,18 @@ export default {
components
:
{
VueDraggable
,
PanelType
,
GraphGroup
,
EmptyState
,
GroupEmptyState
,
Icon
,
GlButton
,
GlDropdown
,
GlDropdownItem
,
GlFormGroup
,
GlModal
,
DateTimePicker
,
GraphGroup
,
EmptyState
,
GroupEmptyState
,
DashboardsDropdown
,
},
directives
:
{
GlModal
:
GlModalDirective
,
...
...
@@ -83,6 +88,10 @@ export default {
type
:
String
,
required
:
true
,
},
defaultBranch
:
{
type
:
String
,
required
:
true
,
},
metricsEndpoint
:
{
type
:
String
,
required
:
true
,
...
...
@@ -140,6 +149,11 @@ export default {
required
:
false
,
default
:
invalidUrl
,
},
dashboardsEndpoint
:
{
type
:
String
,
required
:
false
,
default
:
invalidUrl
,
},
currentDashboard
:
{
type
:
String
,
required
:
false
,
...
...
@@ -199,9 +213,6 @@ export default {
selectedDashboard
()
{
return
this
.
allDashboards
.
find
(
d
=>
d
.
path
===
this
.
currentDashboard
)
||
this
.
firstDashboard
;
},
selectedDashboardText
()
{
return
this
.
selectedDashboard
.
display_name
;
},
showRearrangePanelsBtn
()
{
return
!
this
.
showEmptyState
&&
this
.
rearrangePanelsAvailable
;
},
...
...
@@ -223,6 +234,7 @@ export default {
environmentsEndpoint
:
this
.
environmentsEndpoint
,
deploymentsEndpoint
:
this
.
deploymentsEndpoint
,
dashboardEndpoint
:
this
.
dashboardEndpoint
,
dashboardsEndpoint
:
this
.
dashboardsEndpoint
,
currentDashboard
:
this
.
currentDashboard
,
projectPath
:
this
.
projectPath
,
});
...
...
@@ -314,6 +326,13 @@ export default {
return
!
this
.
getMetricStates
(
groupKey
).
includes
(
metricStates
.
OK
);
},
getAddMetricTrackingOptions
,
selectDashboard
(
dashboard
)
{
const
params
=
{
dashboard
:
dashboard
.
path
,
};
redirectTo
(
mergeUrlParams
(
params
,
window
.
location
.
href
));
},
},
addMetric
:
{
title
:
s__
(
'
Metrics|Add metric
'
),
...
...
@@ -333,21 +352,14 @@ export default {
label-for=
"monitor-dashboards-dropdown"
class=
"col-sm-12 col-md-6 col-lg-2"
>
<
gl
-dropdown
<
dashboards
-dropdown
id=
"monitor-dashboards-dropdown"
class=
"mb-0 d-flex
js-dashboards-dropdown
"
class=
"mb-0 d-flex"
toggle-class=
"dropdown-menu-toggle"
:text=
"selectedDashboardText"
>
<gl-dropdown-item
v-for=
"dashboard in allDashboards"
:key=
"dashboard.path"
:active=
"dashboard.path === currentDashboard"
active-class=
"is-active"
:href=
"`?dashboard=$
{dashboard.path}`"
>
{{
dashboard
.
display_name
||
dashboard
.
path
}}
</gl-dropdown-item
>
</gl-dropdown>
:default-branch=
"defaultBranch"
:selected-dashboard=
"selectedDashboard"
@
selectDashboard=
"selectDashboard($event)"
/>
</gl-form-group>
<gl-form-group
...
...
app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
0 → 100644
View file @
372c1e50
<
script
>
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
GlAlert
,
GlDropdown
,
GlDropdownItem
,
GlDropdownDivider
,
GlModal
,
GlLoadingIcon
,
GlModalDirective
,
}
from
'
@gitlab/ui
'
;
import
DuplicateDashboardForm
from
'
./duplicate_dashboard_form.vue
'
;
const
events
=
{
selectDashboard
:
'
selectDashboard
'
,
};
export
default
{
components
:
{
GlAlert
,
GlDropdown
,
GlDropdownItem
,
GlDropdownDivider
,
GlModal
,
GlLoadingIcon
,
DuplicateDashboardForm
,
},
directives
:
{
GlModal
:
GlModalDirective
,
},
props
:
{
selectedDashboard
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
defaultBranch
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
alert
:
null
,
loading
:
false
,
form
:
{},
};
},
computed
:
{
...
mapState
(
'
monitoringDashboard
'
,
[
'
allDashboards
'
]),
isSystemDashboard
()
{
return
this
.
selectedDashboard
.
system_dashboard
;
},
selectedDashboardText
()
{
return
this
.
selectedDashboard
.
display_name
;
},
},
methods
:
{
...
mapActions
(
'
monitoringDashboard
'
,
[
'
duplicateSystemDashboard
'
]),
selectDashboard
(
dashboard
)
{
this
.
$emit
(
events
.
selectDashboard
,
dashboard
);
},
ok
(
bvModalEvt
)
{
// Prevent modal from hiding in case submit fails
bvModalEvt
.
preventDefault
();
this
.
loading
=
true
;
this
.
alert
=
null
;
this
.
duplicateSystemDashboard
(
this
.
form
)
.
then
(
createdDashboard
=>
{
this
.
loading
=
false
;
this
.
alert
=
null
;
// Trigger hide modal as submit is successful
this
.
$refs
.
duplicateDashboardModal
.
hide
();
// Dashboards in the default branch become available immediately.
// Not so in other branches, so we refresh the current dashboard
const
dashboard
=
this
.
form
.
branch
===
this
.
defaultBranch
?
createdDashboard
:
this
.
selectedDashboard
;
this
.
$emit
(
events
.
selectDashboard
,
dashboard
);
})
.
catch
(
error
=>
{
this
.
loading
=
false
;
this
.
alert
=
error
;
});
},
hide
()
{
this
.
alert
=
null
;
},
formChange
(
form
)
{
this
.
form
=
form
;
},
},
};
</
script
>
<
template
>
<gl-dropdown
toggle-class=
"dropdown-menu-toggle"
:text=
"selectedDashboardText"
>
<gl-dropdown-item
v-for=
"dashboard in allDashboards"
:key=
"dashboard.path"
:active=
"dashboard.path === selectedDashboard.path"
active-class=
"is-active"
@
click=
"selectDashboard(dashboard)"
>
{{
dashboard
.
display_name
||
dashboard
.
path
}}
</gl-dropdown-item>
<template
v-if=
"isSystemDashboard"
>
<gl-dropdown-divider
/>
<gl-modal
ref=
"duplicateDashboardModal"
modal-id=
"duplicateDashboardModal"
:title=
"s__('Metrics|Duplicate dashboard')"
ok-variant=
"success"
@
ok=
"ok"
@
hide=
"hide"
>
<gl-alert
v-if=
"alert"
class=
"mb-3"
variant=
"danger"
@
dismiss=
"alert = null"
>
{{
alert
}}
</gl-alert>
<duplicate-dashboard-form
:dashboard=
"selectedDashboard"
:default-branch=
"defaultBranch"
@
change=
"formChange"
/>
<template
#modal-ok
>
<gl-loading-icon
v-if=
"loading"
inline
color=
"light"
/>
{{
loading
?
s__
(
'
Metrics|Duplicating...
'
)
:
s__
(
'
Metrics|Duplicate
'
)
}}
</
template
>
</gl-modal>
<gl-dropdown-item
ref=
"duplicateDashboardItem"
v-gl-modal=
"'duplicateDashboardModal'"
>
{{ s__('Metrics|Duplicate dashboard') }}
</gl-dropdown-item>
</template>
</gl-dropdown>
</template>
app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue
0 → 100644
View file @
372c1e50
<
script
>
import
{
__
,
s__
,
sprintf
}
from
'
~/locale
'
;
import
{
GlFormGroup
,
GlFormInput
,
GlFormRadioGroup
,
GlFormTextarea
}
from
'
@gitlab/ui
'
;
const
defaultFileName
=
dashboard
=>
dashboard
.
path
.
split
(
'
/
'
).
reverse
()[
0
];
export
default
{
components
:
{
GlFormGroup
,
GlFormInput
,
GlFormRadioGroup
,
GlFormTextarea
,
},
props
:
{
dashboard
:
{
type
:
Object
,
required
:
true
,
},
defaultBranch
:
{
type
:
String
,
required
:
true
,
},
},
radioVals
:
{
/* Use the default branch (e.g. master) */
DEFAULT
:
'
DEFAULT
'
,
/* Create a new branch */
NEW
:
'
NEW
'
,
},
data
()
{
return
{
form
:
{
dashboard
:
this
.
dashboard
.
path
,
fileName
:
defaultFileName
(
this
.
dashboard
),
commitMessage
:
''
,
},
branchName
:
''
,
branchOption
:
this
.
$options
.
radioVals
.
NEW
,
branchOptions
:
[
{
value
:
this
.
$options
.
radioVals
.
DEFAULT
,
html
:
sprintf
(
__
(
'
Commit to %{branchName} branch
'
),
{
branchName
:
`<strong>
${
this
.
defaultBranch
}
</strong>`
,
},
false
,
),
},
{
value
:
this
.
$options
.
radioVals
.
NEW
,
text
:
__
(
'
Create new branch
'
)
},
],
};
},
computed
:
{
defaultCommitMsg
()
{
return
sprintf
(
s__
(
'
Metrics|Create custom dashboard %{fileName}
'
),
{
fileName
:
this
.
form
.
fileName
,
});
},
fileNameState
()
{
// valid if empty or *.yml
return
!
(
this
.
form
.
fileName
&&
!
this
.
form
.
fileName
.
endsWith
(
'
.yml
'
));
},
fileNameFeedback
()
{
return
!
this
.
fileNameState
?
s__
(
'
The file name should have a .yml extension
'
)
:
''
;
},
},
mounted
()
{
this
.
change
();
},
methods
:
{
change
()
{
this
.
$emit
(
'
change
'
,
{
...
this
.
form
,
commitMessage
:
this
.
form
.
commitMessage
||
this
.
defaultCommitMsg
,
branch
:
this
.
branchOption
===
this
.
$options
.
radioVals
.
NEW
?
this
.
branchName
:
this
.
defaultBranch
,
});
},
focus
(
option
)
{
if
(
option
===
this
.
$options
.
radioVals
.
NEW
)
{
this
.
$nextTick
(()
=>
{
this
.
$refs
.
branchName
.
$el
.
focus
();
});
}
},
},
};
</
script
>
<
template
>
<form
@
change=
"change"
>
<p
class=
"text-muted"
>
{{
s__
(
`Metrics|You can save a copy of this dashboard to your repository
so it can be customized. Select a file name and branch to
save it.`
)
}}
</p>
<gl-form-group
ref=
"fileNameFormGroup"
:label=
"__('File name')"
:state=
"fileNameState"
:invalid-feedback=
"fileNameFeedback"
label-size=
"sm"
label-for=
"fileName"
>
<gl-form-input
id=
"fileName"
ref=
"fileName"
v-model=
"form.fileName"
:required=
"true"
/>
</gl-form-group>
<gl-form-group
:label=
"__('Branch')"
label-size=
"sm"
label-for=
"branch"
>
<gl-form-radio-group
ref=
"branchOption"
v-model=
"branchOption"
:checked=
"$options.radioVals.NEW"
:stacked=
"true"
:options=
"branchOptions"
@
change=
"focus"
/>
<gl-form-input
v-show=
"branchOption === $options.radioVals.NEW"
id=
"branchName"
ref=
"branchName"
v-model=
"branchName"
/>
</gl-form-group>
<gl-form-group
:label=
"__('Commit message (optional)')"
label-size=
"sm"
label-for=
"commitMessage"
>
<gl-form-textarea
id=
"commitMessage"
ref=
"commitMessage"
v-model=
"form.commitMessage"
:placeholder=
"defaultCommitMsg"
/>
</gl-form-group>
</form>
</
template
>
app/assets/javascripts/monitoring/stores/actions.js
View file @
372c1e50
...
...
@@ -214,5 +214,29 @@ export const setPanelGroupMetrics = ({ commit }, data) => {
commit
(
types
.
SET_PANEL_GROUP_METRICS
,
data
);
};
export
const
duplicateSystemDashboard
=
({
state
},
payload
)
=>
{
const
params
=
{
dashboard
:
payload
.
dashboard
,
file_name
:
payload
.
fileName
,
branch
:
payload
.
branch
,
commit_message
:
payload
.
commitMessage
,
};
return
axios
.
post
(
state
.
dashboardsEndpoint
,
params
)
.
then
(
response
=>
response
.
data
)
.
then
(
data
=>
data
.
dashboard
)
.
catch
(
error
=>
{
const
{
response
}
=
error
;
if
(
response
&&
response
.
data
&&
response
.
data
.
error
)
{
throw
sprintf
(
s__
(
'
Metrics|There was an error creating the dashboard. %{error}
'
),
{
error
:
response
.
data
.
error
,
});
}
else
{
throw
s__
(
'
Metrics|There was an error creating the dashboard.
'
);
}
});
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export
default
()
=>
{};
app/assets/javascripts/monitoring/stores/mutations.js
View file @
372c1e50
...
...
@@ -175,6 +175,7 @@ export default {
state
.
environmentsEndpoint
=
endpoints
.
environmentsEndpoint
;
state
.
deploymentsEndpoint
=
endpoints
.
deploymentsEndpoint
;
state
.
dashboardEndpoint
=
endpoints
.
dashboardEndpoint
;
state
.
dashboardsEndpoint
=
endpoints
.
dashboardsEndpoint
;
state
.
currentDashboard
=
endpoints
.
currentDashboard
;
state
.
projectPath
=
endpoints
.
projectPath
;
},
...
...
app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment.vue
View file @
372c1e50
<
script
>
import
{
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
DeploymentInfo
from
'
./deployment_info.vue
'
;
import
DeploymentViewButton
from
'
./deployment_view_button.vue
'
;
import
DeploymentStopButton
from
'
./deployment_stop_button.vue
'
;
...
...
@@ -14,9 +14,6 @@ export default {
DeploymentStopButton
,
DeploymentViewButton
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
props
:
{
deployment
:
{
type
:
Object
,
...
...
@@ -43,6 +40,14 @@ export default {
},
},
computed
:
{
appButtonText
()
{
return
{
text
:
this
.
isCurrent
?
s__
(
'
Review App|View app
'
)
:
s__
(
'
Review App|View latest app
'
),
tooltip
:
this
.
isCurrent
?
''
:
__
(
'
View the latest successful deployment to this environment
'
),
};
},
canBeManuallyDeployed
()
{
return
this
.
computedDeploymentStatus
===
MANUAL_DEPLOY
;
},
...
...
@@ -55,9 +60,6 @@ export default {
hasExternalUrls
()
{
return
Boolean
(
this
.
deployment
.
external_url
&&
this
.
deployment
.
external_url_formatted
);
},
hasPreviousDeployment
()
{
return
Boolean
(
!
this
.
isCurrent
&&
this
.
deployment
.
deployed_at
);
},
isCurrent
()
{
return
this
.
computedDeploymentStatus
===
SUCCESS
;
},
...
...
@@ -89,7 +91,7 @@ export default {
<!-- show appropriate version of review app button -->
<deployment-view-button
v-if=
"hasExternalUrls"
:
is-current=
"isCurren
t"
:
app-button-text=
"appButtonTex
t"
:deployment=
"deployment"
:show-visual-review-app=
"showVisualReviewApp"
:visual-review-app-metadata=
"visualReviewAppMeta"
...
...
app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_view_button.vue
View file @
372c1e50
...
...
@@ -11,12 +11,12 @@ export default {
import
(
'
ee_component/vue_merge_request_widget/components/visual_review_app_link.vue
'
),
},
props
:
{
deploymen
t
:
{
appButtonTex
t
:
{
type
:
Object
,
required
:
true
,
},
isCurr
ent
:
{
type
:
Boolean
,
deploym
ent
:
{
type
:
Object
,
required
:
true
,
},
showVisualReviewApp
:
{
...
...
@@ -60,7 +60,7 @@ export default {
>
<template
slot=
"mainAction"
slot-scope=
"slotProps"
>
<review-app-link
:is
-current=
"isCurren
t"
:
d
is
play=
"appButtonTex
t"
:link=
"deploymentExternalUrl"
:css-class=
"`deploy-link js-deploy-url inline $
{slotProps.className}`"
/>
...
...
@@ -85,7 +85,7 @@ export default {
</filtered-search-dropdown>
<
template
v-else
>
<review-app-link
:is
-current=
"isCurren
t"
:
d
is
play=
"appButtonTex
t"
:link=
"deploymentExternalUrl"
css-class=
"js-deploy-url deploy-link btn btn-default btn-sm inline"
/>
...
...
app/assets/javascripts/vue_merge_request_widget/components/review_app_link.vue
View file @
372c1e50
<
script
>
import
{
__
}
from
'
~/locale
'
;
import
{
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
export
default
{
components
:
{
Icon
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
props
:
{
cssClass
:
{
type
:
String
,
required
:
true
,
},
is
Current
:
{
type
:
Boolean
,
d
is
play
:
{
type
:
Object
,
required
:
true
,
},
link
:
{
...
...
@@ -20,15 +23,12 @@ export default {
required
:
true
,
},
},
computed
:
{
linkText
()
{
return
this
.
isCurrent
?
__
(
'
View app
'
)
:
__
(
'
View previous app
'
);
},
},
};
</
script
>
<
template
>
<a
v-gl-tooltip
:title=
"display.tooltip"
:href=
"link"
target=
"_blank"
rel=
"noopener noreferrer nofollow"
...
...
@@ -36,6 +36,6 @@ export default {
data-track-event=
"open_review_app"
data-track-label=
"review_app"
>
{{
linkT
ext
}}
<icon
class=
"fgray"
name=
"external-link"
/>
{{
display
.
t
ext
}}
<icon
class=
"fgray"
name=
"external-link"
/>
</a>
</
template
>
app/controllers/projects/performance_monitoring/dashboards_controller.rb
View file @
372c1e50
...
...
@@ -7,90 +7,53 @@ class DashboardsController < ::Projects::ApplicationController
before_action
:check_repository_available!
before_action
:validate_required_params!
before_action
:validate_dashboard_template!
before_action
:authorize_push!
USER_DASHBOARDS_DIR
=
::
Metrics
::
Dashboard
::
ProjectDashboardService
::
DASHBOARD_ROOT
DASHBOARD_TEMPLATES
=
{
::
Metrics
::
Dashboard
::
SystemDashboardService
::
DASHBOARD_PATH
=>
::
Metrics
::
Dashboard
::
SystemDashboardService
::
DASHBOARD_PATH
}.
freeze
rescue_from
ActionController
::
ParameterMissing
do
|
exception
|
respond_error
(
http_status: :bad_request
,
message:
_
(
'Request parameter %{param} is missing.'
)
%
{
param:
exception
.
param
})
end
def
create
result
=
::
Files
::
Create
Service
.
new
(
project
,
current_user
,
dashboard_
attr
s
).
execute
result
=
::
Metrics
::
Dashboard
::
CloneDashboard
Service
.
new
(
project
,
current_user
,
dashboard_
param
s
).
execute