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 ""