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