github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/src/Dashboard/Pipeline.elm (about) 1 module Dashboard.Pipeline exposing 2 ( hdPipelineView 3 , pipelineNotSetView 4 , pipelineStatus 5 , pipelineView 6 ) 7 8 import Assets 9 import Concourse 10 import Concourse.BuildStatus exposing (BuildStatus(..)) 11 import Concourse.PipelineStatus as PipelineStatus 12 import Dashboard.DashboardPreview as DashboardPreview 13 import Dashboard.Group.Models exposing (Pipeline) 14 import Dashboard.Styles as Styles 15 import Duration 16 import HoverState 17 import Html exposing (Html) 18 import Html.Attributes 19 exposing 20 ( attribute 21 , class 22 , classList 23 , draggable 24 , href 25 , id 26 , style 27 ) 28 import Html.Events exposing (onClick, onMouseEnter, onMouseLeave) 29 import Message.Effects as Effects 30 import Message.Message exposing (DomID(..), Message(..), PipelinesSection(..)) 31 import Routes 32 import Set exposing (Set) 33 import Time 34 import UserState exposing (UserState) 35 import Views.FavoritedIcon 36 import Views.Icon as Icon 37 import Views.PauseToggle as PauseToggle 38 import Views.Spinner as Spinner 39 import Views.Styles 40 41 42 previewPlaceholder : Html Message 43 previewPlaceholder = 44 Html.div 45 (class "card-body" :: Styles.cardBody) 46 [ Html.div Styles.previewPlaceholder [] ] 47 48 49 pipelineNotSetView : Html Message 50 pipelineNotSetView = 51 Html.div (class "card" :: Styles.noPipelineCard) 52 [ Html.div 53 (class "card-header" :: Styles.noPipelineCardHeader) 54 [ Html.text "no pipeline set" 55 ] 56 , previewPlaceholder 57 , Html.div 58 (class "card-footer" :: Styles.cardFooter) 59 [] 60 ] 61 62 63 hdPipelineView : 64 { pipeline : Pipeline 65 , pipelineRunningKeyframes : String 66 , resourceError : Bool 67 , existingJobs : List Concourse.Job 68 } 69 -> Html Message 70 hdPipelineView { pipeline, pipelineRunningKeyframes, resourceError, existingJobs } = 71 let 72 bannerStyle = 73 if pipeline.stale then 74 Styles.pipelineCardBannerStaleHd 75 76 else if pipeline.archived then 77 Styles.pipelineCardBannerArchivedHd 78 79 else 80 Styles.pipelineCardBannerHd 81 { status = pipelineStatus existingJobs pipeline 82 , pipelineRunningKeyframes = pipelineRunningKeyframes 83 } 84 in 85 Html.a 86 ([ class "card" 87 , attribute "data-pipeline-name" pipeline.name 88 , attribute "data-team-name" pipeline.teamName 89 , onMouseEnter <| TooltipHd pipeline.name pipeline.teamName 90 , href <| Routes.toString <| Routes.pipelineRoute pipeline 91 ] 92 ++ Styles.pipelineCardHd (pipelineStatus existingJobs pipeline) 93 ) 94 <| 95 [ Html.div 96 (class "banner" :: bannerStyle) 97 [] 98 , Html.div 99 (class "dashboardhd-pipeline-name" :: Styles.pipelineCardBodyHd) 100 [ Html.text pipeline.name ] 101 ] 102 ++ (if resourceError then 103 [ Html.div Styles.resourceErrorTriangle [] ] 104 105 else 106 [] 107 ) 108 109 110 pipelineView : 111 { now : Maybe Time.Posix 112 , pipeline : Pipeline 113 , hovered : HoverState.HoverState 114 , pipelineRunningKeyframes : String 115 , userState : UserState 116 , favoritedPipelines : Set Concourse.DatabaseID 117 , resourceError : Bool 118 , existingJobs : List Concourse.Job 119 , layers : List (List Concourse.Job) 120 , section : PipelinesSection 121 } 122 -> Html Message 123 pipelineView { now, pipeline, hovered, pipelineRunningKeyframes, userState, favoritedPipelines, resourceError, existingJobs, layers, section } = 124 let 125 bannerStyle = 126 if pipeline.stale then 127 Styles.pipelineCardBannerStale 128 129 else if pipeline.archived then 130 Styles.pipelineCardBannerArchived 131 132 else 133 Styles.pipelineCardBanner 134 { status = pipelineStatus existingJobs pipeline 135 , pipelineRunningKeyframes = pipelineRunningKeyframes 136 } 137 in 138 Html.div 139 (Styles.pipelineCard 140 ++ (if section == AllPipelinesSection && not pipeline.stale then 141 [ style "cursor" "move" ] 142 143 else 144 [] 145 ) 146 ++ (if pipeline.stale then 147 [ style "opacity" "0.45" ] 148 149 else 150 [] 151 ) 152 ) 153 [ Html.div 154 (class "banner" :: bannerStyle) 155 [] 156 , headerView pipeline resourceError 157 , if pipeline.jobsDisabled || pipeline.archived then 158 previewPlaceholder 159 160 else 161 bodyView section hovered layers 162 , footerView userState favoritedPipelines pipeline section now hovered existingJobs 163 ] 164 165 166 pipelineStatus : List Concourse.Job -> Pipeline -> PipelineStatus.PipelineStatus 167 pipelineStatus jobs pipeline = 168 if pipeline.paused then 169 PipelineStatus.PipelineStatusPaused 170 171 else 172 let 173 isRunning = 174 List.any (\job -> job.nextBuild /= Nothing) jobs 175 176 unpausedJobs = 177 jobs |> List.filter (\job -> not job.paused) 178 179 mostImportantJobStatus = 180 unpausedJobs 181 |> List.map jobStatus 182 |> List.sortWith Concourse.BuildStatus.ordering 183 |> List.head 184 185 firstNonSuccess = 186 unpausedJobs 187 |> List.filter (jobStatus >> (/=) BuildStatusSucceeded) 188 |> List.filterMap transition 189 |> List.sortBy Time.posixToMillis 190 |> List.head 191 192 lastTransition = 193 unpausedJobs 194 |> List.filterMap transition 195 |> List.sortBy Time.posixToMillis 196 |> List.reverse 197 |> List.head 198 199 transitionTime = 200 case firstNonSuccess of 201 Just t -> 202 Just t 203 204 Nothing -> 205 lastTransition 206 in 207 case ( mostImportantJobStatus, transitionTime ) of 208 ( _, Nothing ) -> 209 PipelineStatus.PipelineStatusPending isRunning 210 211 ( Nothing, _ ) -> 212 PipelineStatus.PipelineStatusPending isRunning 213 214 ( Just BuildStatusPending, _ ) -> 215 PipelineStatus.PipelineStatusPending isRunning 216 217 ( Just BuildStatusStarted, _ ) -> 218 PipelineStatus.PipelineStatusPending isRunning 219 220 ( Just BuildStatusSucceeded, Just since ) -> 221 if isRunning then 222 PipelineStatus.PipelineStatusSucceeded PipelineStatus.Running 223 224 else 225 PipelineStatus.PipelineStatusSucceeded (PipelineStatus.Since since) 226 227 ( Just BuildStatusFailed, Just since ) -> 228 if isRunning then 229 PipelineStatus.PipelineStatusFailed PipelineStatus.Running 230 231 else 232 PipelineStatus.PipelineStatusFailed (PipelineStatus.Since since) 233 234 ( Just BuildStatusErrored, Just since ) -> 235 if isRunning then 236 PipelineStatus.PipelineStatusErrored PipelineStatus.Running 237 238 else 239 PipelineStatus.PipelineStatusErrored (PipelineStatus.Since since) 240 241 ( Just BuildStatusAborted, Just since ) -> 242 if isRunning then 243 PipelineStatus.PipelineStatusAborted PipelineStatus.Running 244 245 else 246 PipelineStatus.PipelineStatusAborted (PipelineStatus.Since since) 247 248 249 jobStatus : Concourse.Job -> BuildStatus 250 jobStatus job = 251 case job.finishedBuild of 252 Just build -> 253 build.status 254 255 Nothing -> 256 BuildStatusPending 257 258 259 transition : Concourse.Job -> Maybe Time.Posix 260 transition = 261 .transitionBuild >> Maybe.andThen (.duration >> .finishedAt) 262 263 264 headerView : Pipeline -> Bool -> Html Message 265 headerView pipeline resourceError = 266 Html.a 267 [ href <| Routes.toString <| Routes.pipelineRoute pipeline, draggable "false" ] 268 [ Html.div 269 ([ class "card-header" 270 , onMouseEnter <| Tooltip pipeline.name pipeline.teamName 271 ] 272 ++ Styles.pipelineCardHeader 273 ) 274 [ Html.div 275 (class "dashboard-pipeline-name" :: Styles.pipelineName) 276 [ Html.text pipeline.name ] 277 , Html.div 278 [ classList 279 [ ( "dashboard-resource-error", resourceError ) 280 ] 281 ] 282 [] 283 ] 284 ] 285 286 287 bodyView : PipelinesSection -> HoverState.HoverState -> List (List Concourse.Job) -> Html Message 288 bodyView section hovered layers = 289 Html.div 290 (class "card-body" :: Styles.pipelineCardBody) 291 [ DashboardPreview.view section hovered layers ] 292 293 294 footerView : 295 UserState 296 -> Set Concourse.DatabaseID 297 -> Pipeline 298 -> PipelinesSection 299 -> Maybe Time.Posix 300 -> HoverState.HoverState 301 -> List Concourse.Job 302 -> Html Message 303 footerView userState favoritedPipelines pipeline section now hovered existingJobs = 304 let 305 spacer = 306 Html.div [ style "width" "12px" ] [] 307 308 pipelineId = 309 { pipelineName = pipeline.name 310 , teamName = pipeline.teamName 311 } 312 313 status = 314 pipelineStatus existingJobs pipeline 315 316 pauseToggle = 317 PauseToggle.view 318 { isPaused = 319 status == PipelineStatus.PipelineStatusPaused 320 , pipeline = pipelineId 321 , isToggleHovered = 322 HoverState.isHovered (PipelineCardPauseToggle section pipelineId) hovered 323 , isToggleLoading = pipeline.isToggleLoading 324 , tooltipPosition = Views.Styles.Above 325 , margin = "0" 326 , userState = userState 327 , domID = PipelineCardPauseToggle section pipelineId 328 } 329 330 visibilityButton = 331 visibilityView 332 { public = pipeline.public 333 , pipelineId = pipelineId 334 , isClickable = 335 UserState.isAnonymous userState 336 || UserState.isMember 337 { teamName = pipeline.teamName 338 , userState = userState 339 } 340 , isHovered = 341 HoverState.isHovered (VisibilityButton section pipelineId) hovered 342 , isVisibilityLoading = pipeline.isVisibilityLoading 343 , section = section 344 } 345 346 favoritedIcon = 347 Views.FavoritedIcon.view 348 { isFavorited = Set.member pipeline.id favoritedPipelines 349 , isHovered = HoverState.isHovered (PipelineCardFavoritedIcon section pipeline.id) hovered 350 , isSideBar = False 351 , domID = PipelineCardFavoritedIcon section pipeline.id 352 } 353 [ id <| Effects.toHtmlID <| PipelineCardFavoritedIcon section pipeline.id ] 354 in 355 Html.div 356 (class "card-footer" :: Styles.pipelineCardFooter) 357 [ pipelineStatusView section pipeline status now 358 , Html.div 359 [ style "display" "flex" ] 360 <| 361 List.intersperse spacer 362 (if pipeline.archived then 363 [ visibilityButton, favoritedIcon ] 364 365 else 366 [ pauseToggle, visibilityButton, favoritedIcon ] 367 ) 368 ] 369 370 371 pipelineStatusView : PipelinesSection -> Pipeline -> PipelineStatus.PipelineStatus -> Maybe Time.Posix -> Html Message 372 pipelineStatusView section pipeline status now = 373 let 374 pipelineId = 375 { pipelineName = pipeline.name 376 , teamName = pipeline.teamName 377 } 378 in 379 Html.div 380 [ style "display" "flex" 381 , class "pipeline-status" 382 ] 383 (if pipeline.archived then 384 [] 385 386 else 387 [ if pipeline.jobsDisabled then 388 Icon.icon 389 { sizePx = 20, image = Assets.PipelineStatusIconJobsDisabled } 390 ([ style "opacity" "0.5" 391 , id <| Effects.toHtmlID <| PipelineStatusIcon section pipelineId 392 , onMouseEnter <| Hover <| Just <| PipelineStatusIcon section pipelineId 393 ] 394 ++ Styles.pipelineStatusIcon 395 ) 396 397 else if pipeline.stale then 398 Icon.icon 399 { sizePx = 20, image = Assets.PipelineStatusIconStale } 400 Styles.pipelineStatusIcon 401 402 else 403 Icon.icon 404 { sizePx = 20, image = Assets.PipelineStatusIcon status } 405 Styles.pipelineStatusIcon 406 , if pipeline.jobsDisabled then 407 Html.div 408 (class "build-duration" 409 :: Styles.pipelineCardTransitionAgeStale 410 ) 411 [ Html.text "no data" ] 412 413 else if pipeline.stale then 414 Html.div 415 (class "build-duration" 416 :: Styles.pipelineCardTransitionAgeStale 417 ) 418 [ Html.text "loading..." ] 419 420 else 421 transitionView now status 422 ] 423 ) 424 425 426 visibilityView : 427 { public : Bool 428 , pipelineId : Concourse.PipelineIdentifier 429 , isClickable : Bool 430 , isHovered : Bool 431 , isVisibilityLoading : Bool 432 , section : PipelinesSection 433 } 434 -> Html Message 435 visibilityView { public, pipelineId, isClickable, isHovered, isVisibilityLoading, section } = 436 if isVisibilityLoading then 437 Spinner.hoverableSpinner 438 { sizePx = 20 439 , margin = "0" 440 , hoverable = Just <| VisibilityButton section pipelineId 441 } 442 443 else 444 Html.div 445 (Styles.visibilityToggle 446 { public = public 447 , isClickable = isClickable 448 , isHovered = isHovered 449 } 450 ++ [ onMouseEnter <| Hover <| Just <| VisibilityButton section pipelineId 451 , onMouseLeave <| Hover Nothing 452 , id <| Effects.toHtmlID <| VisibilityButton section pipelineId 453 ] 454 ++ (if isClickable then 455 [ onClick <| Click <| VisibilityButton section pipelineId ] 456 457 else 458 [] 459 ) 460 ) 461 [] 462 463 464 sinceTransitionText : PipelineStatus.StatusDetails -> Time.Posix -> String 465 sinceTransitionText details now = 466 case details of 467 PipelineStatus.Running -> 468 "running" 469 470 PipelineStatus.Since time -> 471 Duration.format <| Duration.between time now 472 473 474 transitionView : Maybe Time.Posix -> PipelineStatus.PipelineStatus -> Html Message 475 transitionView t status = 476 case ( status, t ) of 477 ( PipelineStatus.PipelineStatusPaused, _ ) -> 478 Html.div 479 (class "build-duration" 480 :: Styles.pipelineCardTransitionAge status 481 ) 482 [ Html.text "paused" ] 483 484 ( PipelineStatus.PipelineStatusPending False, _ ) -> 485 Html.div 486 (class "build-duration" 487 :: Styles.pipelineCardTransitionAge status 488 ) 489 [ Html.text "pending" ] 490 491 ( PipelineStatus.PipelineStatusPending True, _ ) -> 492 Html.div 493 (class "build-duration" 494 :: Styles.pipelineCardTransitionAge status 495 ) 496 [ Html.text "running" ] 497 498 ( PipelineStatus.PipelineStatusAborted details, Just now ) -> 499 Html.div 500 (class "build-duration" 501 :: Styles.pipelineCardTransitionAge status 502 ) 503 [ Html.text <| sinceTransitionText details now ] 504 505 ( PipelineStatus.PipelineStatusErrored details, Just now ) -> 506 Html.div 507 (class "build-duration" 508 :: Styles.pipelineCardTransitionAge status 509 ) 510 [ Html.text <| sinceTransitionText details now ] 511 512 ( PipelineStatus.PipelineStatusFailed details, Just now ) -> 513 Html.div 514 (class "build-duration" 515 :: Styles.pipelineCardTransitionAge status 516 ) 517 [ Html.text <| sinceTransitionText details now ] 518 519 ( PipelineStatus.PipelineStatusSucceeded details, Just now ) -> 520 Html.div 521 (class "build-duration" 522 :: Styles.pipelineCardTransitionAge status 523 ) 524 [ Html.text <| sinceTransitionText details now ] 525 526 _ -> 527 Html.text ""