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
9867eaf3
Commit
9867eaf3
authored
Oct 10, 2018
by
Mike Greiling
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Prettify issue_show and jobs modules
parent
550f5574
Changes
23
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
1074 additions
and
1080 deletions
+1074
-1080
app/assets/javascripts/issue_show/components/app.vue
app/assets/javascripts/issue_show/components/app.vue
+257
-259
app/assets/javascripts/issue_show/components/description.vue
app/assets/javascripts/issue_show/components/description.vue
+88
-93
app/assets/javascripts/issue_show/components/edit_actions.vue
...assets/javascripts/issue_show/components/edit_actions.vue
+53
-53
app/assets/javascripts/issue_show/components/edited.vue
app/assets/javascripts/issue_show/components/edited.vue
+25
-26
app/assets/javascripts/issue_show/components/fields/description.vue
.../javascripts/issue_show/components/fields/description.vue
+37
-37
app/assets/javascripts/issue_show/components/fields/description_template.vue
...pts/issue_show/components/fields/description_template.vue
+38
-38
app/assets/javascripts/issue_show/components/fields/title.vue
...assets/javascripts/issue_show/components/fields/title.vue
+9
-9
app/assets/javascripts/issue_show/components/form.vue
app/assets/javascripts/issue_show/components/form.vue
+71
-71
app/assets/javascripts/issue_show/components/locked_warning.vue
...sets/javascripts/issue_show/components/locked_warning.vue
+6
-6
app/assets/javascripts/issue_show/stores/index.js
app/assets/javascripts/issue_show/stores/index.js
+4
-2
app/assets/javascripts/jobs/components/artifacts_block.vue
app/assets/javascripts/jobs/components/artifacts_block.vue
+20
-22
app/assets/javascripts/jobs/components/commit_block.vue
app/assets/javascripts/jobs/components/commit_block.vue
+19
-19
app/assets/javascripts/jobs/components/empty_state.vue
app/assets/javascripts/jobs/components/empty_state.vue
+32
-32
app/assets/javascripts/jobs/components/environments_block.vue
...assets/javascripts/jobs/components/environments_block.vue
+106
-104
app/assets/javascripts/jobs/components/erased_block.vue
app/assets/javascripts/jobs/components/erased_block.vue
+20
-20
app/assets/javascripts/jobs/components/job_log.vue
app/assets/javascripts/jobs/components/job_log.vue
+12
-12
app/assets/javascripts/jobs/components/job_log_controllers.vue
...ssets/javascripts/jobs/components/job_log_controllers.vue
+57
-58
app/assets/javascripts/jobs/components/jobs_container.vue
app/assets/javascripts/jobs/components/jobs_container.vue
+27
-27
app/assets/javascripts/jobs/components/sidebar.vue
app/assets/javascripts/jobs/components/sidebar.vue
+101
-101
app/assets/javascripts/jobs/components/sidebar_detail_row.vue
...assets/javascripts/jobs/components/sidebar_detail_row.vue
+25
-25
app/assets/javascripts/jobs/components/stages_dropdown.vue
app/assets/javascripts/jobs/components/stages_dropdown.vue
+40
-40
app/assets/javascripts/jobs/components/trigger_block.vue
app/assets/javascripts/jobs/components/trigger_block.vue
+20
-20
app/assets/javascripts/jobs/store/index.js
app/assets/javascripts/jobs/store/index.js
+7
-6
No files found.
app/assets/javascripts/issue_show/components/app.vue
View file @
9867eaf3
<
script
>
import
Visibility
from
'
visibilityjs
'
;
import
{
visitUrl
}
from
'
../../lib/utils/url_utility
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
eventHub
from
'
../event_hub
'
;
import
Service
from
'
../services/index
'
;
import
Store
from
'
../stores
'
;
import
titleComponent
from
'
./title.vue
'
;
import
descriptionComponent
from
'
./description.vue
'
;
import
editedComponent
from
'
./edited.vue
'
;
import
formComponent
from
'
./form.vue
'
;
import
recaptchaModalImplementor
from
'
../../vue_shared/mixins/recaptcha_modal_implementor
'
;
import
Visibility
from
'
visibilityjs
'
;
import
{
visitUrl
}
from
'
../../lib/utils/url_utility
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
eventHub
from
'
../event_hub
'
;
import
Service
from
'
../services/index
'
;
import
Store
from
'
../stores
'
;
import
titleComponent
from
'
./title.vue
'
;
import
descriptionComponent
from
'
./description.vue
'
;
import
editedComponent
from
'
./edited.vue
'
;
import
formComponent
from
'
./form.vue
'
;
import
recaptchaModalImplementor
from
'
../../vue_shared/mixins/recaptcha_modal_implementor
'
;
export
default
{
components
:
{
descriptionComponent
,
titleComponent
,
editedComponent
,
formComponent
,
},
mixins
:
[
recaptchaModalImplementor
,
],
props
:
{
endpoint
:
{
required
:
true
,
type
:
String
,
},
updateEndpoint
:
{
required
:
true
,
type
:
String
,
},
canUpdate
:
{
required
:
true
,
type
:
Boolean
,
},
canDestroy
:
{
required
:
true
,
type
:
Boolean
,
},
showInlineEditButton
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
showDeleteButton
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
enableAutocomplete
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
issuableRef
:
{
type
:
String
,
required
:
true
,
},
initialTitleHtml
:
{
type
:
String
,
required
:
true
,
},
initialTitleText
:
{
type
:
String
,
required
:
true
,
},
initialDescriptionHtml
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
initialDescriptionText
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
initialTaskStatus
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedAt
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedByName
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedByPath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
issuableTemplates
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
markdownPreviewPath
:
{
type
:
String
,
required
:
true
,
},
markdownDocsPath
:
{
type
:
String
,
required
:
true
,
},
markdownVersion
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
projectPath
:
{
type
:
String
,
required
:
true
,
},
projectNamespace
:
{
type
:
String
,
required
:
true
,
},
issuableType
:
{
type
:
String
,
required
:
false
,
default
:
'
issue
'
,
},
canAttachFile
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
export
default
{
components
:
{
descriptionComponent
,
titleComponent
,
editedComponent
,
formComponent
,
},
mixins
:
[
recaptchaModalImplementor
],
props
:
{
endpoint
:
{
required
:
true
,
type
:
String
,
},
data
()
{
const
store
=
new
Store
({
titleHtml
:
this
.
initialTitleHtml
,
titleText
:
this
.
initialTitleText
,
descriptionHtml
:
this
.
initialDescriptionHtml
,
descriptionText
:
this
.
initialDescriptionText
,
updatedAt
:
this
.
updatedAt
,
updatedByName
:
this
.
updatedByName
,
updatedByPath
:
this
.
updatedByPath
,
taskStatus
:
this
.
initialTaskStatus
,
});
updateEndpoint
:
{
required
:
true
,
type
:
String
,
},
canUpdate
:
{
required
:
true
,
type
:
Boolean
,
},
canDestroy
:
{
required
:
true
,
type
:
Boolean
,
},
showInlineEditButton
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
showDeleteButton
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
enableAutocomplete
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
issuableRef
:
{
type
:
String
,
required
:
true
,
},
initialTitleHtml
:
{
type
:
String
,
required
:
true
,
},
initialTitleText
:
{
type
:
String
,
required
:
true
,
},
initialDescriptionHtml
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
initialDescriptionText
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
initialTaskStatus
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedAt
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedByName
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedByPath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
issuableTemplates
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
markdownPreviewPath
:
{
type
:
String
,
required
:
true
,
},
markdownDocsPath
:
{
type
:
String
,
required
:
true
,
},
markdownVersion
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
projectPath
:
{
type
:
String
,
required
:
true
,
},
projectNamespace
:
{
type
:
String
,
required
:
true
,
},
issuableType
:
{
type
:
String
,
required
:
false
,
default
:
'
issue
'
,
},
canAttachFile
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
},
data
()
{
const
store
=
new
Store
({
titleHtml
:
this
.
initialTitleHtml
,
titleText
:
this
.
initialTitleText
,
descriptionHtml
:
this
.
initialDescriptionHtml
,
descriptionText
:
this
.
initialDescriptionText
,
updatedAt
:
this
.
updatedAt
,
updatedByName
:
this
.
updatedByName
,
updatedByPath
:
this
.
updatedByPath
,
taskStatus
:
this
.
initialTaskStatus
,
});
return
{
store
,
state
:
store
.
state
,
showForm
:
false
,
};
},
computed
:
{
formState
()
{
return
this
.
store
.
formState
;
},
hasUpdated
()
{
return
!!
this
.
state
.
updatedAt
;
},
issueChanged
()
{
const
descriptionChanged
=
this
.
initialDescriptionText
!==
this
.
store
.
formState
.
description
;
const
titleChanged
=
this
.
initialTitleText
!==
this
.
store
.
formState
.
title
;
return
descriptionChanged
||
titleChanged
;
},
return
{
store
,
state
:
store
.
state
,
showForm
:
false
,
};
},
computed
:
{
formState
()
{
return
this
.
store
.
formState
;
},
created
()
{
this
.
service
=
new
Service
(
this
.
endpoint
);
this
.
poll
=
new
Poll
({
resource
:
this
.
service
,
method
:
'
getData
'
,
successCallback
:
res
=>
this
.
store
.
updateState
(
res
.
data
),
errorCallback
(
err
)
{
throw
new
Error
(
err
);
},
});
hasUpdated
()
{
return
!!
this
.
state
.
updatedAt
;
},
issueChanged
()
{
const
descriptionChanged
=
this
.
initialDescriptionText
!==
this
.
store
.
formState
.
description
;
const
titleChanged
=
this
.
initialTitleText
!==
this
.
store
.
formState
.
title
;
return
descriptionChanged
||
titleChanged
;
},
},
created
()
{
this
.
service
=
new
Service
(
this
.
endpoint
);
this
.
poll
=
new
Poll
({
resource
:
this
.
service
,
method
:
'
getData
'
,
successCallback
:
res
=>
this
.
store
.
updateState
(
res
.
data
),
errorCallback
(
err
)
{
throw
new
Error
(
err
);
},
});
if
(
!
Visibility
.
hidden
())
{
this
.
poll
.
makeRequest
();
}
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
this
.
poll
.
makeRequest
();
this
.
poll
.
restart
();
}
else
{
this
.
poll
.
stop
();
}
});
Visibility
.
change
(()
=>
{
if
(
!
Visibility
.
hidden
())
{
this
.
poll
.
restart
();
}
else
{
this
.
poll
.
stop
();
}
});
window
.
addEventListener
(
'
beforeunload
'
,
this
.
handleBeforeUnloadEvent
);
window
.
addEventListener
(
'
beforeunload
'
,
this
.
handleBeforeUnloadEvent
);
eventHub
.
$on
(
'
delete.issuable
'
,
this
.
deleteIssuable
);
eventHub
.
$on
(
'
update.issuable
'
,
this
.
updateIssuable
);
eventHub
.
$on
(
'
close.form
'
,
this
.
closeForm
);
eventHub
.
$on
(
'
open.form
'
,
this
.
openForm
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
delete.issuable
'
,
this
.
deleteIssuable
);
eventHub
.
$off
(
'
update.issuable
'
,
this
.
updateIssuable
);
eventHub
.
$off
(
'
close.form
'
,
this
.
closeForm
);
eventHub
.
$off
(
'
open.form
'
,
this
.
openForm
);
window
.
removeEventListener
(
'
beforeunload
'
,
this
.
handleBeforeUnloadEvent
);
},
methods
:
{
handleBeforeUnloadEvent
(
e
)
{
const
event
=
e
;
if
(
this
.
showForm
&&
this
.
issueChanged
)
{
event
.
returnValue
=
'
Are you sure you want to lose your issue information?
'
;
}
return
undefined
;
},
eventHub
.
$on
(
'
delete.issuable
'
,
this
.
deleteIssuable
);
eventHub
.
$on
(
'
update.issuable
'
,
this
.
updateIssuable
);
eventHub
.
$on
(
'
close.form
'
,
this
.
closeForm
);
eventHub
.
$on
(
'
open.form
'
,
this
.
openForm
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
delete.issuable
'
,
this
.
deleteIssuable
);
eventHub
.
$off
(
'
update.issuable
'
,
this
.
updateIssuable
);
eventHub
.
$off
(
'
close.form
'
,
this
.
closeForm
);
eventHub
.
$off
(
'
open.form
'
,
this
.
openForm
);
window
.
removeEventListener
(
'
beforeunload
'
,
this
.
handleBeforeUnloadEvent
);
},
methods
:
{
handleBeforeUnloadEvent
(
e
)
{
const
event
=
e
;
if
(
this
.
showForm
&&
this
.
issueChanged
)
{
event
.
returnValue
=
'
Are you sure you want to lose your issue information?
'
;
}
return
undefined
;
},
openForm
()
{
if
(
!
this
.
showForm
)
{
this
.
showForm
=
true
;
this
.
store
.
setFormState
({
title
:
this
.
state
.
titleText
,
description
:
this
.
state
.
descriptionText
,
lockedWarningVisible
:
false
,
updateLoading
:
false
,
});
}
},
closeForm
()
{
this
.
showForm
=
false
;
},
openForm
()
{
if
(
!
this
.
showForm
)
{
this
.
showForm
=
true
;
this
.
store
.
setFormState
({
title
:
this
.
state
.
titleText
,
description
:
this
.
state
.
descriptionText
,
lockedWarningVisible
:
false
,
updateLoading
:
false
,
});
}
},
closeForm
()
{
this
.
showForm
=
false
;
},
updateIssuable
()
{
return
this
.
service
.
updateIssuable
(
this
.
store
.
formState
)
.
then
(
res
=>
res
.
data
)
.
then
(
data
=>
this
.
checkForSpam
(
data
))
.
then
((
data
)
=>
{
if
(
window
.
location
.
pathname
!==
data
.
web_url
)
{
visitUrl
(
data
.
web_url
);
}
updateIssuable
()
{
return
this
.
service
.
updateIssuable
(
this
.
store
.
formState
)
.
then
(
res
=>
res
.
data
)
.
then
(
data
=>
this
.
checkForSpam
(
data
))
.
then
(
data
=>
{
if
(
window
.
location
.
pathname
!==
data
.
web_url
)
{
visitUrl
(
data
.
web_url
);
}
return
this
.
service
.
getData
();
})
.
then
(
res
=>
res
.
data
)
.
then
((
data
)
=>
{
this
.
store
.
updateState
(
data
);
return
this
.
service
.
getData
();
})
.
then
(
res
=>
res
.
data
)
.
then
(
data
=>
{
this
.
store
.
updateState
(
data
);
eventHub
.
$emit
(
'
close.form
'
);
})
.
catch
(
error
=>
{
if
(
error
&&
error
.
name
===
'
SpamError
'
)
{
this
.
openRecaptcha
();
}
else
{
eventHub
.
$emit
(
'
close.form
'
);
})
.
catch
((
error
)
=>
{
if
(
error
&&
error
.
name
===
'
SpamError
'
)
{
this
.
openRecaptcha
();
}
else
{
eventHub
.
$emit
(
'
close.form
'
);
window
.
Flash
(
`Error updating
${
this
.
issuableType
}
`
);
}
});
},
closeRecaptchaModal
()
{
this
.
store
.
setFormState
({
updateLoading
:
false
,
window
.
Flash
(
`Error updating
${
this
.
issuableType
}
`
);
}
});
},
this
.
closeRecaptcha
();
},
closeRecaptchaModal
()
{
this
.
store
.
setFormState
({
updateLoading
:
false
,
});
deleteIssuable
()
{
this
.
service
.
deleteIssuable
()
.
then
(
res
=>
res
.
data
)
.
then
((
data
)
=>
{
// Stop the poll so we don't get 404's with the issuable not existing
this
.
poll
.
stop
();
this
.
closeRecaptcha
();
},
visitUrl
(
data
.
web_url
);
})
.
catch
(()
=>
{
eventHub
.
$emit
(
'
close.form
'
);
window
.
Flash
(
`Error deleting
${
this
.
issuableType
}
`
);
});
},
deleteIssuable
()
{
this
.
service
.
deleteIssuable
()
.
then
(
res
=>
res
.
data
)
.
then
(
data
=>
{
// Stop the poll so we don't get 404's with the issuable not existing
this
.
poll
.
stop
();
visitUrl
(
data
.
web_url
);
})
.
catch
(()
=>
{
eventHub
.
$emit
(
'
close.form
'
);
window
.
Flash
(
`Error deleting
${
this
.
issuableType
}
`
);
});
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/issue_show/components/description.vue
View file @
9867eaf3
<
script
>
import
$
from
'
jquery
'
;
import
animateMixin
from
'
../mixins/animate
'
;
import
TaskList
from
'
../../task_list
'
;
import
recaptchaModalImplementor
from
'
../../vue_shared/mixins/recaptcha_modal_implementor
'
;
import
$
from
'
jquery
'
;
import
animateMixin
from
'
../mixins/animate
'
;
import
TaskList
from
'
../../task_list
'
;
import
recaptchaModalImplementor
from
'
../../vue_shared/mixins/recaptcha_modal_implementor
'
;
export
default
{
mixins
:
[
animateMixin
,
recaptchaModalImplementor
,
],
export
default
{
mixins
:
[
animateMixin
,
recaptchaModalImplementor
],
props
:
{
canUpdate
:
{
type
:
Boolean
,
required
:
true
,
},
descriptionHtml
:
{
type
:
String
,
required
:
true
,
},
descriptionText
:
{
type
:
String
,
required
:
true
,
},
taskStatus
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
issuableType
:
{
type
:
String
,
required
:
false
,
default
:
'
issue
'
,
},
updateUrl
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
props
:
{
canUpdate
:
{
type
:
Boolean
,
required
:
true
,
},
data
()
{
return
{
preAnimation
:
false
,
pulseAnimation
:
false
,
};
descriptionHtml
:
{
type
:
String
,
required
:
true
,
},
watch
:
{
descriptionHtml
()
{
this
.
animateChange
();
descriptionText
:
{
type
:
String
,
required
:
true
,
},
taskStatus
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
issuableType
:
{
type
:
String
,
required
:
false
,
default
:
'
issue
'
,
},
updateUrl
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
},
data
()
{
return
{
preAnimation
:
false
,
pulseAnimation
:
false
,
};
},
watch
:
{
descriptionHtml
()
{
this
.
animateChange
();
this
.
$nextTick
(()
=>
{
this
.
renderGFM
();
});
},
taskStatus
()
{
this
.
updateTaskStatusText
();
},
this
.
$nextTick
(()
=>
{
this
.
renderGFM
();
});
},
mounted
()
{
this
.
renderGFM
();
taskStatus
()
{
this
.
updateTaskStatusText
();
},
methods
:
{
renderGFM
()
{
$
(
this
.
$refs
[
'
gfm-content
'
]).
renderGFM
();
},
mounted
()
{
this
.
renderGFM
();
this
.
updateTaskStatusText
();
},
methods
:
{
renderGFM
()
{
$
(
this
.
$refs
[
'
gfm-content
'
]).
renderGFM
();
if
(
this
.
canUpdate
)
{
// eslint-disable-next-line no-new
new
TaskList
({
dataType
:
this
.
issuableType
,
fieldName
:
'
description
'
,
selector
:
'
.detail-page-description
'
,
onSuccess
:
this
.
taskListUpdateSuccess
.
bind
(
this
),
});
}
},
if
(
this
.
canUpdate
)
{
// eslint-disable-next-line no-new
new
TaskList
({
dataType
:
this
.
issuableType
,
fieldName
:
'
description
'
,
selector
:
'
.detail-page-description
'
,
onSuccess
:
this
.
taskListUpdateSuccess
.
bind
(
this
),
});
}
},
taskListUpdateSuccess
(
data
)
{
try
{
this
.
checkForSpam
(
data
);
this
.
closeRecaptcha
();
}
catch
(
error
)
{
if
(
error
&&
error
.
name
===
'
SpamError
'
)
this
.
openRecaptcha
();
}
},
taskListUpdateSuccess
(
data
)
{
try
{
this
.
checkForSpam
(
data
);
this
.
closeRecaptcha
();
}
catch
(
error
)
{
if
(
error
&&
error
.
name
===
'
SpamError
'
)
this
.
openRecaptcha
();
}
},
updateTaskStatusText
()
{
const
taskRegexMatches
=
this
.
taskStatus
.
match
(
/
(\d
+
)
of
((?!
0
)\d
+
)
/
);
const
$issuableHeader
=
$
(
'
.issuable-meta
'
);
const
$tasks
=
$
(
'
#task_status
'
,
$issuableHeader
);
const
$tasksShort
=
$
(
'
#task_status_short
'
,
$issuableHeader
);
updateTaskStatusText
()
{
const
taskRegexMatches
=
this
.
taskStatus
.
match
(
/
(\d
+
)
of
((?!
0
)\d
+
)
/
);
const
$issuableHeader
=
$
(
'
.issuable-meta
'
);
const
$tasks
=
$
(
'
#task_status
'
,
$issuableHeader
);
const
$tasksShort
=
$
(
'
#task_status_short
'
,
$issuableHeader
);
if
(
taskRegexMatches
)
{
$tasks
.
text
(
this
.
taskStatus
);
$tasksShort
.
text
(
`
${
taskRegexMatches
[
1
]}
/
${
taskRegexMatches
[
2
]}
task
${
taskRegexMatches
[
2
]
>
1
?
'
s
'
:
''
}
`
,
);
}
else
{
$tasks
.
text
(
''
);
$tasksShort
.
text
(
''
);
}
},
if
(
taskRegexMatches
)
{
$tasks
.
text
(
this
.
taskStatus
);
$tasksShort
.
text
(
`
${
taskRegexMatches
[
1
]}
/
${
taskRegexMatches
[
2
]}
task
${
taskRegexMatches
[
2
]
>
1
?
'
s
'
:
''
}
`
,
);
}
else
{
$tasks
.
text
(
''
);
$tasksShort
.
text
(
''
);
}
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/issue_show/components/edit_actions.vue
View file @
9867eaf3
<
script
>
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
updateMixin
from
'
../mixins/update
'
;
import
eventHub
from
'
../event_hub
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
updateMixin
from
'
../mixins/update
'
;
import
eventHub
from
'
../event_hub
'
;
const
issuableTypes
=
{
issue
:
__
(
'
Issue
'
),
epic
:
__
(
'
Epic
'
),
};
const
issuableTypes
=
{
issue
:
__
(
'
Issue
'
),
epic
:
__
(
'
Epic
'
),
};
export
default
{
mixins
:
[
updateMixin
],
props
:
{
canDestroy
:
{
type
:
Boolean
,
required
:
true
,
},
formState
:
{
type
:
Object
,
required
:
true
,
},
showDeleteButton
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
issuableType
:
{
type
:
String
,
required
:
true
,
},
export
default
{
mixins
:
[
updateMixin
],
props
:
{
canDestroy
:
{
type
:
Boolean
,
required
:
true
,
},
data
()
{
return
{
deleteLoading
:
false
,
};
formState
:
{
type
:
Object
,
required
:
true
,
},
computed
:
{
isSubmitEnabled
()
{
return
this
.
formState
.
title
.
trim
()
!==
''
;
},
shouldShowDeleteButton
()
{
return
this
.
canDestroy
&&
this
.
showDeleteButton
;
},
showDeleteButton
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
methods
:
{
closeForm
()
{
eventHub
.
$emit
(
'
close.form
'
);
},
deleteIssuable
()
{
const
confirmMessage
=
sprintf
(
__
(
'
%{issuableType} will be removed! Are you sure?
'
),
{
issuableType
:
issuableTypes
[
this
.
issuableType
],
});
// eslint-disable-next-line no-alert
if
(
window
.
confirm
(
confirmMessage
))
{
this
.
deleteLoading
=
true
;
issuableType
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
deleteLoading
:
false
,
};
},
computed
:
{
isSubmitEnabled
()
{
return
this
.
formState
.
title
.
trim
()
!==
''
;
},
shouldShowDeleteButton
()
{
return
this
.
canDestroy
&&
this
.
showDeleteButton
;
},
},
methods
:
{
closeForm
()
{
eventHub
.
$emit
(
'
close.form
'
);
},
deleteIssuable
()
{
const
confirmMessage
=
sprintf
(
__
(
'
%{issuableType} will be removed! Are you sure?
'
),
{
issuableType
:
issuableTypes
[
this
.
issuableType
],
});
// eslint-disable-next-line no-alert
if
(
window
.
confirm
(
confirmMessage
))
{
this
.
deleteLoading
=
true
;
eventHub
.
$emit
(
'
delete.issuable
'
);
}
},
eventHub
.
$emit
(
'
delete.issuable
'
);
}
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/issue_show/components/edited.vue
View file @
9867eaf3
<
script
>
import
timeAgoTooltip
from
'
../../vue_shared/components/time_ago_tooltip.vue
'
;
import
timeAgoTooltip
from
'
../../vue_shared/components/time_ago_tooltip.vue
'
;
export
default
{
components
:
{
timeAgoTooltip
,
export
default
{
components
:
{
timeAgoTooltip
,
},
props
:
{
updatedAt
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
props
:
{
updatedAt
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedByName
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedByPath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
updatedByName
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
computed
:
{
hasUpdatedBy
()
{
return
this
.
updatedByName
&&
this
.
updatedByPath
;
}
,
updatedByPath
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
};
},
computed
:
{
hasUpdatedBy
()
{
return
this
.
updatedByName
&&
this
.
updatedByPath
;
},
},
};
</
script
>
<
template
>
...
...
@@ -53,4 +53,3 @@
</span>
</small>
</
template
>
app/assets/javascripts/issue_show/components/fields/description.vue
View file @
9867eaf3
<
script
>
import
updateMixin
from
'
../../mixins/update
'
;
import
markdownField
from
'
../../../vue_shared/components/markdown/field.vue
'
;
import
updateMixin
from
'
../../mixins/update
'
;
import
markdownField
from
'
../../../vue_shared/components/markdown/field.vue
'
;
export
default
{
components
:
{
markdownField
,
export
default
{
components
:
{
markdownField
,
},
mixins
:
[
updateMixin
],
props
:
{
formState
:
{
type
:
Object
,
required
:
true
,
},
mixins
:
[
updateMixin
],
props
:
{
formState
:
{
type
:
Object
,
required
:
true
,
},
markdownPreviewPath
:
{
type
:
String
,
required
:
true
,
},
markdownDocsPath
:
{
type
:
String
,
required
:
true
,
},
markdownVersion
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
canAttachFile
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
enableAutocomplete
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
markdownPreviewPath
:
{
type
:
String
,
required
:
true
,
},
mounted
()
{
this
.
$refs
.
textarea
.
focus
();
markdownDocsPath
:
{
type
:
String
,
required
:
true
,
},
};
markdownVersion
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
canAttachFile
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
enableAutocomplete
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
},
mounted
()
{
this
.
$refs
.
textarea
.
focus
();
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/issue_show/components/fields/description_template.vue
View file @
9867eaf3
<
script
>
import
$
from
'
jquery
'
;
import
IssuableTemplateSelectors
from
'
../../../templates/issuable_template_selectors
'
;
import
$
from
'
jquery
'
;
import
IssuableTemplateSelectors
from
'
../../../templates/issuable_template_selectors
'
;
export
default
{
props
:
{
formState
:
{
type
:
Object
,
required
:
true
,
},
issuableTemplates
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
projectPath
:
{
type
:
String
,
required
:
true
,
},
projectNamespace
:
{
type
:
String
,
required
:
true
,
},
export
default
{
props
:
{
formState
:
{
type
:
Object
,
required
:
true
,
},
computed
:
{
issuableTemplatesJson
()
{
return
JSON
.
stringify
(
this
.
issuableTemplates
);
}
,
issuableTemplates
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[]
,
},
mounted
()
{
// Create the editor for the template
const
editor
=
document
.
querySelector
(
'
.detail-page-description .note-textarea
'
)
||
{};
editor
.
setValue
=
(
val
)
=>
{
this
.
formState
.
description
=
val
;
};
editor
.
getValue
=
()
=>
this
.
formState
.
description
;
this
.
issuableTemplate
=
new
IssuableTemplateSelectors
({
$dropdowns
:
$
(
this
.
$refs
.
toggle
),
editor
,
});
projectPath
:
{
type
:
String
,
required
:
true
,
},
projectNamespace
:
{
type
:
String
,
required
:
true
,
},
};
},
computed
:
{
issuableTemplatesJson
()
{
return
JSON
.
stringify
(
this
.
issuableTemplates
);
},
},
mounted
()
{
// Create the editor for the template
const
editor
=
document
.
querySelector
(
'
.detail-page-description .note-textarea
'
)
||
{};
editor
.
setValue
=
val
=>
{
this
.
formState
.
description
=
val
;
};
editor
.
getValue
=
()
=>
this
.
formState
.
description
;
this
.
issuableTemplate
=
new
IssuableTemplateSelectors
({
$dropdowns
:
$
(
this
.
$refs
.
toggle
),
editor
,
});
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/issue_show/components/fields/title.vue
View file @
9867eaf3
<
script
>
import
updateMixin
from
'
../../mixins/update
'
;
import
updateMixin
from
'
../../mixins/update
'
;
export
default
{
mixins
:
[
updateMixin
],
props
:
{
formState
:
{
type
:
Object
,
required
:
true
,
},
export
default
{
mixins
:
[
updateMixin
],
props
:
{
formState
:
{
type
:
Object
,
required
:
true
,
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/issue_show/components/form.vue
View file @
9867eaf3
<
script
>
import
lockedWarning
from
'
./locked_warning.vue
'
;
import
titleField
from
'
./fields/title.vue
'
;
import
descriptionField
from
'
./fields/description.vue
'
;
import
editActions
from
'
./edit_actions.vue
'
;
import
descriptionTemplate
from
'
./fields/description_template.vue
'
;
import
lockedWarning
from
'
./locked_warning.vue
'
;
import
titleField
from
'
./fields/title.vue
'
;
import
descriptionField
from
'
./fields/description.vue
'
;
import
editActions
from
'
./edit_actions.vue
'
;
import
descriptionTemplate
from
'
./fields/description_template.vue
'
;
export
default
{
components
:
{
lockedWarning
,
titleField
,
descriptionField
,
descriptionTemplate
,
editActions
,
export
default
{
components
:
{
lockedWarning
,
titleField
,
descriptionField
,
descriptionTemplate
,
editActions
,
},
props
:
{
canDestroy
:
{
type
:
Boolean
,
required
:
true
,
},
props
:
{
canDestroy
:
{
type
:
Boolean
,
required
:
true
,
},
formState
:
{
type
:
Object
,
required
:
true
,
},
issuableTemplates
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
issuableType
:
{
type
:
String
,
required
:
true
,
},
markdownPreviewPath
:
{
type
:
String
,
required
:
true
,
},
markdownDocsPath
:
{
type
:
String
,
required
:
true
,
},
markdownVersion
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
projectPath
:
{
type
:
String
,
required
:
true
,
},
projectNamespace
:
{
type
:
String
,
required
:
true
,
},
showDeleteButton
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
canAttachFile
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
enableAutocomplete
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
formState
:
{
type
:
Object
,
required
:
true
,
},
computed
:
{
hasIssuableTemplates
()
{
return
this
.
issuableTemplates
.
length
;
}
,
issuableTemplates
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[]
,
},
};
issuableType
:
{
type
:
String
,
required
:
true
,
},
markdownPreviewPath
:
{
type
:
String
,
required
:
true
,
},
markdownDocsPath
:
{
type
:
String
,
required
:
true
,
},
markdownVersion
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
projectPath
:
{
type
:
String
,
required
:
true
,
},
projectNamespace
:
{
type
:
String
,
required
:
true
,
},
showDeleteButton
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
canAttachFile
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
enableAutocomplete
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
},
computed
:
{
hasIssuableTemplates
()
{
return
this
.
issuableTemplates
.
length
;
},
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/issue_show/components/locked_warning.vue
View file @
9867eaf3
<
script
>
export
default
{
computed
:
{
currentPath
()
{
return
window
.
location
.
pathname
;
},
export
default
{
computed
:
{
currentPath
()
{
return
window
.
location
.
pathname
;
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/issue_show/stores/index.js
View file @
9867eaf3
...
...
@@ -25,8 +25,10 @@ export default class Store {
}
stateShouldUpdate
(
data
)
{
return
this
.
state
.
titleText
!==
data
.
title_text
||
this
.
state
.
descriptionText
!==
data
.
description_text
;
return
(
this
.
state
.
titleText
!==
data
.
title_text
||
this
.
state
.
descriptionText
!==
data
.
description_text
);
}
setFormState
(
state
)
{
...
...
app/assets/javascripts/jobs/components/artifacts_block.vue
View file @
9867eaf3
<
script
>
import
TimeagoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
TimeagoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
export
default
{
components
:
{
TimeagoTooltip
,
export
default
{
components
:
{
TimeagoTooltip
,
},
mixins
:
[
timeagoMixin
],
props
:
{
artifact
:
{
type
:
Object
,
required
:
true
,
},
mixins
:
[
timeagoMixin
,
],
props
:
{
artifact
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
isExpired
()
{
return
this
.
artifact
.
expired
;
},
computed
:
{
isExpired
()
{
return
this
.
artifact
.
expired
;
},
// Only when the key is `false` we can render this block
willExpire
()
{
return
this
.
artifact
.
expired
===
false
;
},
// Only when the key is `false` we can render this block
willExpire
()
{
return
this
.
artifact
.
expired
===
false
;
},
};
},
};
</
script
>
<
template
>
<div
class=
"block"
>
...
...
app/assets/javascripts/jobs/components/commit_block.vue
View file @
9867eaf3
<
script
>
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
export
default
{
components
:
{
ClipboardButton
,
export
default
{
components
:
{
ClipboardButton
,
},
props
:
{
commit
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
commit
:
{
type
:
Object
,
required
:
true
,
},
mergeRequest
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
isLastBlock
:
{
type
:
Boolean
,
required
:
true
,
},
mergeRequest
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
},
};
isLastBlock
:
{
type
:
Boolean
,
required
:
true
,
},
},
};
</
script
>
<
template
>
<div
...
...
app/assets/javascripts/jobs/components/empty_state.vue
View file @
9867eaf3
<
script
>
export
default
{
props
:
{
illustrationPath
:
{
type
:
String
,
required
:
true
,
},
illustrationSizeClass
:
{
type
:
String
,
required
:
true
,
},
title
:
{
type
:
String
,
required
:
true
,
},
content
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
action
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
validator
(
value
)
{
return
(
value
===
null
||
(
Object
.
prototype
.
hasOwnProperty
.
call
(
value
,
'
path
'
)
&&
Object
.
prototype
.
hasOwnProperty
.
call
(
value
,
'
method
'
)
&&
Object
.
prototype
.
hasOwnProperty
.
call
(
value
,
'
button_title
'
))
);
},
export
default
{
props
:
{
illustrationPath
:
{
type
:
String
,
required
:
true
,
},
illustrationSizeClass
:
{
type
:
String
,
required
:
true
,
},
title
:
{
type
:
String
,
required
:
true
,
},
content
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
action
:
{
type
:
Object
,
required
:
false
,
default
:
null
,
validator
(
value
)
{
return
(
value
===
null
||
(
Object
.
prototype
.
hasOwnProperty
.
call
(
value
,
'
path
'
)
&&
Object
.
prototype
.
hasOwnProperty
.
call
(
value
,
'
method
'
)
&&
Object
.
prototype
.
hasOwnProperty
.
call
(
value
,
'
button_title
'
))
);
},
},
};
},
};
</
script
>
<
template
>
<div
class=
"row empty-state"
>
...
...
app/assets/javascripts/jobs/components/environments_block.vue
View file @
9867eaf3
<
script
>
import
_
from
'
underscore
'
;
import
CiIcon
from
'
~/vue_shared/components/ci_icon.vue
'
;
import
{
sprintf
,
__
}
from
'
../../locale
'
;
import
_
from
'
underscore
'
;
import
CiIcon
from
'
~/vue_shared/components/ci_icon.vue
'
;
import
{
sprintf
,
__
}
from
'
../../locale
'
;
export
default
{
components
:
{
CiIcon
,
export
default
{
components
:
{
CiIcon
,
},
props
:
{
deploymentStatus
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
deploymentStatus
:
{
type
:
Object
,
required
:
true
,
},
iconStatus
:
{
type
:
Object
,
required
:
true
,
},
iconStatus
:
{
type
:
Object
,
required
:
true
,
},
computed
:
{
environment
()
{
let
environmentText
;
switch
(
this
.
deploymentStatus
.
status
)
{
case
'
last
'
:
},
computed
:
{
environment
()
{
let
environmentText
;
switch
(
this
.
deploymentStatus
.
status
)
{
case
'
last
'
:
environmentText
=
sprintf
(
__
(
'
This job is the most recent deployment to %{link}.
'
),
{
link
:
this
.
environmentLink
},
false
,
);
break
;
case
'
out_of_date
'
:
if
(
this
.
hasLastDeployment
)
{
environmentText
=
sprintf
(
__
(
'
This job is the most recent deployment to %{link}.
'
),
{
link
:
this
.
environmentLink
},
__
(
'
This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.
'
,
),
{
environmentLink
:
this
.
environmentLink
,
deploymentLink
:
this
.
deploymentLink
(
`#
${
this
.
lastDeployment
.
iid
}
`
),
},
false
,
);
break
;
case
'
out_of_date
'
:
if
(
this
.
hasLastDeployment
)
{
environmentText
=
sprintf
(
__
(
'
This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.
'
,
),
{
environmentLink
:
this
.
environmentLink
,
deploymentLink
:
this
.
deploymentLink
(
`#
${
this
.
lastDeployment
.
iid
}
`
),
},
false
,
);
}
else
{
environmentText
=
sprintf
(
__
(
'
This job is an out-of-date deployment to %{environmentLink}.
'
),
{
environmentLink
:
this
.
environmentLink
},
false
,
);
}
break
;
case
'
failed
'
:
}
else
{
environmentText
=
sprintf
(
__
(
'
Th
e deployment of this job to %{environmentLink} did not succeed
.
'
),
__
(
'
Th
is job is an out-of-date deployment to %{environmentLink}
.
'
),
{
environmentLink
:
this
.
environmentLink
},
false
,
);
break
;
case
'
creating
'
:
if
(
this
.
hasLastDeployment
)
{
environmentText
=
sprintf
(
__
(
'
This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.
'
,
),
{
environmentLink
:
this
.
environmentLink
,
deploymentLink
:
this
.
deploymentLink
(
__
(
'
latest deployment
'
)),
},
false
,
);
}
else
{
environmentText
=
sprintf
(
__
(
'
This job is creating a deployment to %{environmentLink}.
'
),
{
environmentLink
:
this
.
environmentLink
},
false
,
);
}
break
;
default
:
break
;
}
return
environmentText
;
},
environmentLink
()
{
if
(
this
.
hasEnvironment
)
{
return
sprintf
(
'
%{startLink}%{name}%{endLink}
'
,
{
startLink
:
`<a href="
${
this
.
deploymentStatus
.
environment
.
environment_path
}
" class="js-environment-link">`
,
name
:
_
.
escape
(
this
.
deploymentStatus
.
environment
.
name
),
endLink
:
'
</a>
'
,
},
}
break
;
case
'
failed
'
:
environmentText
=
sprintf
(
__
(
'
The deployment of this job to %{environmentLink} did not succeed.
'
),
{
environmentLink
:
this
.
environmentLink
},
false
,
);
}
return
''
;
},
hasLastDeployment
()
{
return
this
.
hasEnvironment
&&
this
.
deploymentStatus
.
environment
.
last_deployment
;
},
lastDeployment
()
{
return
this
.
hasLastDeployment
?
this
.
deploymentStatus
.
environment
.
last_deployment
:
{};
},
hasEnvironment
()
{
return
!
_
.
isEmpty
(
this
.
deploymentStatus
.
environment
);
},
lastDeploymentPath
()
{
return
!
_
.
isEmpty
(
this
.
lastDeployment
.
deployable
)
?
this
.
lastDeployment
.
deployable
.
build_path
:
''
;
},
break
;
case
'
creating
'
:
if
(
this
.
hasLastDeployment
)
{
environmentText
=
sprintf
(
__
(
'
This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.
'
,
),
{
environmentLink
:
this
.
environmentLink
,
deploymentLink
:
this
.
deploymentLink
(
__
(
'
latest deployment
'
)),
},
false
,
);
}
else
{
environmentText
=
sprintf
(
__
(
'
This job is creating a deployment to %{environmentLink}.
'
),
{
environmentLink
:
this
.
environmentLink
},
false
,
);
}
break
;
default
:
break
;
}
return
environmentText
;
},
methods
:
{
deploymentLink
(
name
)
{
environmentLink
()
{
if
(
this
.
hasEnvironment
)
{
return
sprintf
(
'
%{startLink}%{name}%{endLink}
'
,
{
startLink
:
`<a href="
${
this
.
lastDeploymentPath
}
" class="js-job-deployment-link">`
,
name
,
startLink
:
`<a href="
${
this
.
deploymentStatus
.
environment
.
environment_path
}
" class="js-environment-link">`
,
name
:
_
.
escape
(
this
.
deploymentStatus
.
environment
.
name
),
endLink
:
'
</a>
'
,
},
false
,
);
},
}
return
''
;
},
hasLastDeployment
()
{
return
this
.
hasEnvironment
&&
this
.
deploymentStatus
.
environment
.
last_deployment
;
},
lastDeployment
()
{
return
this
.
hasLastDeployment
?
this
.
deploymentStatus
.
environment
.
last_deployment
:
{};
},
hasEnvironment
()
{
return
!
_
.
isEmpty
(
this
.
deploymentStatus
.
environment
);
},
lastDeploymentPath
()
{
return
!
_
.
isEmpty
(
this
.
lastDeployment
.
deployable
)
?
this
.
lastDeployment
.
deployable
.
build_path
:
''
;
},
},
methods
:
{
deploymentLink
(
name
)
{
return
sprintf
(
'
%{startLink}%{name}%{endLink}
'
,
{
startLink
:
`<a href="
${
this
.
lastDeploymentPath
}
" class="js-job-deployment-link">`
,
name
,
endLink
:
'
</a>
'
,
},
false
,
);
},
};
},
};
</
script
>
<
template
>
<div
class=
"prepend-top-default js-environment-container"
>
...
...
app/assets/javascripts/jobs/components/erased_block.vue
View file @
9867eaf3
<
script
>
import
_
from
'
underscore
'
;
import
TimeagoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
_
from
'
underscore
'
;
import
TimeagoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
export
default
{
components
:
{
TimeagoTooltip
,
export
default
{
components
:
{
TimeagoTooltip
,
},
props
:
{
user
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
props
:
{
user
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
erasedAt
:
{
type
:
String
,
required
:
true
,
},
erasedAt
:
{
type
:
String
,
required
:
true
,
},
computed
:
{
isErasedByUser
()
{
return
!
_
.
isEmpty
(
this
.
user
);
},
},
computed
:
{
isErasedByUser
()
{
return
!
_
.
isEmpty
(
this
.
user
);
},
};
},
};
</
script
>
<
template
>
<div
class=
"prepend-top-default js-build-erased"
>
...
...
app/assets/javascripts/jobs/components/job_log.vue
View file @
9867eaf3
<
script
>
export
default
{
name
:
'
JobLog
'
,
props
:
{
trace
:
{
type
:
String
,
required
:
true
,
},
isComplete
:
{
type
:
Boolean
,
required
:
true
,
},
export
default
{
name
:
'
JobLog
'
,
props
:
{
trace
:
{
type
:
String
,
required
:
true
,
},
};
isComplete
:
{
type
:
Boolean
,
required
:
true
,
},
},
};
</
script
>
<
template
>
<pre
class=
"build-trace"
>
...
...
app/assets/javascripts/jobs/components/job_log_controllers.vue
View file @
9867eaf3
<
script
>
import
{
polyfillSticky
}
from
'
~/lib/utils/sticky
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
{
numberToHumanSize
}
from
'
~/lib/utils/number_utils
'
;
import
{
sprintf
}
from
'
~/locale
'
;
import
{
polyfillSticky
}
from
'
~/lib/utils/sticky
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
{
numberToHumanSize
}
from
'
~/lib/utils/number_utils
'
;
import
{
sprintf
}
from
'
~/locale
'
;
export
default
{
components
:
{
Icon
,
export
default
{
components
:
{
Icon
,
},
directives
:
{
tooltip
,
},
props
:
{
erasePath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
directives
:
{
tooltip
,
size
:
{
type
:
Number
,
required
:
true
,
},
props
:
{
erasePath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
size
:
{
type
:
Number
,
required
:
true
,
},
rawPath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
isScrollTopDisabled
:
{
type
:
Boolean
,
required
:
true
,
},
isScrollBottomDisabled
:
{
type
:
Boolean
,
required
:
true
,
},
isScrollingDown
:
{
type
:
Boolean
,
required
:
true
,
},
isTraceSizeVisible
:
{
type
:
Boolean
,
required
:
true
,
},
rawPath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
computed
:
{
jobLogSize
()
{
return
sprintf
(
'
Showing last %{size} of log -
'
,
{
size
:
numberToHumanSize
(
this
.
size
),
});
},
isScrollTopDisabled
:
{
type
:
Boolean
,
required
:
true
,
},
mounted
()
{
polyfillSticky
(
this
.
$el
);
isScrollBottomDisabled
:
{
type
:
Boolean
,
required
:
true
,
},
methods
:
{
handleScrollToTop
()
{
this
.
$emit
(
'
scrollJobLogTop
'
);
},
handleScrollToBottom
()
{
this
.
$emit
(
'
scrollJobLogBottom
'
);
},
isScrollingDown
:
{
type
:
Boolean
,
required
:
true
,
},
};
isTraceSizeVisible
:
{
type
:
Boolean
,
required
:
true
,
},
},
computed
:
{
jobLogSize
()
{
return
sprintf
(
'
Showing last %{size} of log -
'
,
{
size
:
numberToHumanSize
(
this
.
size
),
});
},
},
mounted
()
{
polyfillSticky
(
this
.
$el
);
},
methods
:
{
handleScrollToTop
()
{
this
.
$emit
(
'
scrollJobLogTop
'
);
},
handleScrollToBottom
()
{
this
.
$emit
(
'
scrollJobLogBottom
'
);
},
},
};
</
script
>
<
template
>
<div
class=
"top-bar"
>
...
...
app/assets/javascripts/jobs/components/jobs_container.vue
View file @
9867eaf3
<
script
>
import
_
from
'
underscore
'
;
import
CiIcon
from
'
~/vue_shared/components/ci_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
_
from
'
underscore
'
;
import
CiIcon
from
'
~/vue_shared/components/ci_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
export
default
{
components
:
{
CiIcon
,
Icon
,
export
default
{
components
:
{
CiIcon
,
Icon
,
},
directives
:
{
tooltip
,
},
props
:
{
jobs
:
{
type
:
Array
,
required
:
true
,
},
directives
:
{
tooltip
,
jobId
:
{
type
:
Number
,
required
:
true
,
},
props
:
{
jobs
:
{
type
:
Array
,
required
:
true
,
},
jobId
:
{
type
:
Number
,
required
:
true
,
},
},
methods
:
{
isJobActive
(
currentJobId
)
{
return
this
.
jobId
===
currentJobId
;
},
methods
:
{
isJobActive
(
currentJobId
)
{
return
this
.
jobId
===
currentJobId
;
},
tooltipText
(
job
)
{
return
`
${
_
.
escape
(
job
.
name
)}
-
${
job
.
status
.
tooltip
}
`
;
},
tooltipText
(
job
)
{
return
`
${
_
.
escape
(
job
.
name
)}
-
${
job
.
status
.
tooltip
}
`
;
},
};
},
};
</
script
>
<
template
>
<div
class=
"js-jobs-container builds-container"
>
...
...
app/assets/javascripts/jobs/components/sidebar.vue
View file @
9867eaf3
<
script
>
import
_
from
'
underscore
'
;
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
{
timeIntervalInWords
}
from
'
~/lib/utils/datetime_utility
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
DetailRow
from
'
./sidebar_detail_row.vue
'
;
import
ArtifactsBlock
from
'
./artifacts_block.vue
'
;
import
TriggerBlock
from
'
./trigger_block.vue
'
;
import
CommitBlock
from
'
./commit_block.vue
'
;
import
StagesDropdown
from
'
./stages_dropdown.vue
'
;
import
JobsContainer
from
'
./jobs_container.vue
'
;
import
_
from
'
underscore
'
;
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
{
timeIntervalInWords
}
from
'
~/lib/utils/datetime_utility
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
DetailRow
from
'
./sidebar_detail_row.vue
'
;
import
ArtifactsBlock
from
'
./artifacts_block.vue
'
;
import
TriggerBlock
from
'
./trigger_block.vue
'
;
import
CommitBlock
from
'
./commit_block.vue
'
;
import
StagesDropdown
from
'
./stages_dropdown.vue
'
;
import
JobsContainer
from
'
./jobs_container.vue
'
;
export
default
{
name
:
'
JobSidebar
'
,
components
:
{
ArtifactsBlock
,
CommitBlock
,
DetailRow
,
Icon
,
TriggerBlock
,
StagesDropdown
,
JobsContainer
,
export
default
{
name
:
'
JobSidebar
'
,
components
:
{
ArtifactsBlock
,
CommitBlock
,
DetailRow
,
Icon
,
TriggerBlock
,
StagesDropdown
,
JobsContainer
,
},
mixins
:
[
timeagoMixin
],
props
:
{
runnerHelpUrl
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
mixins
:
[
timeagoMixin
],
props
:
{
runnerHelpUrl
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
terminalPath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
terminalPath
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
computed
:
{
...
mapState
([
'
job
'
,
'
isLoading
'
,
'
stages
'
,
'
jobs
'
]),
coverage
()
{
return
`
${
this
.
job
.
coverage
}
%`
;
},
duration
()
{
return
timeIntervalInWords
(
this
.
job
.
duration
);
},
queued
()
{
return
timeIntervalInWords
(
this
.
job
.
queued
);
},
runnerId
()
{
return
`
${
this
.
job
.
runner
.
description
}
(#
${
this
.
job
.
runner
.
id
}
)`
;
},
retryButtonClass
()
{
let
className
=
'
js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block
'
;
className
+=
this
.
job
.
status
&&
this
.
job
.
recoverable
?
'
btn-primary
'
:
'
btn-inverted-secondary
'
;
return
className
;
},
hasTimeout
()
{
return
this
.
job
.
metadata
!=
null
&&
this
.
job
.
metadata
.
timeout_human_readable
!==
null
;
},
timeout
()
{
if
(
this
.
job
.
metadata
==
null
)
{
return
''
;
}
},
computed
:
{
...
mapState
([
'
job
'
,
'
isLoading
'
,
'
stages
'
,
'
jobs
'
]),
coverage
()
{
return
`
${
this
.
job
.
coverage
}
%`
;
},
duration
()
{
return
timeIntervalInWords
(
this
.
job
.
duration
);
},
queued
()
{
return
timeIntervalInWords
(
this
.
job
.
queued
);
},
runnerId
()
{
return
`
${
this
.
job
.
runner
.
description
}
(#
${
this
.
job
.
runner
.
id
}
)`
;
},
retryButtonClass
()
{
let
className
=
'
js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block
'
;
className
+=
this
.
job
.
status
&&
this
.
job
.
recoverable
?
'
btn-primary
'
:
'
btn-inverted-secondary
'
;
return
className
;
},
hasTimeout
()
{
return
this
.
job
.
metadata
!=
null
&&
this
.
job
.
metadata
.
timeout_human_readable
!==
null
;
},
timeout
()
{
if
(
this
.
job
.
metadata
==
null
)
{
return
''
;
}
let
t
=
this
.
job
.
metadata
.
timeout_human_readable
;
if
(
this
.
job
.
metadata
.
timeout_source
!==
''
)
{
t
+=
` (from
${
this
.
job
.
metadata
.
timeout_source
}
)`
;
}
let
t
=
this
.
job
.
metadata
.
timeout_human_readable
;
if
(
this
.
job
.
metadata
.
timeout_source
!==
''
)
{
t
+=
` (from
${
this
.
job
.
metadata
.
timeout_source
}
)`
;
}
return
t
;
},
renderBlock
()
{
return
(
this
.
job
.
merge_request
||
this
.
job
.
duration
||
this
.
job
.
finished_data
||
this
.
job
.
erased_at
||
this
.
job
.
queued
||
this
.
job
.
runner
||
this
.
job
.
coverage
||
this
.
job
.
tags
.
length
||
this
.
job
.
cancel_path
);
},
hasArtifact
()
{
return
!
_
.
isEmpty
(
this
.
job
.
artifact
);
},
hasTriggers
()
{
return
!
_
.
isEmpty
(
this
.
job
.
trigger
);
},
hasStages
()
{
return
(
(
this
.
job
&&
this
.
job
.
pipeline
&&
this
.
job
.
pipeline
.
stages
&&
this
.
job
.
pipeline
.
stages
.
length
>
0
)
||
false
);
},
commit
()
{
return
this
.
job
.
pipeline
.
commit
||
{};
},
return
t
;
},
renderBlock
()
{
return
(
this
.
job
.
merge_request
||
this
.
job
.
duration
||
this
.
job
.
finished_data
||
this
.
job
.
erased_at
||
this
.
job
.
queued
||
this
.
job
.
runner
||
this
.
job
.
coverage
||
this
.
job
.
tags
.
length
||
this
.
job
.
cancel_path
);
},
hasArtifact
()
{
return
!
_
.
isEmpty
(
this
.
job
.
artifact
);
},
hasTriggers
()
{
return
!
_
.
isEmpty
(
this
.
job
.
trigger
);
},
hasStages
()
{
return
(
(
this
.
job
&&
this
.
job
.
pipeline
&&
this
.
job
.
pipeline
.
stages
&&
this
.
job
.
pipeline
.
stages
.
length
>
0
)
||
false
);
},
methods
:
{
...
mapActions
([
'
fetchJobsForStage
'
]),
commit
()
{
return
this
.
job
.
pipeline
.
commit
||
{};
},
};
},
methods
:
{
...
mapActions
([
'
fetchJobsForStage
'
]),
},
};
</
script
>
<
template
>
<aside
...
...
app/assets/javascripts/jobs/components/sidebar_detail_row.vue
View file @
9867eaf3
<
script
>
export
default
{
name
:
'
SidebarDetailRow
'
,
props
:
{
title
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
value
:
{
type
:
String
,
required
:
true
,
},
helpUrl
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
export
default
{
name
:
'
SidebarDetailRow
'
,
props
:
{
title
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
computed
:
{
hasTitle
()
{
return
this
.
title
.
length
>
0
;
},
hasHelpURL
()
{
return
this
.
helpUrl
.
length
>
0
;
},
value
:
{
type
:
String
,
required
:
true
,
},
};
helpUrl
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
hasTitle
()
{
return
this
.
title
.
length
>
0
;
},
hasHelpURL
()
{
return
this
.
helpUrl
.
length
>
0
;
},
},
};
</
script
>
<
template
>
<p
class=
"build-detail-row"
>
...
...
app/assets/javascripts/jobs/components/stages_dropdown.vue
View file @
9867eaf3
<
script
>
import
_
from
'
underscore
'
;
import
CiIcon
from
'
~/vue_shared/components/ci_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
{
__
}
from
'
~/locale
'
;
import
_
from
'
underscore
'
;
import
CiIcon
from
'
~/vue_shared/components/ci_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
{
__
}
from
'
~/locale
'
;
export
default
{
components
:
{
CiIcon
,
Icon
,
export
default
{
components
:
{
CiIcon
,
Icon
,
},
props
:
{
pipeline
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
pipeline
:
{
type
:
Object
,
required
:
true
,
},
stages
:
{
type
:
Array
,
required
:
true
,
},
stages
:
{
type
:
Array
,
required
:
true
,
},
data
()
{
return
{
selectedStage
:
this
.
stages
.
length
>
0
?
this
.
stages
[
0
].
name
:
__
(
'
More
'
),
};
},
data
()
{
return
{
selectedStage
:
this
.
stages
.
length
>
0
?
this
.
stages
[
0
].
name
:
__
(
'
More
'
),
};
},
computed
:
{
hasRef
()
{
return
!
_
.
isEmpty
(
this
.
pipeline
.
ref
);
},
computed
:
{
hasRef
()
{
return
!
_
.
isEmpty
(
this
.
pipeline
.
ref
);
},
},
watch
:
{
// When the component is initially mounted it may start with an empty stages array.
// Once the prop is updated, we set the first stage as the selected one
stages
(
newVal
)
{
if
(
newVal
.
length
)
{
this
.
selectedStage
=
newVal
[
0
].
name
;
}
},
watch
:
{
// When the component is initially mounted it may start with an empty stages array.
// Once the prop is updated, we set the first stage as the selected one
stages
(
newVal
)
{
if
(
newVal
.
length
)
{
this
.
selectedStage
=
newVal
[
0
].
name
;
}
},
},
methods
:
{
onStageClick
(
stage
)
{
this
.
$emit
(
'
requestSidebarStageDropdown
'
,
stage
);
this
.
selectedStage
=
stage
.
name
;
},
methods
:
{
onStageClick
(
stage
)
{
this
.
$emit
(
'
requestSidebarStageDropdown
'
,
stage
);
this
.
selectedStage
=
stage
.
name
;
},
},
};
},
};
</
script
>
<
template
>
<div
class=
"block-last dropdown"
>
...
...
app/assets/javascripts/jobs/components/trigger_block.vue
View file @
9867eaf3
<
script
>
export
default
{
props
:
{
trigger
:
{
type
:
Object
,
required
:
true
,
},
export
default
{
props
:
{
trigger
:
{
type
:
Object
,
required
:
true
,
},
data
()
{
return
{
areVariablesVisible
:
false
,
};
},
data
()
{
return
{
areVariablesVisible
:
false
,
};
},
computed
:
{
hasVariables
()
{
return
this
.
trigger
.
variables
&&
this
.
trigger
.
variables
.
length
>
0
;
},
computed
:
{
hasVariables
()
{
return
this
.
trigger
.
variables
&&
this
.
trigger
.
variables
.
length
>
0
;
},
},
methods
:
{
revealVariables
()
{
this
.
areVariablesVisible
=
true
;
},
methods
:
{
revealVariables
()
{
this
.
areVariablesVisible
=
true
;
},
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/jobs/store/index.js
View file @
9867eaf3
...
...
@@ -7,9 +7,10 @@ import mutations from './mutations';
Vue
.
use
(
Vuex
);
export
default
()
=>
new
Vuex
.
Store
({
actions
,
mutations
,
getters
,
state
:
state
(),
});
export
default
()
=>
new
Vuex
.
Store
({
actions
,
mutations
,
getters
,
state
:
state
(),
});
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