github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/src/Build/StepTree/StepTree.elm (about)

     1  module Build.StepTree.StepTree exposing
     2      ( extendHighlight
     3      , finished
     4      , init
     5      , setHighlight
     6      , setImageCheck
     7      , setImageGet
     8      , switchTab
     9      , toggleStep
    10      , toggleStepInitialization
    11      , toggleStepSubHeader
    12      , tooltip
    13      , view
    14      )
    15  
    16  import Ansi.Log
    17  import Array exposing (Array)
    18  import Assets
    19  import Build.Models exposing (StepHeaderType(..))
    20  import Build.StepTree.Models
    21      exposing
    22          ( HookedStep
    23          , MetadataField
    24          , Step
    25          , StepName
    26          , StepState(..)
    27          , StepTree(..)
    28          , StepTreeModel
    29          , TabFocus(..)
    30          , Version
    31          , focusTabbed
    32          , isActive
    33          , lastActive
    34          , mostSevereStepState
    35          , showStepState
    36          , toggleSubHeaderExpanded
    37          , treeIsActive
    38          , updateAt
    39          )
    40  import Build.Styles as Styles
    41  import Colors
    42  import Concourse exposing (JsonValue(..))
    43  import DateFormat
    44  import Dict exposing (Dict)
    45  import Duration
    46  import HoverState
    47  import Html exposing (Html)
    48  import Html.Attributes exposing (attribute, class, classList, href, id, style, target)
    49  import Html.Events exposing (onClick, onMouseEnter, onMouseLeave)
    50  import Json.Encode
    51  import List.Extra
    52  import Maybe.Extra
    53  import Message.Effects exposing (Effect(..), toHtmlID)
    54  import Message.Message exposing (DomID(..), Message(..))
    55  import Routes exposing (Highlight(..), StepID, showHighlight)
    56  import StrictEvents
    57  import Time
    58  import Tooltip
    59  import Url
    60  import Views.DictView as DictView
    61  import Views.Icon as Icon
    62  import Views.Spinner as Spinner
    63  
    64  
    65  init :
    66      Highlight
    67      -> Concourse.BuildResources
    68      -> Concourse.BuildPlan
    69      -> StepTreeModel
    70  init hl resources ({ id, step } as plan) =
    71      case step of
    72          Concourse.BuildStepTask name ->
    73              constructStep id name
    74                  |> initBottom hl resources plan Task
    75  
    76          Concourse.BuildStepCheck name ->
    77              constructStep id name
    78                  |> initBottom hl resources plan Check
    79  
    80          Concourse.BuildStepGet name version ->
    81              constructStep id name
    82                  |> setupGetStep resources name version
    83                  |> initBottom hl resources plan Get
    84  
    85          Concourse.BuildStepPut name ->
    86              constructStep id name
    87                  |> initBottom hl resources plan Put
    88  
    89          Concourse.BuildStepArtifactInput name ->
    90              constructStep id name
    91                  |> initBottom hl resources plan ArtifactInput
    92  
    93          Concourse.BuildStepArtifactOutput name ->
    94              constructStep id name
    95                  |> initBottom hl resources plan ArtifactOutput
    96  
    97          Concourse.BuildStepSetPipeline name ->
    98              constructStep id name
    99                  |> initBottom hl resources plan SetPipeline
   100  
   101          Concourse.BuildStepLoadVar name ->
   102              constructStep id name
   103                  |> initBottom hl resources plan LoadVar
   104  
   105          Concourse.BuildStepAggregate plans ->
   106              initMultiStep hl resources id Aggregate plans Nothing
   107  
   108          Concourse.BuildStepInParallel plans ->
   109              initMultiStep hl resources id InParallel plans Nothing
   110  
   111          Concourse.BuildStepDo plans ->
   112              initMultiStep hl resources id Do plans Nothing
   113  
   114          Concourse.BuildStepAcross { vars, steps } ->
   115              let
   116                  ( values, plans ) =
   117                      List.unzip steps
   118              in
   119              constructStep id (String.join ", " vars)
   120                  |> (\s ->
   121                          { s
   122                              | expandedHeaders =
   123                                  plans
   124                                      |> List.indexedMap (\i p -> ( i, planIsHighlighted hl p ))
   125                                      |> List.filter Tuple.second
   126                                      |> Dict.fromList
   127                          }
   128                     )
   129                  |> Just
   130                  |> initMultiStep hl resources id (Across id vars values) (Array.fromList plans)
   131                  |> (\model ->
   132                          List.foldl
   133                              (\plan_ ->
   134                                  updateAt plan_.id (\s -> { s | expanded = True })
   135                              )
   136                              model
   137                              plans
   138                     )
   139  
   140          Concourse.BuildStepRetry plans ->
   141              constructStep id "retry"
   142                  |> (\s -> { s | tabFocus = startingTab hl (Array.toList plans) })
   143                  |> Just
   144                  |> initMultiStep hl resources id (Retry id) plans
   145  
   146          Concourse.BuildStepOnSuccess hookedPlan ->
   147              initHookedStep hl resources OnSuccess hookedPlan
   148  
   149          Concourse.BuildStepOnFailure hookedPlan ->
   150              initHookedStep hl resources OnFailure hookedPlan
   151  
   152          Concourse.BuildStepOnAbort hookedPlan ->
   153              initHookedStep hl resources OnAbort hookedPlan
   154  
   155          Concourse.BuildStepOnError hookedPlan ->
   156              initHookedStep hl resources OnError hookedPlan
   157  
   158          Concourse.BuildStepEnsure hookedPlan ->
   159              initHookedStep hl resources Ensure hookedPlan
   160  
   161          Concourse.BuildStepTry subPlan ->
   162              initWrappedStep hl resources Try subPlan
   163  
   164          Concourse.BuildStepTimeout subPlan ->
   165              initWrappedStep hl resources Timeout subPlan
   166  
   167  
   168  setImageCheck : StepID -> Concourse.BuildPlan -> StepTreeModel -> StepTreeModel
   169  setImageCheck stepId subPlan model =
   170      let
   171          sub =
   172              init model.highlight model.resources subPlan
   173      in
   174      { model
   175          | steps =
   176              Dict.union sub.steps model.steps
   177                  |> Dict.update stepId (Maybe.map (\step -> { step | imageCheck = Just sub.tree }))
   178      }
   179  
   180  
   181  setImageGet : StepID -> Concourse.BuildPlan -> StepTreeModel -> StepTreeModel
   182  setImageGet stepId subPlan model =
   183      let
   184          sub =
   185              init model.highlight model.resources subPlan
   186      in
   187      { model
   188          | steps =
   189              Dict.union sub.steps model.steps
   190                  |> Dict.update stepId (Maybe.map (\step -> { step | imageGet = Just sub.tree }))
   191      }
   192  
   193  
   194  planIsHighlighted : Highlight -> Concourse.BuildPlan -> Bool
   195  planIsHighlighted hl plan =
   196      case hl of
   197          HighlightNothing ->
   198              False
   199  
   200          HighlightLine stepID _ ->
   201              planContainsID stepID plan
   202  
   203          HighlightRange stepID _ _ ->
   204              planContainsID stepID plan
   205  
   206  
   207  planContainsID : StepID -> Concourse.BuildPlan -> Bool
   208  planContainsID stepID plan =
   209      plan |> Concourse.mapBuildPlan .id |> List.member stepID
   210  
   211  
   212  startingTab : Highlight -> List Concourse.BuildPlan -> TabFocus
   213  startingTab hl plans =
   214      let
   215          idx =
   216              case hl of
   217                  HighlightNothing ->
   218                      Nothing
   219  
   220                  HighlightLine stepID _ ->
   221                      plans |> List.Extra.findIndex (planContainsID stepID)
   222  
   223                  HighlightRange stepID _ _ ->
   224                      plans |> List.Extra.findIndex (planContainsID stepID)
   225      in
   226      case idx of
   227          Nothing ->
   228              Auto
   229  
   230          Just tab ->
   231              Manual tab
   232  
   233  
   234  initBottom : Highlight -> Concourse.BuildResources -> Concourse.BuildPlan -> (StepID -> StepTree) -> Step -> StepTreeModel
   235  initBottom hl resources plan construct step =
   236      { tree = construct plan.id
   237      , steps = Dict.singleton plan.id (expand plan hl step)
   238      , highlight = hl
   239      , resources = resources
   240      }
   241  
   242  
   243  initMultiStep :
   244      Highlight
   245      -> Concourse.BuildResources
   246      -> StepID
   247      -> (Array StepTree -> StepTree)
   248      -> Array Concourse.BuildPlan
   249      -> Maybe Step
   250      -> StepTreeModel
   251  initMultiStep hl resources stepId constructor plans rootStep =
   252      let
   253          inited =
   254              Array.map (init hl resources) plans
   255  
   256          trees =
   257              Array.map .tree inited
   258  
   259          selfFoci =
   260              case rootStep of
   261                  Nothing ->
   262                      Dict.empty
   263  
   264                  Just step ->
   265                      Dict.singleton stepId step
   266      in
   267      { tree = constructor trees
   268      , steps =
   269          inited
   270              |> Array.map .steps
   271              |> Array.foldr Dict.union selfFoci
   272      , highlight = hl
   273      , resources = resources
   274      }
   275  
   276  
   277  constructStep : StepID -> StepName -> Step
   278  constructStep stepId name =
   279      { id = stepId
   280      , name = name
   281      , state = StepStatePending
   282      , log = Ansi.Log.init Ansi.Log.Cooked
   283      , error = Nothing
   284      , expanded = False
   285      , version = Nothing
   286      , metadata = []
   287      , changed = False
   288      , timestamps = Dict.empty
   289      , initialize = Nothing
   290      , start = Nothing
   291      , finish = Nothing
   292      , tabFocus = Auto
   293      , expandedHeaders = Dict.empty
   294      , initializationExpanded = False
   295      , imageCheck = Nothing
   296      , imageGet = Nothing
   297      }
   298  
   299  
   300  expand : Concourse.BuildPlan -> Highlight -> Step -> Step
   301  expand plan hl step =
   302      { step
   303          | expanded =
   304              case hl of
   305                  HighlightNothing ->
   306                      False
   307  
   308                  HighlightLine stepID _ ->
   309                      List.member stepID (Concourse.mapBuildPlan .id plan)
   310  
   311                  HighlightRange stepID _ _ ->
   312                      List.member stepID (Concourse.mapBuildPlan .id plan)
   313      }
   314  
   315  
   316  initWrappedStep :
   317      Highlight
   318      -> Concourse.BuildResources
   319      -> (StepTree -> StepTree)
   320      -> Concourse.BuildPlan
   321      -> StepTreeModel
   322  initWrappedStep hl resources create plan =
   323      let
   324          { tree, steps } =
   325              init hl resources plan
   326      in
   327      { tree = create tree
   328      , steps = steps
   329      , highlight = hl
   330      , resources = resources
   331      }
   332  
   333  
   334  initHookedStep :
   335      Highlight
   336      -> Concourse.BuildResources
   337      -> (HookedStep -> StepTree)
   338      -> Concourse.HookedPlan
   339      -> StepTreeModel
   340  initHookedStep hl resources create hookedPlan =
   341      let
   342          stepModel =
   343              init hl resources hookedPlan.step
   344  
   345          hookModel =
   346              init hl resources hookedPlan.hook
   347      in
   348      { tree = create { step = stepModel.tree, hook = hookModel.tree }
   349      , steps = Dict.union stepModel.steps hookModel.steps
   350      , highlight = hl
   351      , resources = resources
   352      }
   353  
   354  
   355  setupGetStep : Concourse.BuildResources -> StepName -> Maybe Version -> Step -> Step
   356  setupGetStep resources name version step =
   357      { step
   358          | version = version
   359          , changed = isFirstOccurrence resources.inputs name
   360      }
   361  
   362  
   363  isFirstOccurrence : List Concourse.BuildResourcesInput -> StepName -> Bool
   364  isFirstOccurrence resources step =
   365      case resources of
   366          [] ->
   367              False
   368  
   369          { name, firstOccurrence } :: rest ->
   370              if name == step then
   371                  firstOccurrence
   372  
   373              else
   374                  isFirstOccurrence rest step
   375  
   376  
   377  finished : StepTreeModel -> StepTreeModel
   378  finished model =
   379      { model | steps = Dict.map (always finishStep) model.steps }
   380  
   381  
   382  finishStep : Step -> Step
   383  finishStep step =
   384      let
   385          newState =
   386              case step.state of
   387                  StepStateRunning ->
   388                      StepStateInterrupted
   389  
   390                  StepStatePending ->
   391                      StepStateCancelled
   392  
   393                  otherwise ->
   394                      otherwise
   395      in
   396      { step | state = newState }
   397  
   398  
   399  toggleStep : StepID -> StepTreeModel -> ( StepTreeModel, List Effect )
   400  toggleStep id root =
   401      ( updateAt id (\step -> { step | expanded = not step.expanded }) root
   402      , []
   403      )
   404  
   405  
   406  toggleStepInitialization : StepID -> StepTreeModel -> ( StepTreeModel, List Effect )
   407  toggleStepInitialization id root =
   408      ( updateAt id (\step -> { step | initializationExpanded = not step.initializationExpanded }) root
   409      , []
   410      )
   411  
   412  
   413  toggleStepSubHeader : StepID -> Int -> StepTreeModel -> ( StepTreeModel, List Effect )
   414  toggleStepSubHeader id i root =
   415      ( updateAt id (toggleSubHeaderExpanded i) root, [] )
   416  
   417  
   418  switchTab : StepID -> Int -> StepTreeModel -> ( StepTreeModel, List Effect )
   419  switchTab id tab root =
   420      ( updateAt id (focusTabbed tab) root, [] )
   421  
   422  
   423  setHighlight : StepID -> Int -> StepTreeModel -> ( StepTreeModel, List Effect )
   424  setHighlight id line root =
   425      let
   426          hl =
   427              HighlightLine id line
   428      in
   429      ( { root | highlight = hl }, [ ModifyUrl (showHighlight hl) ] )
   430  
   431  
   432  extendHighlight : StepID -> Int -> StepTreeModel -> ( StepTreeModel, List Effect )
   433  extendHighlight id line root =
   434      let
   435          hl =
   436              case root.highlight of
   437                  HighlightNothing ->
   438                      HighlightLine id line
   439  
   440                  HighlightLine currentID currentLine ->
   441                      if currentID == id then
   442                          if currentLine < line then
   443                              HighlightRange id currentLine line
   444  
   445                          else
   446                              HighlightRange id line currentLine
   447  
   448                      else
   449                          HighlightLine id line
   450  
   451                  HighlightRange currentID currentLine _ ->
   452                      if currentID == id then
   453                          if currentLine < line then
   454                              HighlightRange id currentLine line
   455  
   456                          else
   457                              HighlightRange id line currentLine
   458  
   459                      else
   460                          HighlightLine id line
   461      in
   462      ( { root | highlight = hl }, [ ModifyUrl (showHighlight hl) ] )
   463  
   464  
   465  view :
   466      { timeZone : Time.Zone, hovered : HoverState.HoverState }
   467      -> StepTreeModel
   468      -> Html Message
   469  view session model =
   470      viewTree session model model.tree 0
   471  
   472  
   473  assumeStep : StepTreeModel -> StepID -> (Step -> Html Message) -> Html Message
   474  assumeStep model stepId f =
   475      case Dict.get stepId model.steps of
   476          Nothing ->
   477              -- should be impossible
   478              Html.text ""
   479  
   480          Just step ->
   481              f step
   482  
   483  
   484  viewTree :
   485      { timeZone : Time.Zone, hovered : HoverState.HoverState }
   486      -> StepTreeModel
   487      -> StepTree
   488      -> Int
   489      -> Html Message
   490  viewTree session model tree depth =
   491      case tree of
   492          Task stepId ->
   493              viewStep model session depth stepId StepHeaderTask
   494  
   495          Check stepId ->
   496              viewStep model session depth stepId StepHeaderCheck
   497  
   498          Get stepId ->
   499              viewStep model session depth stepId StepHeaderGet
   500  
   501          Put stepId ->
   502              viewStep model session depth stepId StepHeaderPut
   503  
   504          ArtifactInput stepId ->
   505              viewStep model session depth stepId StepHeaderGet
   506  
   507          ArtifactOutput stepId ->
   508              viewStep model session depth stepId StepHeaderPut
   509  
   510          SetPipeline stepId ->
   511              viewStep model session depth stepId StepHeaderSetPipeline
   512  
   513          LoadVar stepId ->
   514              viewStep model session depth stepId StepHeaderLoadVar
   515  
   516          Try subTree ->
   517              viewTree session model subTree depth
   518  
   519          Across stepId vars vals substeps ->
   520              assumeStep model stepId <|
   521                  \step ->
   522                      viewStepWithBody model session depth step StepHeaderAcross <|
   523                          (vals
   524                              |> List.indexedMap
   525                                  (\i vals_ ->
   526                                      ( vals_
   527                                      , Dict.get stepId model.steps
   528                                          |> Maybe.andThen (.expandedHeaders >> Dict.get i)
   529                                          |> Maybe.withDefault False
   530                                      , substeps |> Array.get i
   531                                      )
   532                                  )
   533                              |> List.filterMap
   534                                  (\( vals_, expanded_, substep ) ->
   535                                      case substep of
   536                                          Nothing ->
   537                                              -- impossible, but need to get rid of the Maybe
   538                                              Nothing
   539  
   540                                          Just substep_ ->
   541                                              Just ( vals_, expanded_, substep_ )
   542                                  )
   543                              |> List.indexedMap
   544                                  (\i ( vals_, expanded_, substep ) ->
   545                                      let
   546                                          keyVals =
   547                                              List.map2 Tuple.pair vars vals_
   548                                      in
   549                                      viewAcrossStepSubHeader model session step.id i keyVals expanded_ (depth + 1) substep
   550                                  )
   551                          )
   552  
   553          Retry stepId steps ->
   554              assumeStep model stepId <|
   555                  \{ tabFocus } ->
   556                      let
   557                          activeTab =
   558                              case tabFocus of
   559                                  Manual i ->
   560                                      i
   561  
   562                                  Auto ->
   563                                      Maybe.withDefault 0 (lastActive model steps)
   564                      in
   565                      Html.div [ class "retry" ]
   566                          [ Html.ul
   567                              (class "retry-tabs" :: Styles.retryTabList)
   568                              (Array.toList <| Array.indexedMap (viewRetryTab session model stepId activeTab) steps)
   569                          , case Array.get activeTab steps of
   570                              Just step ->
   571                                  viewTree session model step depth
   572  
   573                              Nothing ->
   574                                  -- impossible (bogus tab selected)
   575                                  Html.text ""
   576                          ]
   577  
   578          Timeout subTree ->
   579              viewTree session model subTree depth
   580  
   581          Aggregate trees ->
   582              Html.div [ class "aggregate" ]
   583                  (Array.toList <| Array.map (viewSeq session model depth) trees)
   584  
   585          InParallel trees ->
   586              Html.div [ class "parallel" ]
   587                  (Array.toList <| Array.map (viewSeq session model depth) trees)
   588  
   589          Do trees ->
   590              Html.div [ class "do" ]
   591                  (Array.toList <| Array.map (viewSeq session model depth) trees)
   592  
   593          OnSuccess { step, hook } ->
   594              viewHooked session "success" model depth step hook
   595  
   596          OnFailure { step, hook } ->
   597              viewHooked session "failure" model depth step hook
   598  
   599          OnAbort { step, hook } ->
   600              viewHooked session "abort" model depth step hook
   601  
   602          OnError { step, hook } ->
   603              viewHooked session "error" model depth step hook
   604  
   605          Ensure { step, hook } ->
   606              viewHooked session "ensure" model depth step hook
   607  
   608  
   609  viewAcrossStepSubHeader :
   610      StepTreeModel
   611      -> { timeZone : Time.Zone, hovered : HoverState.HoverState }
   612      -> StepID
   613      -> Int
   614      -> List ( String, JsonValue )
   615      -> Bool
   616      -> Int
   617      -> StepTree
   618      -> Html Message
   619  viewAcrossStepSubHeader model session stepID subHeaderIdx keyVals expanded depth subtree =
   620      let
   621          state =
   622              mostSevereStepState model subtree
   623      in
   624      Html.div
   625          [ classList
   626              [ ( "build-step", True )
   627              , ( "inactive", not <| isActive state )
   628              ]
   629          , style "margin-top" "10px"
   630          ]
   631          [ Html.div
   632              ([ class "header"
   633               , class "sub-header"
   634               , onClick <| Click <| StepSubHeader stepID subHeaderIdx
   635               , style "z-index" <| String.fromInt <| max (maxDepth - depth) 1
   636               ]
   637                  ++ Styles.stepHeader state
   638              )
   639              [ Html.div
   640                  [ style "display" "flex" ]
   641                  [ viewAcrossStepSubHeaderLabels keyVals ]
   642              , Html.div
   643                  [ style "display" "flex" ]
   644                  [ viewStepStateWithoutTooltip state ]
   645              ]
   646          , if expanded then
   647              Html.div
   648                  [ class "step-body"
   649                  , class "clearfix"
   650                  , style "padding-bottom" "0"
   651                  ]
   652                  [ viewTree session model subtree (depth + 1) ]
   653  
   654            else
   655              Html.text ""
   656          ]
   657  
   658  
   659  viewAcrossStepSubHeaderLabels : List ( String, JsonValue ) -> Html Message
   660  viewAcrossStepSubHeaderLabels keyVals =
   661      Html.div Styles.acrossStepSubHeaderLabel
   662          (keyVals
   663              |> List.concatMap
   664                  (\( k, v ) ->
   665                      viewAcrossStepSubHeaderKeyValue k v
   666                  )
   667          )
   668  
   669  
   670  viewAcrossStepSubHeaderKeyValue : String -> JsonValue -> List (Html Message)
   671  viewAcrossStepSubHeaderKeyValue key val =
   672      let
   673          keyValueSpan text =
   674              [ Html.span
   675                  [ style "display" "inline-block"
   676                  , style "margin-right" "10px"
   677                  ]
   678                  [ Html.span [ style "color" Colors.pending ]
   679                      [ Html.text <| key ++ ": " ]
   680                  , Html.text text
   681                  ]
   682              ]
   683      in
   684      case val of
   685          JsonString s ->
   686              keyValueSpan s
   687  
   688          JsonNumber n ->
   689              keyValueSpan <| String.fromFloat n
   690  
   691          JsonRaw v ->
   692              keyValueSpan <| Json.Encode.encode 0 v
   693  
   694          JsonArray l ->
   695              List.indexedMap
   696                  (\i v ->
   697                      let
   698                          subKey =
   699                              key ++ "[" ++ String.fromInt i ++ "]"
   700                      in
   701                      viewAcrossStepSubHeaderKeyValue subKey v
   702                  )
   703                  l
   704                  |> List.concat
   705  
   706          JsonObject o ->
   707              List.concatMap
   708                  (\( k, v ) ->
   709                      let
   710                          subKey =
   711                              key ++ "." ++ k
   712                      in
   713                      viewAcrossStepSubHeaderKeyValue subKey v
   714                  )
   715                  o
   716  
   717  
   718  viewRetryTab :
   719      { r | hovered : HoverState.HoverState }
   720      -> StepTreeModel
   721      -> StepID
   722      -> Int
   723      -> Int
   724      -> StepTree
   725      -> Html Message
   726  viewRetryTab { hovered } model stepId activeTab tab step =
   727      let
   728          label =
   729              String.fromInt (tab + 1)
   730  
   731          active =
   732              treeIsActive model step
   733  
   734          current =
   735              activeTab == tab
   736      in
   737      Html.li
   738          ([ classList
   739              [ ( "current", current )
   740              , ( "inactive", not active )
   741              ]
   742           , onMouseEnter <| Hover <| Just <| StepTab stepId tab
   743           , onMouseLeave <| Hover Nothing
   744           , onClick <| Click <| StepTab stepId tab
   745           ]
   746              ++ Styles.tab
   747                  { isHovered = HoverState.isHovered (StepTab stepId tab) hovered
   748                  , isCurrent = current
   749                  , isStarted = active
   750                  }
   751          )
   752          [ Html.text label ]
   753  
   754  
   755  viewSeq : { timeZone : Time.Zone, hovered : HoverState.HoverState } -> StepTreeModel -> Int -> StepTree -> Html Message
   756  viewSeq session model depth tree =
   757      Html.div [ class "seq" ] [ viewTree session model tree depth ]
   758  
   759  
   760  viewHooked : { timeZone : Time.Zone, hovered : HoverState.HoverState } -> String -> StepTreeModel -> Int -> StepTree -> StepTree -> Html Message
   761  viewHooked session name model depth step hook =
   762      Html.div [ class "hooked" ]
   763          [ Html.div [ class "step" ] [ viewTree session model step depth ]
   764          , Html.div [ class "children" ]
   765              [ Html.div [ class ("hook hook-" ++ name) ] [ viewTree session model hook depth ]
   766              ]
   767          ]
   768  
   769  
   770  maxDepth : Int
   771  maxDepth =
   772      10
   773  
   774  
   775  viewStepWithBody :
   776      StepTreeModel
   777      -> { timeZone : Time.Zone, hovered : HoverState.HoverState }
   778      -> Int
   779      -> Step
   780      -> StepHeaderType
   781      -> List (Html Message)
   782      -> Html Message
   783  viewStepWithBody model session depth step headerType body =
   784      Html.div
   785          [ classList
   786              [ ( "build-step", True )
   787              , ( "inactive", not <| isActive step.state )
   788              ]
   789          , attribute "data-step-name" step.name
   790          ]
   791          [ Html.div
   792              ([ class "header"
   793               , onClick <| Click <| StepHeader step.id
   794               , style "z-index" <| String.fromInt <| max (maxDepth - depth) 1
   795               ]
   796                  ++ Styles.stepHeader step.state
   797              )
   798              [ Html.div
   799                  [ style "display" "flex" ]
   800                  [ viewStepHeaderLabel headerType step.changed step.id
   801                  , Html.h3 [] [ Html.text step.name ]
   802                  ]
   803              , Html.div
   804                  [ style "display" "flex" ]
   805                  [ viewVersion step.version
   806                  , case Maybe.Extra.or step.imageCheck step.imageGet of
   807                      Just _ ->
   808                          viewInitializationToggle step
   809  
   810                      Nothing ->
   811                          Html.text ""
   812                  , viewStepState step.state step.id
   813                  ]
   814              ]
   815          , if step.initializationExpanded then
   816              Html.div (class "sub-steps" :: Styles.imageSteps)
   817                  [ case step.imageCheck of
   818                      Just subTree ->
   819                          Html.div [ class "seq" ]
   820                              [ viewTree session model subTree (depth + 1)
   821                              ]
   822  
   823                      Nothing ->
   824                          Html.text ""
   825                  , case step.imageGet of
   826                      Just subTree ->
   827                          Html.div [ class "seq" ]
   828                              [ viewTree session model subTree (depth + 1)
   829                              ]
   830  
   831                      Nothing ->
   832                          Html.text ""
   833                  ]
   834  
   835            else
   836              Html.text ""
   837          , if step.expanded then
   838              Html.div
   839                  [ class "step-body"
   840                  , class "clearfix"
   841                  ]
   842                  ([ viewMetadata step.metadata
   843                   , Html.pre [ class "timestamped-logs" ] <|
   844                      viewLogs step.log step.timestamps model.highlight session.timeZone step.id
   845                   , case step.error of
   846                      Nothing ->
   847                          Html.span [] []
   848  
   849                      Just msg ->
   850                          Html.span [ class "error" ] [ Html.pre [] [ Html.text msg ] ]
   851                   ]
   852                      ++ body
   853                  )
   854  
   855            else
   856              Html.text ""
   857          ]
   858  
   859  
   860  viewInitializationToggle : Step -> Html Message
   861  viewInitializationToggle step =
   862      let
   863          domId =
   864              StepInitialization step.id
   865      in
   866      Html.h3
   867          ([ StrictEvents.onLeftClickStopPropagation (Click domId)
   868           , onMouseLeave <| Hover Nothing
   869           , onMouseEnter <| Hover (Just domId)
   870           , id (toHtmlID domId)
   871           ]
   872              ++ Styles.initializationToggle step.initializationExpanded
   873          )
   874          [ Icon.icon
   875              { sizePx = 14
   876              , image = Assets.CogsIcon
   877              }
   878              [ style "margin" "7px 0"
   879              , style "background-size" "contain"
   880              ]
   881          ]
   882  
   883  
   884  viewStep : StepTreeModel -> { timeZone : Time.Zone, hovered : HoverState.HoverState } -> Int -> StepID -> StepHeaderType -> Html Message
   885  viewStep model session depth stepId headerType =
   886      assumeStep model stepId <|
   887          \step ->
   888              viewStepWithBody model session depth step headerType []
   889  
   890  
   891  viewLogs :
   892      Ansi.Log.Model
   893      -> Dict Int Time.Posix
   894      -> Highlight
   895      -> Time.Zone
   896      -> String
   897      -> List (Html Message)
   898  viewLogs { lines } timestamps hl timeZone id =
   899      Array.toList <|
   900          Array.indexedMap
   901              (\idx line ->
   902                  viewTimestampedLine
   903                      { timestamps = timestamps
   904                      , highlight = hl
   905                      , id = id
   906                      , lineNo = idx + 1
   907                      , line = line
   908                      , timeZone = timeZone
   909                      }
   910              )
   911              lines
   912  
   913  
   914  viewTimestampedLine :
   915      { timestamps : Dict Int Time.Posix
   916      , highlight : Highlight
   917      , id : StepID
   918      , lineNo : Int
   919      , line : Ansi.Log.Line
   920      , timeZone : Time.Zone
   921      }
   922      -> Html Message
   923  viewTimestampedLine { timestamps, highlight, id, lineNo, line, timeZone } =
   924      let
   925          highlighted =
   926              case highlight of
   927                  HighlightNothing ->
   928                      False
   929  
   930                  HighlightLine hlId hlLine ->
   931                      hlId == id && hlLine == lineNo
   932  
   933                  HighlightRange hlId hlLine1 hlLine2 ->
   934                      hlId == id && lineNo >= hlLine1 && lineNo <= hlLine2
   935  
   936          ts =
   937              Dict.get lineNo timestamps
   938      in
   939      Html.tr
   940          [ classList
   941              [ ( "timestamped-line", True )
   942              , ( "highlighted-line", highlighted )
   943              ]
   944          , Html.Attributes.id <| id ++ ":" ++ String.fromInt lineNo
   945          ]
   946          [ viewTimestamp
   947              { id = id
   948              , lineNo = lineNo
   949              , date = ts
   950              , timeZone = timeZone
   951              }
   952          , viewLine line
   953          ]
   954  
   955  
   956  viewLine : Ansi.Log.Line -> Html Message
   957  viewLine line =
   958      Html.td [ class "timestamped-content" ]
   959          [ Ansi.Log.viewLine line
   960          ]
   961  
   962  
   963  viewTimestamp :
   964      { id : String
   965      , lineNo : Int
   966      , date : Maybe Time.Posix
   967      , timeZone : Time.Zone
   968      }
   969      -> Html Message
   970  viewTimestamp { id, lineNo, date, timeZone } =
   971      Html.a
   972          [ href (showHighlight (HighlightLine id lineNo))
   973          , StrictEvents.onLeftClickOrShiftLeftClick
   974              (SetHighlight id lineNo)
   975              (ExtendHighlight id lineNo)
   976          ]
   977          [ case date of
   978              Just d ->
   979                  Html.td
   980                      [ class "timestamp" ]
   981                      [ Html.text <|
   982                          DateFormat.format
   983                              [ DateFormat.hourMilitaryFixed
   984                              , DateFormat.text ":"
   985                              , DateFormat.minuteFixed
   986                              , DateFormat.text ":"
   987                              , DateFormat.secondFixed
   988                              ]
   989                              timeZone
   990                              d
   991                      ]
   992  
   993              _ ->
   994                  Html.td [ class "timestamp placeholder" ] []
   995          ]
   996  
   997  
   998  viewVersion : Maybe Version -> Html Message
   999  viewVersion version =
  1000      Maybe.withDefault Dict.empty version
  1001          |> Dict.map (always Html.text)
  1002          |> DictView.view []
  1003  
  1004  
  1005  viewMetadata : List MetadataField -> Html Message
  1006  viewMetadata meta =
  1007      let
  1008          val value =
  1009              case Url.fromString value of
  1010                  Just _ ->
  1011                      Html.a
  1012                          [ href value
  1013                          , target "_blank"
  1014                          , style "text-decoration-line" "underline"
  1015                          ]
  1016                          [ Html.text value ]
  1017  
  1018                  Nothing ->
  1019                      Html.text value
  1020  
  1021          tr { name, value } =
  1022              Html.tr []
  1023                  [ Html.td (Styles.metadataCell Styles.Key)
  1024                      [ Html.text name ]
  1025                  , Html.td (Styles.metadataCell Styles.Value)
  1026                      [ val value ]
  1027                  ]
  1028      in
  1029      if meta == [] then
  1030          Html.text ""
  1031  
  1032      else
  1033          meta
  1034              |> List.map tr
  1035              |> Html.table Styles.metadataTable
  1036  
  1037  
  1038  viewStepStateWithoutTooltip : StepState -> Html Message
  1039  viewStepStateWithoutTooltip state =
  1040      let
  1041          attributes =
  1042              [ style "position" "relative" ]
  1043      in
  1044      case state of
  1045          StepStateRunning ->
  1046              Spinner.spinner
  1047                  { sizePx = 14
  1048                  , margin = "7px"
  1049                  }
  1050  
  1051          StepStatePending ->
  1052              Icon.icon
  1053                  { sizePx = 28
  1054                  , image = Assets.PendingIcon
  1055                  }
  1056                  (attribute "data-step-state" "pending"
  1057                      :: Styles.stepStatusIcon
  1058                      ++ attributes
  1059                  )
  1060  
  1061          StepStateInterrupted ->
  1062              Icon.icon
  1063                  { sizePx = 28
  1064                  , image = Assets.InterruptedIcon
  1065                  }
  1066                  (attribute "data-step-state" "interrupted"
  1067                      :: Styles.stepStatusIcon
  1068                      ++ attributes
  1069                  )
  1070  
  1071          StepStateCancelled ->
  1072              Icon.icon
  1073                  { sizePx = 28
  1074                  , image = Assets.CancelledIcon
  1075                  }
  1076                  (attribute "data-step-state" "cancelled"
  1077                      :: Styles.stepStatusIcon
  1078                      ++ attributes
  1079                  )
  1080  
  1081          StepStateSucceeded ->
  1082              Icon.icon
  1083                  { sizePx = 28
  1084                  , image = Assets.SuccessCheckIcon
  1085                  }
  1086                  (attribute "data-step-state" "succeeded"
  1087                      :: Styles.stepStatusIcon
  1088                      ++ attributes
  1089                  )
  1090  
  1091          StepStateFailed ->
  1092              Icon.icon
  1093                  { sizePx = 28
  1094                  , image = Assets.FailureTimesIcon
  1095                  }
  1096                  (attribute "data-step-state" "failed"
  1097                      :: Styles.stepStatusIcon
  1098                      ++ attributes
  1099                  )
  1100  
  1101          StepStateErrored ->
  1102              Icon.icon
  1103                  { sizePx = 28
  1104                  , image = Assets.ExclamationTriangleIcon
  1105                  }
  1106                  (attribute "data-step-state" "errored"
  1107                      :: Styles.stepStatusIcon
  1108                      ++ attributes
  1109                  )
  1110  
  1111  
  1112  viewStepState : StepState -> StepID -> Html Message
  1113  viewStepState state stepID =
  1114      let
  1115          attributes =
  1116              [ onMouseLeave <| Hover Nothing
  1117              , onMouseEnter <| Hover (Just (StepState stepID))
  1118              , id <| toHtmlID <| StepState stepID
  1119              , style "position" "relative"
  1120              ]
  1121      in
  1122      case state of
  1123          StepStateRunning ->
  1124              Spinner.spinner
  1125                  { sizePx = 14
  1126                  , margin = "7px"
  1127                  }
  1128  
  1129          StepStatePending ->
  1130              Icon.icon
  1131                  { sizePx = 28
  1132                  , image = Assets.PendingIcon
  1133                  }
  1134                  (attribute "data-step-state" "pending"
  1135                      :: Styles.stepStatusIcon
  1136                      ++ attributes
  1137                  )
  1138  
  1139          StepStateInterrupted ->
  1140              Icon.icon
  1141                  { sizePx = 28
  1142                  , image = Assets.InterruptedIcon
  1143                  }
  1144                  (attribute "data-step-state" "interrupted"
  1145                      :: Styles.stepStatusIcon
  1146                      ++ attributes
  1147                  )
  1148  
  1149          StepStateCancelled ->
  1150              Icon.icon
  1151                  { sizePx = 28
  1152                  , image = Assets.CancelledIcon
  1153                  }
  1154                  (attribute "data-step-state" "cancelled"
  1155                      :: Styles.stepStatusIcon
  1156                      ++ attributes
  1157                  )
  1158  
  1159          StepStateSucceeded ->
  1160              Icon.icon
  1161                  { sizePx = 28
  1162                  , image = Assets.SuccessCheckIcon
  1163                  }
  1164                  (attribute "data-step-state" "succeeded"
  1165                      :: Styles.stepStatusIcon
  1166                      ++ attributes
  1167                  )
  1168  
  1169          StepStateFailed ->
  1170              Icon.icon
  1171                  { sizePx = 28
  1172                  , image = Assets.FailureTimesIcon
  1173                  }
  1174                  (attribute "data-step-state" "failed"
  1175                      :: Styles.stepStatusIcon
  1176                      ++ attributes
  1177                  )
  1178  
  1179          StepStateErrored ->
  1180              Icon.icon
  1181                  { sizePx = 28
  1182                  , image = Assets.ExclamationTriangleIcon
  1183                  }
  1184                  (attribute "data-step-state" "errored"
  1185                      :: Styles.stepStatusIcon
  1186                      ++ attributes
  1187                  )
  1188  
  1189  
  1190  viewStepHeaderLabel : StepHeaderType -> Bool -> StepID -> Html Message
  1191  viewStepHeaderLabel headerType changed stepID =
  1192      let
  1193          eventHandlers =
  1194              case ( headerType, changed ) of
  1195                  ( StepHeaderGet, True ) ->
  1196                      [ onMouseLeave <| Hover Nothing
  1197                      , onMouseEnter <| Hover <| Just <| ChangedStepLabel stepID "new version"
  1198                      ]
  1199  
  1200                  ( StepHeaderSetPipeline, True ) ->
  1201                      [ onMouseLeave <| Hover Nothing
  1202                      , onMouseEnter <| Hover <| Just <| ChangedStepLabel stepID "pipeline config changed"
  1203                      ]
  1204  
  1205                  _ ->
  1206                      []
  1207      in
  1208      Html.div
  1209          (id (toHtmlID <| ChangedStepLabel stepID "")
  1210              :: Styles.stepHeaderLabel changed
  1211              ++ eventHandlers
  1212          )
  1213          [ Html.text <|
  1214              case headerType of
  1215                  StepHeaderGet ->
  1216                      "get:"
  1217  
  1218                  StepHeaderPut ->
  1219                      "put:"
  1220  
  1221                  StepHeaderTask ->
  1222                      "task:"
  1223  
  1224                  StepHeaderCheck ->
  1225                      "check:"
  1226  
  1227                  StepHeaderSetPipeline ->
  1228                      "set_pipeline:"
  1229  
  1230                  StepHeaderLoadVar ->
  1231                      "load_var:"
  1232  
  1233                  StepHeaderAcross ->
  1234                      "across:"
  1235          ]
  1236  
  1237  
  1238  tooltip : StepTreeModel -> { a | hovered : HoverState.HoverState } -> Maybe Tooltip.Tooltip
  1239  tooltip model { hovered } =
  1240      case hovered of
  1241          HoverState.Tooltip (ChangedStepLabel _ text) _ ->
  1242              Just
  1243                  { body =
  1244                      Html.div
  1245                          Styles.changedStepTooltip
  1246                          [ Html.text text ]
  1247                  , attachPosition =
  1248                      { direction = Tooltip.Top
  1249                      , alignment = Tooltip.Start
  1250                      }
  1251                  , arrow = Just { size = 5, color = Colors.tooltipBackground }
  1252                  }
  1253  
  1254          HoverState.Tooltip (StepInitialization _) _ ->
  1255              Just
  1256                  { body =
  1257                      Html.div
  1258                          Styles.changedStepTooltip
  1259                          [ Html.text "image fetching" ]
  1260                  , attachPosition =
  1261                      { direction = Tooltip.Top
  1262                      , alignment = Tooltip.End
  1263                      }
  1264                  , arrow = Just { size = 5, color = Colors.tooltipBackground }
  1265                  }
  1266  
  1267          HoverState.Tooltip (StepState id) _ ->
  1268              Dict.get id model.steps
  1269                  |> Maybe.map stepDurationTooltip
  1270  
  1271          _ ->
  1272              Nothing
  1273  
  1274  
  1275  stepDurationTooltip : Step -> Tooltip.Tooltip
  1276  stepDurationTooltip { state, initialize, start, finish } =
  1277      { body =
  1278          Html.div Styles.durationTooltip
  1279              [ case ( initialize, start, finish ) of
  1280                  ( Just initializedAt, Just startedAt, Just finishedAt ) ->
  1281                      let
  1282                          initDuration =
  1283                              Duration.between initializedAt startedAt
  1284  
  1285                          stepDuration =
  1286                              Duration.between startedAt finishedAt
  1287                      in
  1288                      DictView.view []
  1289                          (Dict.fromList
  1290                              [ ( "initialization"
  1291                                , Html.text (Duration.format initDuration)
  1292                                )
  1293                              , ( "step"
  1294                                , Html.text (Duration.format stepDuration)
  1295                                )
  1296                              ]
  1297                          )
  1298  
  1299                  _ ->
  1300                      Html.text (showStepState state)
  1301              ]
  1302      , attachPosition =
  1303          { direction = Tooltip.Top
  1304          , alignment = Tooltip.End
  1305          }
  1306      , arrow = Just { size = 5, color = Colors.tooltipBackground }
  1307      }