2020-04-21 15:21:10 +00:00
< script >
2020-05-04 06:10:10 +00:00
import {
GlLoadingIcon ,
GlTable ,
GlAlert ,
2020-05-13 18:08:47 +00:00
GlIcon ,
2020-06-18 00:08:35 +00:00
GlLink ,
2020-05-07 15:09:29 +00:00
GlTabs ,
GlTab ,
2020-05-29 21:08:35 +00:00
GlBadge ,
2020-06-01 15:08:16 +00:00
GlPagination ,
2020-06-18 09:08:41 +00:00
GlSearchBoxByType ,
2020-06-18 00:08:35 +00:00
GlSprintf ,
2020-05-04 06:10:10 +00:00
} from '@gitlab/ui' ;
2020-06-18 09:08:41 +00:00
import { _ _ , s _ _ } from '~/locale' ;
import { debounce , trim } from 'lodash' ;
2020-05-18 21:08:42 +00:00
import { joinPaths , visitUrl } from '~/lib/utils/url_utility' ;
2020-05-21 15:08:18 +00:00
import { fetchPolicies } from '~/lib/graphql' ;
2020-04-30 12:09:45 +00:00
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue' ;
2020-06-24 15:08:50 +00:00
import { convertToSnakeCase } from '~/lib/utils/text_utility' ;
import Tracking from '~/tracking' ;
2020-05-21 15:08:18 +00:00
import getAlerts from '../graphql/queries/get_alerts.query.graphql' ;
import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql' ;
2020-05-26 15:08:17 +00:00
import {
ALERTS _STATUS _TABS ,
ALERTS _SEVERITY _LABELS ,
2020-06-01 15:08:16 +00:00
DEFAULT _PAGE _SIZE ,
2020-05-26 15:08:17 +00:00
trackAlertListViewsOptions ,
trackAlertStatusUpdateOptions ,
} from '../constants' ;
2020-06-24 15:08:50 +00:00
import AlertStatus from './alert_status.vue' ;
2020-04-24 09:09:44 +00:00
2020-06-29 12:09:20 +00:00
const tdClass =
'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap' ;
2020-06-15 18:08:43 +00:00
const thClass = 'gl-hover-bg-blue-50' ;
2020-05-15 18:07:52 +00:00
const bodyTrClass =
2020-05-26 15:08:17 +00:00
'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200' ;
2020-05-07 06:09:38 +00:00
2020-06-01 15:08:16 +00:00
const initialPaginationState = {
currentPage : 1 ,
prevPageCursor : '' ,
nextPageCursor : '' ,
firstPageSize : DEFAULT _PAGE _SIZE ,
lastPageSize : null ,
} ;
2020-04-21 15:21:10 +00:00
export default {
2020-04-24 18:09:46 +00:00
i18n : {
noAlertsMsg : s _ _ (
2020-06-18 00:08:35 +00:00
'AlertManagement|No alerts available to display. See %{linkStart}enabling alert management%{linkEnd} for more information on adding alerts to the list.' ,
2020-04-24 18:09:46 +00:00
) ,
errorMsg : s _ _ (
"AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear." ,
) ,
2020-06-18 09:08:41 +00:00
searchPlaceholder : _ _ ( 'Search or filter results...' ) ,
2020-04-24 18:09:46 +00:00
} ,
2020-04-24 09:09:44 +00:00
fields : [
2020-05-14 00:07:47 +00:00
{
key : 'severity' ,
label : s _ _ ( 'AlertManagement|Severity' ) ,
tdClass : ` ${ tdClass } rounded-top text-capitalize ` ,
2020-06-29 12:09:20 +00:00
thClass : ` ${ thClass } gl-w-eighth ` ,
2020-05-25 15:07:58 +00:00
sortable : true ,
2020-05-14 00:07:47 +00:00
} ,
2020-04-24 09:09:44 +00:00
{
2020-05-27 00:08:11 +00:00
key : 'startedAt' ,
2020-04-30 12:09:45 +00:00
label : s _ _ ( 'AlertManagement|Start time' ) ,
2020-06-29 12:09:20 +00:00
thClass : ` ${ thClass } js-started-at w-15p ` ,
2020-05-07 06:09:38 +00:00
tdClass ,
2020-05-25 15:07:58 +00:00
sortable : true ,
2020-04-24 09:09:44 +00:00
} ,
{
2020-04-30 12:09:45 +00:00
key : 'title' ,
2020-04-24 18:09:46 +00:00
label : s _ _ ( 'AlertManagement|Alert' ) ,
2020-07-01 15:08:45 +00:00
thClass : ` gl-pointer-events-none ` ,
2020-05-07 06:09:38 +00:00
tdClass ,
2020-04-24 09:09:44 +00:00
} ,
{
2020-05-27 00:08:11 +00:00
key : 'eventCount' ,
2020-04-24 18:09:46 +00:00
label : s _ _ ( 'AlertManagement|Events' ) ,
2020-06-29 12:09:20 +00:00
thClass : ` ${ thClass } text-right gl-w-12 ` ,
2020-05-20 12:07:52 +00:00
tdClass : ` ${ tdClass } text-md-right ` ,
2020-05-25 15:07:58 +00:00
sortable : true ,
2020-04-24 09:09:44 +00:00
} ,
2020-07-03 09:08:53 +00:00
{
key : 'issue' ,
label : s _ _ ( 'AlertManagement|Issue' ) ,
thClass : 'gl-w-12 gl-pointer-events-none' ,
tdClass ,
sortable : false ,
} ,
2020-05-29 21:08:35 +00:00
{
key : 'assignees' ,
label : s _ _ ( 'AlertManagement|Assignees' ) ,
2020-07-01 15:08:45 +00:00
thClass : 'gl-w-eighth gl-pointer-events-none' ,
2020-05-29 21:08:35 +00:00
tdClass ,
} ,
2020-04-24 09:09:44 +00:00
{
key : 'status' ,
2020-06-15 18:08:43 +00:00
thClass : ` ${ thClass } w-15p ` ,
2020-04-24 18:09:46 +00:00
label : s _ _ ( 'AlertManagement|Status' ) ,
2020-05-15 18:07:52 +00:00
tdClass : ` ${ tdClass } rounded-bottom ` ,
2020-05-25 15:07:58 +00:00
sortable : true ,
2020-04-24 09:09:44 +00:00
} ,
] ,
2020-05-14 00:07:47 +00:00
severityLabels : ALERTS _SEVERITY _LABELS ,
2020-05-07 15:09:29 +00:00
statusTabs : ALERTS _STATUS _TABS ,
2020-04-21 15:21:10 +00:00
components : {
GlLoadingIcon ,
2020-04-24 09:09:44 +00:00
GlTable ,
GlAlert ,
2020-04-30 12:09:45 +00:00
TimeAgo ,
2020-05-13 18:08:47 +00:00
GlIcon ,
2020-06-18 00:08:35 +00:00
GlLink ,
2020-05-07 15:09:29 +00:00
GlTabs ,
GlTab ,
2020-05-21 15:08:18 +00:00
GlBadge ,
2020-06-01 15:08:16 +00:00
GlPagination ,
2020-06-18 09:08:41 +00:00
GlSearchBoxByType ,
2020-06-18 00:08:35 +00:00
GlSprintf ,
2020-06-24 15:08:50 +00:00
AlertStatus ,
2020-04-21 15:21:10 +00:00
} ,
props : {
2020-04-30 12:09:45 +00:00
projectPath : {
2020-04-21 15:21:10 +00:00
type : String ,
required : true ,
} ,
2020-06-18 00:08:35 +00:00
populatingAlertsHelpUrl : {
type : String ,
required : true ,
} ,
2020-04-21 15:21:10 +00:00
} ,
2020-04-24 18:09:46 +00:00
apollo : {
alerts : {
2020-05-21 15:08:18 +00:00
fetchPolicy : fetchPolicies . CACHE _AND _NETWORK ,
2020-04-24 18:09:46 +00:00
query : getAlerts ,
variables ( ) {
return {
2020-06-18 09:08:41 +00:00
searchTerm : this . searchTerm ,
2020-04-30 12:09:45 +00:00
projectPath : this . projectPath ,
2020-05-15 09:07:59 +00:00
statuses : this . statusFilter ,
2020-05-25 15:07:58 +00:00
sort : this . sort ,
2020-06-01 15:08:16 +00:00
firstPageSize : this . pagination . firstPageSize ,
lastPageSize : this . pagination . lastPageSize ,
prevPageCursor : this . pagination . prevPageCursor ,
nextPageCursor : this . pagination . nextPageCursor ,
2020-04-24 18:09:46 +00:00
} ;
} ,
2020-04-30 12:09:45 +00:00
update ( data ) {
2020-06-01 15:08:16 +00:00
const { alertManagementAlerts : { nodes : list = [ ] , pageInfo = { } } = { } } =
data . project || { } ;
return {
list ,
pageInfo ,
} ;
2020-04-30 12:09:45 +00:00
} ,
2020-04-24 18:09:46 +00:00
error ( ) {
this . errored = true ;
} ,
} ,
2020-05-21 15:08:18 +00:00
alertsCount : {
query : getAlertsCountByStatus ,
variables ( ) {
return {
2020-06-18 09:08:41 +00:00
searchTerm : this . searchTerm ,
2020-05-21 15:08:18 +00:00
projectPath : this . projectPath ,
} ;
} ,
update ( data ) {
return data . project ? . alertManagementAlertStatusCounts ;
} ,
} ,
2020-04-24 18:09:46 +00:00
} ,
2020-04-21 15:21:10 +00:00
data ( ) {
return {
2020-06-18 09:08:41 +00:00
searchTerm : '' ,
2020-04-24 18:09:46 +00:00
errored : false ,
2020-06-24 15:08:50 +00:00
errorMessage : '' ,
2020-04-24 09:09:44 +00:00
isAlertDismissed : false ,
2020-04-24 18:09:46 +00:00
isErrorAlertDismissed : false ,
2020-06-06 03:08:19 +00:00
sort : 'STARTED_AT_DESC' ,
2020-06-01 15:08:16 +00:00
statusFilter : [ ] ,
filteredByStatus : '' ,
pagination : initialPaginationState ,
2020-06-06 03:08:19 +00:00
sortBy : 'startedAt' ,
sortDesc : true ,
2020-06-15 18:08:43 +00:00
sortDirection : 'desc' ,
2020-04-21 15:21:10 +00:00
} ;
} ,
2020-04-24 09:09:44 +00:00
computed : {
showNoAlertsMsg ( ) {
2020-05-21 15:08:18 +00:00
return (
2020-06-18 09:08:41 +00:00
! this . errored &&
! this . loading &&
this . alertsCount ? . all === 0 &&
! this . searchTerm &&
! this . isAlertDismissed
2020-05-21 15:08:18 +00:00
) ;
2020-04-24 18:09:46 +00:00
} ,
showErrorMsg ( ) {
return this . errored && ! this . isErrorAlertDismissed ;
} ,
loading ( ) {
return this . $apollo . queries . alerts . loading ;
2020-04-24 09:09:44 +00:00
} ,
2020-05-20 15:08:20 +00:00
hasAlerts ( ) {
2020-06-01 15:08:16 +00:00
return this . alerts ? . list ? . length ;
2020-05-20 15:08:20 +00:00
} ,
tbodyTrClass ( ) {
return ! this . loading && this . hasAlerts ? bodyTrClass : '' ;
} ,
2020-06-01 15:08:16 +00:00
showPaginationControls ( ) {
return Boolean ( this . prevPage || this . nextPage ) ;
} ,
alertsForCurrentTab ( ) {
return this . alertsCount ? this . alertsCount [ this . filteredByStatus . toLowerCase ( ) ] : 0 ;
} ,
prevPage ( ) {
return Math . max ( this . pagination . currentPage - 1 , 0 ) ;
} ,
nextPage ( ) {
const nextPage = this . pagination . currentPage + 1 ;
return nextPage > Math . ceil ( this . alertsForCurrentTab / DEFAULT _PAGE _SIZE ) ? null : nextPage ;
} ,
2020-04-24 09:09:44 +00:00
} ,
2020-05-25 15:07:58 +00:00
mounted ( ) {
2020-05-26 15:08:17 +00:00
this . trackPageViews ( ) ;
2020-05-25 15:07:58 +00:00
} ,
2020-05-07 15:09:29 +00:00
methods : {
2020-05-15 12:08:28 +00:00
filterAlertsByStatus ( tabIndex ) {
2020-06-01 15:08:16 +00:00
this . resetPagination ( ) ;
const { filters , status } = this . $options . statusTabs [ tabIndex ] ;
this . statusFilter = filters ;
this . filteredByStatus = status ;
2020-05-07 15:09:29 +00:00
} ,
2020-05-25 15:07:58 +00:00
fetchSortedData ( { sortBy , sortDesc } ) {
2020-06-15 18:08:43 +00:00
const sortingDirection = sortDesc ? 'DESC' : 'ASC' ;
const sortingColumn = convertToSnakeCase ( sortBy ) . toUpperCase ( ) ;
2020-05-25 15:07:58 +00:00
2020-06-01 15:08:16 +00:00
this . resetPagination ( ) ;
2020-06-15 18:08:43 +00:00
this . sort = ` ${ sortingColumn } _ ${ sortingDirection } ` ;
2020-05-25 15:07:58 +00:00
} ,
2020-06-18 09:08:41 +00:00
onInputChange : debounce ( function debounceSearch ( input ) {
const trimmedInput = trim ( input ) ;
if ( trimmedInput !== this . searchTerm ) {
this . resetPagination ( ) ;
this . searchTerm = trimmedInput ;
}
} , 500 ) ,
2020-05-18 21:08:42 +00:00
navigateToAlertDetails ( { iid } ) {
return visitUrl ( joinPaths ( window . location . pathname , iid , 'details' ) ) ;
2020-05-15 18:07:52 +00:00
} ,
2020-05-26 15:08:17 +00:00
trackPageViews ( ) {
const { category , action } = trackAlertListViewsOptions ;
Tracking . event ( category , action ) ;
} ,
trackStatusUpdate ( status ) {
const { category , action , label } = trackAlertStatusUpdateOptions ;
Tracking . event ( category , action , { label , property : status } ) ;
} ,
2020-05-29 21:08:35 +00:00
getAssignees ( assignees ) {
// TODO: Update to show list of assignee(s) after https://gitlab.com/gitlab-org/gitlab/-/issues/218405
2020-06-11 00:08:35 +00:00
return assignees . nodes ? . length > 0
? assignees . nodes [ 0 ] ? . username
: s _ _ ( 'AlertManagement|Unassigned' ) ;
2020-05-29 21:08:35 +00:00
} ,
2020-07-03 09:08:53 +00:00
getIssueLink ( item ) {
return joinPaths ( '/' , this . projectPath , '-' , 'issues' , item . issueIid ) ;
} ,
2020-06-01 15:08:16 +00:00
handlePageChange ( page ) {
const { startCursor , endCursor } = this . alerts . pageInfo ;
if ( page > this . pagination . currentPage ) {
this . pagination = {
... initialPaginationState ,
nextPageCursor : endCursor ,
currentPage : page ,
} ;
} else {
this . pagination = {
lastPageSize : DEFAULT _PAGE _SIZE ,
firstPageSize : null ,
prevPageCursor : startCursor ,
nextPageCursor : '' ,
currentPage : page ,
} ;
}
} ,
resetPagination ( ) {
this . pagination = initialPaginationState ;
} ,
2020-06-24 15:08:50 +00:00
handleAlertError ( errorMessage ) {
this . errored = true ;
this . errorMessage = errorMessage ;
} ,
dismissError ( ) {
this . isErrorAlertDismissed = true ;
this . errorMessage = '' ;
} ,
2020-05-07 15:09:29 +00:00
} ,
2020-04-21 15:21:10 +00:00
} ;
< / script >
< template >
< div >
2020-07-10 15:09:07 +00:00
< div class = "alert-management-list" >
2020-04-24 09:09:44 +00:00
< gl -alert v-if ="showNoAlertsMsg" @dismiss="isAlertDismissed = true" >
2020-06-18 00:08:35 +00:00
< gl -sprintf :message ="$options.i18n.noAlertsMsg" >
< template # link = "{ content }" >
< gl -link
class = "gl-display-inline-block"
: href = "populatingAlertsHelpUrl"
target = "_blank"
>
{ { content } }
< / g l - l i n k >
< / template >
< / g l - s p r i n t f >
2020-04-24 18:09:46 +00:00
< / g l - a l e r t >
2020-06-24 15:08:50 +00:00
< gl -alert
v - if = "showErrorMsg"
variant = "danger"
data - testid = "alert-error"
@ dismiss = "dismissError"
>
{ { errorMessage || $options . i18n . errorMsg } }
2020-04-24 09:09:44 +00:00
< / g l - a l e r t >
2020-06-18 09:08:41 +00:00
< gl -tabs content -class = " gl -p -0 " @input ="filterAlertsByStatus" >
2020-05-07 15:09:29 +00:00
< gl -tab v-for ="tab in $options.statusTabs" :key="tab.status" >
< template slot = "title" >
< span > { { tab . title } } < / span >
2020-05-21 15:08:18 +00:00
< gl -badge v-if ="alertsCount" pill size="sm" class="gl-tab-counter-badge" >
{ { alertsCount [ tab . status . toLowerCase ( ) ] } }
< / g l - b a d g e >
2020-05-07 15:09:29 +00:00
< / template >
< / g l - t a b >
< / g l - t a b s >
2020-06-18 09:08:41 +00:00
< div class = "gl-bg-gray-10 gl-p-5 gl-border-b-solid gl-border-b-1 gl-border-gray-100" >
< gl -search -box -by -type
class = "gl-bg-white"
: placeholder = "$options.i18n.searchPlaceholder"
@ input = "onInputChange"
/ >
< / div >
2020-05-07 06:09:38 +00:00
< h4 class = "d-block d-md-none my-3" >
{ { s _ _ ( 'AlertManagement|Alerts' ) } }
< / h4 >
2020-04-24 09:09:44 +00:00
< gl -table
2020-06-18 09:08:41 +00:00
class = "alert-management-table"
2020-06-01 15:08:16 +00:00
: items = "alerts ? alerts.list : []"
2020-04-24 09:09:44 +00:00
: fields = "$options.fields"
: show - empty = "true"
2020-04-24 18:09:46 +00:00
: busy = "loading"
2020-04-30 12:09:45 +00:00
stacked = "md"
2020-05-20 15:08:20 +00:00
: tbody - tr - class = "tbodyTrClass"
2020-05-25 15:07:58 +00:00
: no - local - sorting = "true"
2020-06-15 18:08:43 +00:00
: sort - direction = "sortDirection"
2020-06-06 03:08:19 +00:00
: sort - desc . sync = "sortDesc"
: sort - by . sync = "sortBy"
2020-05-25 15:07:58 +00:00
sort - icon - left
2020-06-29 12:09:20 +00:00
fixed
2020-05-18 21:08:42 +00:00
@ row - clicked = "navigateToAlertDetails"
2020-05-25 15:07:58 +00:00
@ sort - changed = "fetchSortedData"
2020-04-24 09:09:44 +00:00
>
2020-05-14 00:07:47 +00:00
< template # cell ( severity ) = " { item } " >
< div
class = "d-inline-flex align-items-center justify-content-between"
data - testid = "severityField"
>
< gl -icon
class = "mr-2"
: size = "12"
: name = "`severity-${item.severity.toLowerCase()}`"
: class = "`icon-${item.severity.toLowerCase()}`"
/ >
{ { $options . severityLabels [ item . severity ] } }
< / div >
< / template >
2020-05-27 00:08:11 +00:00
< template # cell ( startedAt ) = " { item } " >
2020-05-11 09:09:45 +00:00
< time -ago v -if = " item.startedAt " :time ="item.startedAt" / >
2020-04-30 12:09:45 +00:00
< / template >
2020-05-27 00:08:11 +00:00
< template # cell ( eventCount ) = " { item } " >
2020-05-25 15:07:58 +00:00
{ { item . eventCount } }
< / template >
2020-04-30 12:09:45 +00:00
< template # cell ( title ) = " { item } " >
2020-06-29 12:09:20 +00:00
< div class = "gl-max-w-full text-truncate" :title ="item.title" > { { item . title } } < / div >
2020-07-03 09:08:53 +00:00
< / template >
< template # cell ( issue ) = " { item } " >
< gl -link v-if ="item.issueIid" data-testid="issueField" :href="getIssueLink(item)" >
# { { item . issueIid } }
< / g l - l i n k >
< div v -else data -testid = " issueField " > { { s _ _ ( 'AlertManagement|None' ) } } < / div >
2020-04-30 12:09:45 +00:00
< / template >
2020-05-07 06:09:38 +00:00
2020-05-29 21:08:35 +00:00
< template # cell ( assignees ) = " { item } " >
< div class = "gl-max-w-full text-truncate" data -testid = " assigneesField " >
{ { getAssignees ( item . assignees ) } }
< / div >
< / template >
2020-05-04 06:10:10 +00:00
< template # cell ( status ) = " { item } " >
2020-06-24 15:08:50 +00:00
< alert -status
: alert = "item"
: project - path = "projectPath"
: is - sidebar = "false"
@ alert - error = "handleAlertError"
/ >
2020-05-04 06:10:10 +00:00
< / template >
2020-04-30 12:09:45 +00:00
2020-04-24 09:09:44 +00:00
< template # empty >
2020-04-24 18:09:46 +00:00
{ { s _ _ ( 'AlertManagement|No alerts to display.' ) } }
< / template >
2020-04-30 12:09:45 +00:00
2020-04-24 18:09:46 +00:00
< template # table -busy >
< gl -loading -icon size = "lg" color = "dark" class = "mt-3" / >
2020-04-24 09:09:44 +00:00
< / template >
< / g l - t a b l e >
2020-06-01 15:08:16 +00:00
< gl -pagination
v - if = "showPaginationControls"
: value = "pagination.currentPage"
: prev - page = "prevPage"
: next - page = "nextPage"
align = "center"
2020-06-26 06:09:03 +00:00
class = "gl-pagination gl-mt-3"
2020-06-01 15:08:16 +00:00
@ input = "handlePageChange"
/ >
2020-04-21 15:21:10 +00:00
< / div >
< / div >
< / template >