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

     1  module Build.Output.Output exposing
     2      ( filterHoverState
     3      , handleEnvelopes
     4      , handleStepTreeMsg
     5      , init
     6      , planAndResourcesFetched
     7      , view
     8      )
     9  
    10  import Ansi.Log
    11  import Api.Endpoints as Endpoints
    12  import Array
    13  import Build.Output.Models exposing (OutputModel, OutputState(..))
    14  import Build.StepTree.Models as StepTree
    15      exposing
    16          ( BuildEvent(..)
    17          , BuildEventEnvelope
    18          , Step
    19          , StepState(..)
    20          , StepTreeModel
    21          )
    22  import Build.StepTree.StepTree
    23  import Concourse
    24  import Concourse.BuildStatus
    25  import Dict
    26  import HoverState
    27  import Html exposing (Html)
    28  import Html.Attributes exposing (class)
    29  import Message.Effects exposing (Effect(..))
    30  import Message.Message exposing (DomID(..), Message(..))
    31  import Routes exposing (StepID)
    32  import Time
    33  import Views.LoadingIndicator as LoadingIndicator
    34  
    35  
    36  init : Routes.Highlight -> Concourse.Build -> ( OutputModel, List Effect )
    37  init highlight build =
    38      let
    39          outputState =
    40              if Concourse.BuildStatus.isRunning build.status then
    41                  StepsLiveUpdating
    42  
    43              else
    44                  StepsLoading
    45  
    46          model =
    47              { steps = Nothing
    48              , state = outputState
    49              , eventStreamUrlPath = Nothing
    50              , eventSourceOpened = False
    51              , highlight = highlight
    52              }
    53  
    54          fetch =
    55              if build.job /= Nothing then
    56                  [ FetchBuildPlanAndResources build.id ]
    57  
    58              else
    59                  [ FetchBuildPlan build.id ]
    60      in
    61      ( model, fetch )
    62  
    63  
    64  handleStepTreeMsg :
    65      (StepTreeModel -> ( StepTreeModel, List Effect ))
    66      -> OutputModel
    67      -> ( OutputModel, List Effect )
    68  handleStepTreeMsg action model =
    69      case model.steps of
    70          Just st ->
    71              let
    72                  ( newModel, effects ) =
    73                      action st
    74              in
    75              ( { model | steps = Just newModel }, effects )
    76  
    77          _ ->
    78              ( model, [] )
    79  
    80  
    81  planAndResourcesFetched :
    82      Concourse.BuildId
    83      -> ( Concourse.BuildPlan, Concourse.BuildResources )
    84      -> OutputModel
    85      -> ( OutputModel, List Effect )
    86  planAndResourcesFetched buildId ( plan, resources ) model =
    87      let
    88          url =
    89              Endpoints.BuildEventStream
    90                  |> Endpoints.Build buildId
    91                  |> Endpoints.toString []
    92      in
    93      ( { model
    94          | steps =
    95              Just
    96                  (Build.StepTree.StepTree.init
    97                      model.highlight
    98                      resources
    99                      plan
   100                  )
   101          , eventStreamUrlPath = Just url
   102        }
   103      , []
   104      )
   105  
   106  
   107  handleEnvelopes :
   108      List BuildEventEnvelope
   109      -> OutputModel
   110      -> ( OutputModel, List Effect )
   111  handleEnvelopes envelopes model =
   112      envelopes
   113          |> List.reverse
   114          |> List.foldr handleEnvelope ( model, [] )
   115  
   116  
   117  handleEnvelope :
   118      BuildEventEnvelope
   119      -> ( OutputModel, List Effect )
   120      -> ( OutputModel, List Effect )
   121  handleEnvelope { url, data } ( model, effects ) =
   122      if
   123          model.eventStreamUrlPath
   124              |> Maybe.map (\p -> String.endsWith p url)
   125              |> Maybe.withDefault False
   126      then
   127          handleEvent data ( model, effects )
   128  
   129      else
   130          ( model, effects )
   131  
   132  
   133  handleEvent :
   134      BuildEvent
   135      -> ( OutputModel, List Effect )
   136      -> ( OutputModel, List Effect )
   137  handleEvent event ( model, effects ) =
   138      case event of
   139          Opened ->
   140              ( { model | eventSourceOpened = True }
   141              , effects
   142              )
   143  
   144          Log origin output time ->
   145              ( updateStep origin.id (setRunning << appendStepLog output time) model
   146              , effects
   147              )
   148  
   149          SelectedWorker origin output time ->
   150              ( updateStep origin.id (setRunning << appendStepLog ("\u{001B}[1mselected worker: \u{001B}[0m" ++ output ++ "\n") time) model
   151              , effects
   152              )
   153  
   154          Error origin message time ->
   155              ( updateStep origin.id (setStepError message time) model
   156              , effects
   157              )
   158  
   159          InitializeTask origin time ->
   160              ( updateStep origin.id (setInitialize time) model
   161              , effects
   162              )
   163  
   164          StartTask origin time ->
   165              ( updateStep origin.id (setStart time) model
   166              , effects
   167              )
   168  
   169          FinishTask origin exitStatus time ->
   170              ( updateStep origin.id (finishStep (exitStatus == 0) (Just time)) model
   171              , effects
   172              )
   173  
   174          Initialize origin time ->
   175              ( updateStep origin.id (setInitialize time) model
   176              , effects
   177              )
   178  
   179          Start origin time ->
   180              ( updateStep origin.id (setStart time) model
   181              , effects
   182              )
   183  
   184          Finish origin time succeeded ->
   185              ( updateStep origin.id (finishStep succeeded (Just time)) model
   186              , effects
   187              )
   188  
   189          InitializeGet origin time ->
   190              ( updateStep origin.id (setInitialize time) model
   191              , effects
   192              )
   193  
   194          StartGet origin time ->
   195              ( updateStep origin.id (setStart time) model
   196              , effects
   197              )
   198  
   199          FinishGet origin exitStatus version metadata time ->
   200              ( updateStep origin.id (finishStep (exitStatus == 0) time << setResourceInfo version metadata) model
   201              , effects
   202              )
   203  
   204          InitializePut origin time ->
   205              ( updateStep origin.id (setInitialize time) model
   206              , effects
   207              )
   208  
   209          StartPut origin time ->
   210              ( updateStep origin.id (setStart time) model
   211              , effects
   212              )
   213  
   214          FinishPut origin exitStatus version metadata time ->
   215              ( updateStep origin.id (finishStep (exitStatus == 0) time << setResourceInfo version metadata) model
   216              , effects
   217              )
   218  
   219          SetPipelineChanged origin changed ->
   220              ( updateStep origin.id (setSetPipelineChanged changed) model
   221              , effects
   222              )
   223  
   224          BuildStatus status _ ->
   225              let
   226                  newSt =
   227                      model.steps
   228                          |> Maybe.map
   229                              (\st ->
   230                                  if Concourse.BuildStatus.isRunning status then
   231                                      st
   232  
   233                                  else
   234                                      Build.StepTree.StepTree.finished st
   235                              )
   236              in
   237              ( { model | steps = newSt }, effects )
   238  
   239          ImageCheck { id } plan ->
   240              ( { model | steps = Maybe.map (Build.StepTree.StepTree.setImageCheck id plan) model.steps }
   241              , effects
   242              )
   243  
   244          ImageGet { id } plan ->
   245              ( { model | steps = Maybe.map (Build.StepTree.StepTree.setImageGet id plan) model.steps }
   246              , effects
   247              )
   248  
   249          End ->
   250              ( { model | state = StepsComplete, eventStreamUrlPath = Nothing }
   251              , effects
   252              )
   253  
   254          NetworkError ->
   255              ( model, effects )
   256  
   257  
   258  updateStep : StepID -> (Step -> Step) -> OutputModel -> OutputModel
   259  updateStep id update model =
   260      { model | steps = Maybe.map (StepTree.updateAt id update) model.steps }
   261  
   262  
   263  setRunning : Step -> Step
   264  setRunning =
   265      setStepState StepStateRunning
   266  
   267  
   268  appendStepLog : String -> Maybe Time.Posix -> Step -> Step
   269  appendStepLog output mtime step =
   270      let
   271          outputLineCount =
   272              Ansi.Log.update output (Ansi.Log.init Ansi.Log.Cooked)
   273                  |> .lines
   274                  |> Array.length
   275  
   276          lastLineNo =
   277              max (Array.length step.log.lines) 1
   278  
   279          setLineTimestamp lineNo timestamps =
   280              Dict.update lineNo (always mtime) timestamps
   281  
   282          newTimestamps =
   283              List.foldl
   284                  setLineTimestamp
   285                  step.timestamps
   286                  (List.range lastLineNo (lastLineNo + outputLineCount - 1))
   287  
   288          newLog =
   289              Ansi.Log.update output step.log
   290      in
   291      { step | log = newLog, timestamps = newTimestamps }
   292  
   293  
   294  setStepError : String -> Time.Posix -> Step -> Step
   295  setStepError message time step =
   296      { step
   297          | state = StepStateErrored
   298          , error = Just message
   299          , finish = Just time
   300      }
   301  
   302  
   303  setStart : Time.Posix -> Step -> Step
   304  setStart time step =
   305      setStepStart time (setStepState StepStateRunning step)
   306  
   307  
   308  setInitialize : Time.Posix -> Step -> Step
   309  setInitialize time step =
   310      setStepInitialize time (setStepState StepStateRunning step)
   311  
   312  
   313  finishStep : Bool -> Maybe Time.Posix -> Step -> Step
   314  finishStep succeeded mtime step =
   315      let
   316          stepState =
   317              if succeeded then
   318                  StepStateSucceeded
   319  
   320              else
   321                  StepStateFailed
   322      in
   323      setStepFinish mtime (setStepState stepState step)
   324  
   325  
   326  setResourceInfo : Concourse.Version -> Concourse.Metadata -> Step -> Step
   327  setResourceInfo version metadata step =
   328      { step | version = Just version, metadata = metadata }
   329  
   330  
   331  setStepState : StepState -> Step -> Step
   332  setStepState state step =
   333      { step | state = state }
   334  
   335  
   336  setStepInitialize : Time.Posix -> Step -> Step
   337  setStepInitialize time step =
   338      { step | initialize = Just time }
   339  
   340  
   341  setStepStart : Time.Posix -> Step -> Step
   342  setStepStart time step =
   343      { step | start = Just time }
   344  
   345  
   346  setStepFinish : Maybe Time.Posix -> Step -> Step
   347  setStepFinish mtime step =
   348      { step | finish = mtime }
   349  
   350  
   351  setSetPipelineChanged : Bool -> Step -> Step
   352  setSetPipelineChanged changed step =
   353      { step | changed = changed }
   354  
   355  
   356  view :
   357      { timeZone : Time.Zone, hovered : HoverState.HoverState }
   358      -> OutputModel
   359      -> Html Message
   360  view session { steps, state } =
   361      Html.div [ class "steps" ] [ viewStepTree session steps state ]
   362  
   363  
   364  viewStepTree :
   365      { timeZone : Time.Zone, hovered : HoverState.HoverState }
   366      -> Maybe StepTreeModel
   367      -> OutputState
   368      -> Html Message
   369  viewStepTree session steps state =
   370      case ( state, steps ) of
   371          ( StepsLoading, _ ) ->
   372              LoadingIndicator.view
   373  
   374          ( StepsLiveUpdating, Just root ) ->
   375              Build.StepTree.StepTree.view session root
   376  
   377          ( StepsComplete, Just root ) ->
   378              Build.StepTree.StepTree.view session root
   379  
   380          ( _, Nothing ) ->
   381              Html.div [] []
   382  
   383  
   384  filterHoverState : HoverState.HoverState -> HoverState.HoverState
   385  filterHoverState hovered =
   386      case hovered of
   387          HoverState.TooltipPending (StepState _) ->
   388              hovered
   389  
   390          HoverState.Tooltip (StepState _) _ ->
   391              hovered
   392  
   393          HoverState.Hovered (StepTab _ _) ->
   394              hovered
   395  
   396          _ ->
   397              HoverState.NoHover