github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/tests/JobTests.elm (about) 1 module JobTests exposing (all) 2 3 import Application.Application as Application 4 import Assets 5 import Common exposing (defineHoverBehaviour, queryView) 6 import Concourse exposing (Build) 7 import Concourse.BuildStatus exposing (BuildStatus(..)) 8 import Concourse.Pagination exposing (Direction(..), Paginated) 9 import DashboardTests exposing (darkGrey, iconSelector, middleGrey) 10 import Data 11 import Dict 12 import Expect exposing (..) 13 import Html.Attributes as Attr 14 import Http 15 import Job.Job as Job exposing (update) 16 import Message.Callback as Callback exposing (Callback(..)) 17 import Message.Effects as Effects 18 import Message.Message exposing (DomID(..), Message(..)) 19 import Message.Subscription as Subscription 20 exposing 21 ( Delivery(..) 22 , Interval(..) 23 ) 24 import Message.TopLevelMessage as Msgs 25 import RemoteData 26 import Test exposing (..) 27 import Test.Html.Query as Query 28 import Test.Html.Selector as Selector 29 exposing 30 ( attribute 31 , class 32 , containing 33 , id 34 , style 35 , text 36 ) 37 import Time 38 import Url 39 import Views.Styles 40 41 42 all : Test 43 all = 44 describe "Job" 45 [ describe "update" <| 46 [ describe "while page is loading" 47 [ test "title includes job name" <| 48 \_ -> 49 Common.init "/teams/team/pipelines/pipeline/jobs/job" 50 |> Application.view 51 |> .title 52 |> Expect.equal "job - Concourse" 53 , test "gets current timezone" <| 54 \_ -> 55 Application.init 56 { turbulenceImgSrc = "" 57 , notFoundImgSrc = "notfound.svg" 58 , csrfToken = "csrf_token" 59 , authToken = "" 60 , pipelineRunningKeyframes = "pipeline-running" 61 } 62 { protocol = Url.Http 63 , host = "" 64 , port_ = Nothing 65 , path = "/teams/team/pipelines/pipeline/jobs/job" 66 , query = Nothing 67 , fragment = Nothing 68 } 69 |> Tuple.second 70 |> Common.contains Effects.GetCurrentTimeZone 71 , test "fetches pipelines" <| 72 \_ -> 73 Application.init 74 { turbulenceImgSrc = "" 75 , notFoundImgSrc = "" 76 , csrfToken = "" 77 , authToken = "" 78 , pipelineRunningKeyframes = "" 79 } 80 { protocol = Url.Http 81 , host = "" 82 , port_ = Nothing 83 , path = "/teams/team/pipelines/pipeline/jobs/job" 84 , query = Nothing 85 , fragment = Nothing 86 } 87 |> Tuple.second 88 |> Common.contains Effects.FetchAllPipelines 89 , test "shows two spinners before anything has loaded" <| 90 \_ -> 91 Common.init "/teams/team/pipelines/pipeline/jobs/job" 92 |> queryView 93 |> Query.findAll loadingIndicatorSelector 94 |> Query.count (Expect.equal 2) 95 , test "loading build has spinners for inputs and outputs" <| 96 init { disabled = False, paused = False } 97 >> Application.handleCallback 98 (JobBuildsFetched <| 99 Ok ( Job.startingPage, buildsWithEmptyPagination ) 100 ) 101 >> Tuple.first 102 >> queryView 103 >> Expect.all 104 [ Query.find [ class "inputs" ] 105 >> Query.has loadingIndicatorSelector 106 , Query.find [ class "outputs" ] 107 >> Query.has loadingIndicatorSelector 108 ] 109 ] 110 , test "build header lays out contents horizontally" <| 111 init { disabled = False, paused = False } 112 >> queryView 113 >> Query.find [ class "build-header" ] 114 >> Query.has 115 [ style "display" "flex" 116 , style "justify-content" "space-between" 117 ] 118 , test "header has play/pause button at the left" <| 119 init { disabled = False, paused = False } 120 >> queryView 121 >> Query.find [ class "build-header" ] 122 >> Query.has [ id "pause-toggle" ] 123 , test "play/pause has background of the header color, faded" <| 124 init { disabled = False, paused = False } 125 >> queryView 126 >> Query.find [ id "pause-toggle" ] 127 >> Query.has 128 [ style "padding" "10px" 129 , style "border" "none" 130 , style "background-color" darkGreen 131 , style "outline" "none" 132 ] 133 , test "hover play/pause has background of the header color" <| 134 init { disabled = False, paused = False } 135 >> Application.update 136 (Msgs.Update <| 137 Message.Message.Hover <| 138 Just Message.Message.ToggleJobButton 139 ) 140 >> Tuple.first 141 >> queryView 142 >> Query.find [ id "pause-toggle" ] 143 >> Query.has 144 [ style "padding" "10px" 145 , style "border" "none" 146 , style "background-color" brightGreen 147 , style "outline" "none" 148 ] 149 , defineHoverBehaviour 150 { name = "play/pause button when job is unpaused" 151 , setup = 152 init { disabled = False, paused = False } () 153 , query = 154 queryView >> Query.find [ id "pause-toggle" ] 155 , unhoveredSelector = 156 { description = "grey pause icon" 157 , selector = 158 [ style "opacity" "0.5" ] 159 ++ iconSelector 160 { size = "40px" 161 , image = Assets.PauseCircleIcon |> Assets.CircleOutlineIcon 162 } 163 } 164 , hoveredSelector = 165 { description = "white pause icon" 166 , selector = 167 [ style "opacity" "1" ] 168 ++ iconSelector 169 { size = "40px" 170 , image = Assets.PauseCircleIcon |> Assets.CircleOutlineIcon 171 } 172 } 173 , hoverable = Message.Message.ToggleJobButton 174 } 175 , defineHoverBehaviour 176 { name = "play/pause button when job is paused" 177 , setup = 178 init { disabled = False, paused = True } () 179 , query = 180 queryView >> Query.find [ id "pause-toggle" ] 181 , unhoveredSelector = 182 { description = "grey play icon" 183 , selector = 184 [ style "opacity" "0.5" ] 185 ++ iconSelector 186 { size = "40px" 187 , image = Assets.PlayCircleIcon |> Assets.CircleOutlineIcon 188 } 189 } 190 , hoveredSelector = 191 { description = "white play icon" 192 , selector = 193 [ style "opacity" "1" ] 194 ++ iconSelector 195 { size = "40px" 196 , image = Assets.PlayCircleIcon |> Assets.CircleOutlineIcon 197 } 198 } 199 , hoverable = Message.Message.ToggleJobButton 200 } 201 , test "trigger build button has background of the header color, faded" <| 202 init { disabled = False, paused = False } 203 >> queryView 204 >> Query.find 205 [ attribute <| 206 Attr.attribute "aria-label" "Trigger Build" 207 ] 208 >> Query.has 209 [ style "padding" "10px" 210 , style "border" "none" 211 , style "background-color" darkGreen 212 , style "outline" "none" 213 ] 214 , test "hovered trigger build button has background of the header color" <| 215 init { disabled = False, paused = False } 216 >> Application.update 217 (Msgs.Update <| 218 Message.Message.Hover <| 219 Just Message.Message.TriggerBuildButton 220 ) 221 >> Tuple.first 222 >> queryView 223 >> Query.find 224 [ attribute <| 225 Attr.attribute "aria-label" "Trigger Build" 226 ] 227 >> Query.has 228 [ style "padding" "10px" 229 , style "border" "none" 230 , style "background-color" brightGreen 231 , style "outline" "none" 232 ] 233 , test "trigger build button has 'plus' icon" <| 234 init { disabled = False, paused = False } 235 >> queryView 236 >> Query.find 237 [ attribute <| 238 Attr.attribute "aria-label" "Trigger Build" 239 ] 240 >> Query.children [] 241 >> Query.first 242 >> Query.has 243 (iconSelector 244 { size = "40px" 245 , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon 246 } 247 ) 248 , defineHoverBehaviour 249 { name = "trigger build button" 250 , setup = 251 init { disabled = False, paused = False } () 252 , query = 253 queryView 254 >> Query.find 255 [ attribute <| 256 Attr.attribute "aria-label" "Trigger Build" 257 ] 258 , unhoveredSelector = 259 { description = "grey plus icon" 260 , selector = 261 [ style "opacity" "0.5" ] 262 ++ iconSelector 263 { size = "40px" 264 , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon 265 } 266 } 267 , hoveredSelector = 268 { description = "white plus icon" 269 , selector = 270 [ style "opacity" "1" ] 271 ++ iconSelector 272 { size = "40px" 273 , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon 274 } 275 } 276 , hoverable = Message.Message.TriggerBuildButton 277 } 278 , defineHoverBehaviour 279 { name = "disabled trigger build button" 280 , setup = 281 init { disabled = True, paused = False } () 282 , query = 283 queryView 284 >> Query.find 285 [ attribute <| 286 Attr.attribute "aria-label" "Trigger Build" 287 ] 288 , unhoveredSelector = 289 { description = "grey plus icon" 290 , selector = 291 [ style "opacity" "0.5" ] 292 ++ iconSelector 293 { size = "40px" 294 , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon 295 } 296 } 297 , hoveredSelector = 298 { description = "grey plus icon with tooltip" 299 , selector = 300 [ style "position" "relative" 301 , containing 302 [ containing 303 [ text "manual triggering disabled in job config" ] 304 , style "position" "absolute" 305 , style "right" "100%" 306 , style "top" "15px" 307 , style "width" "300px" 308 , style "color" "#ecf0f1" 309 , style "font-size" "12px" 310 , style "font-family" Views.Styles.fontFamilyDefault 311 , style "padding" "10px" 312 , style "text-align" "right" 313 ] 314 , containing <| 315 [ style "opacity" "0.5" ] 316 ++ iconSelector 317 { size = "40px" 318 , image = Assets.AddCircleIcon |> Assets.CircleOutlineIcon 319 } 320 ] 321 } 322 , hoverable = Message.Message.TriggerBuildButton 323 } 324 , describe "archived pipelines" <| 325 let 326 initWithArchivedPipeline = 327 init { paused = False, disabled = False } 328 >> Application.handleCallback 329 (Callback.AllPipelinesFetched <| 330 Ok 331 [ Data.pipeline "team" 0 332 |> Data.withName "pipeline" 333 |> Data.withArchived True 334 ] 335 ) 336 >> Tuple.first 337 in 338 [ test "play/pause button not displayed" <| 339 initWithArchivedPipeline 340 >> queryView 341 >> Query.find [ class "build-header" ] 342 >> Query.hasNot [ id "pause-toggle" ] 343 , test "header still includes job name" <| 344 initWithArchivedPipeline 345 >> queryView 346 >> Query.find [ class "build-header" ] 347 >> Query.has [ text "job" ] 348 , test "trigger build button not displayed" <| 349 initWithArchivedPipeline 350 >> queryView 351 >> Query.find [ class "build-header" ] 352 >> Query.hasNot [ class "trigger-build" ] 353 ] 354 , test "page below top bar fills height without scrolling" <| 355 init { disabled = False, paused = False } 356 >> queryView 357 >> Query.find [ id "page-below-top-bar" ] 358 >> Query.has 359 [ style "box-sizing" "border-box" 360 , style "height" "100%" 361 , style "display" "flex" 362 ] 363 , test "page contents fill available space and align vertically" <| 364 init { disabled = False, paused = False } 365 >> queryView 366 >> Query.find [ id "page-below-top-bar" ] 367 >> Query.has 368 [ style "flex-grow" "1" 369 , style "display" "flex" 370 , style "flex-direction" "column" 371 ] 372 , test "body scrolls independently" <| 373 init { disabled = False, paused = False } 374 >> Application.handleCallback 375 (JobBuildsFetched <| 376 Ok ( Job.startingPage, buildsWithEmptyPagination ) 377 ) 378 >> Tuple.first 379 >> queryView 380 >> Query.find [ class "job-body" ] 381 >> Query.has [ style "overflow-y" "auto" ] 382 , test "inputs icon on build" <| 383 init { disabled = False, paused = False } 384 >> Application.handleCallback 385 (JobBuildsFetched <| 386 Ok ( Job.startingPage, buildsWithEmptyPagination ) 387 ) 388 >> Tuple.first 389 >> queryView 390 >> Query.find [ class "inputs" ] 391 >> Query.children [] 392 >> Query.first 393 >> Expect.all 394 [ Query.has 395 [ style "display" "flex" 396 , style "align-items" "center" 397 , style "padding-bottom" "5px" 398 ] 399 , Query.children [] 400 >> Query.first 401 >> Query.has 402 (iconSelector 403 { size = "12px" 404 , image = Assets.DownArrow 405 } 406 ++ [ style "background-size" "contain" 407 , style "margin-right" "5px" 408 ] 409 ) 410 ] 411 , test "outputs icon on build" <| 412 init { disabled = False, paused = False } 413 >> Application.handleCallback 414 (JobBuildsFetched <| 415 Ok ( Job.startingPage, buildsWithEmptyPagination ) 416 ) 417 >> Tuple.first 418 >> queryView 419 >> Query.find [ class "outputs" ] 420 >> Query.children [] 421 >> Query.first 422 >> Expect.all 423 [ Query.has 424 [ style "display" "flex" 425 , style "align-items" "center" 426 , style "padding-bottom" "5px" 427 ] 428 , Query.children [] 429 >> Query.first 430 >> Query.has 431 (iconSelector 432 { size = "12px" 433 , image = Assets.UpArrow 434 } 435 ++ [ style "background-size" "contain" 436 , style "margin-right" "5px" 437 ] 438 ) 439 ] 440 , test "pagination header lays out horizontally" <| 441 init { disabled = False, paused = False } 442 >> queryView 443 >> Query.find [ id "pagination-header" ] 444 >> Query.has 445 [ style "display" "flex" 446 , style "justify-content" "space-between" 447 , style "align-items" "stretch" 448 , style "background-color" darkGrey 449 , style "height" "60px" 450 ] 451 , test "the word 'builds' is indented" <| 452 init { disabled = False, paused = False } 453 >> queryView 454 >> Query.find [ id "pagination-header" ] 455 >> Query.children [] 456 >> Query.first 457 >> Query.has 458 [ containing [ text "builds" ] 459 , style "margin" "0 18px" 460 ] 461 , test "pagination lays out horizontally" <| 462 init { disabled = False, paused = False } 463 >> queryView 464 >> Query.find [ id "pagination" ] 465 >> Query.has 466 [ style "display" "flex" 467 , style "align-items" "stretch" 468 ] 469 , test "pagination chevrons with no pages" <| 470 init { disabled = False, paused = False } 471 >> Application.handleCallback 472 (JobBuildsFetched <| 473 Ok ( Job.startingPage, buildsWithEmptyPagination ) 474 ) 475 >> Tuple.first 476 >> queryView 477 >> Query.find [ id "pagination" ] 478 >> Query.children [] 479 >> Expect.all 480 [ Query.index 0 481 >> Query.has 482 [ style "padding" "5px" 483 , style "display" "flex" 484 , style "align-items" "center" 485 , style "border-left" <| 486 "1px solid " 487 ++ middleGrey 488 , containing 489 (iconSelector 490 { image = Assets.ChevronLeft 491 , size = "24px" 492 } 493 ++ [ style "padding" "5px" 494 , style "opacity" "0.5" 495 ] 496 ) 497 ] 498 , Query.index 1 499 >> Query.has 500 [ style "padding" "5px" 501 , style "display" "flex" 502 , style "align-items" "center" 503 , style "border-left" <| 504 "1px solid " 505 ++ middleGrey 506 , containing 507 (iconSelector 508 { image = Assets.ChevronRight 509 , size = "24px" 510 } 511 ++ [ style "padding" "5px" 512 , style "opacity" "0.5" 513 ] 514 ) 515 ] 516 ] 517 , defineHoverBehaviour <| 518 let 519 urlPath = 520 "/teams/team/pipelines/pipeline/jobs/job?from=1&limit=1" 521 in 522 { name = "left pagination chevron with previous page" 523 , setup = 524 let 525 prevPage = 526 { direction = From 1 527 , limit = 1 528 } 529 in 530 init { disabled = False, paused = False } () 531 |> Application.handleCallback 532 (JobBuildsFetched <| 533 Ok 534 ( Job.startingPage 535 , { pagination = 536 { previousPage = Just prevPage 537 , nextPage = Nothing 538 } 539 , content = builds 540 } 541 ) 542 ) 543 |> Tuple.first 544 , query = 545 queryView 546 >> Query.find [ id "pagination" ] 547 >> Query.children [] 548 >> Query.index 0 549 , unhoveredSelector = 550 { description = "white left chevron" 551 , selector = 552 [ style "padding" "5px" 553 , style "display" "flex" 554 , style "align-items" "center" 555 , style "border-left" <| 556 "1px solid " 557 ++ middleGrey 558 , containing 559 (iconSelector 560 { image = Assets.ChevronLeft 561 , size = "24px" 562 } 563 ++ [ style "padding" "5px" 564 , style "opacity" "1" 565 , attribute <| Attr.href urlPath 566 ] 567 ) 568 ] 569 } 570 , hoveredSelector = 571 { description = 572 "left chevron with light grey circular bg" 573 , selector = 574 [ style "padding" "5px" 575 , style "display" "flex" 576 , style "align-items" "center" 577 , style "border-left" <| 578 "1px solid " 579 ++ middleGrey 580 , containing 581 (iconSelector 582 { image = Assets.ChevronLeft 583 , size = "24px" 584 } 585 ++ [ style "padding" "5px" 586 , style "opacity" "1" 587 , style "border-radius" "50%" 588 , style "background-color" <| 589 "#504b4b" 590 , attribute <| Attr.href urlPath 591 ] 592 ) 593 ] 594 } 595 , hoverable = Message.Message.PreviousPageButton 596 } 597 , test "pagination previous page loads most recent page if less than 100 entries" <| 598 \_ -> 599 let 600 previousPage = 601 { direction = From 1, limit = 100 } 602 in 603 init { disabled = False, paused = False } () 604 |> Application.handleCallback 605 (JobBuildsFetched <| 606 Ok 607 ( previousPage 608 , { pagination = 609 { previousPage = Nothing 610 , nextPage = Nothing 611 } 612 , content = [] 613 } 614 ) 615 ) 616 |> Tuple.second 617 |> Common.contains 618 (Effects.FetchJobBuilds 619 { teamName = "team" 620 , pipelineName = "pipeline" 621 , jobName = "job" 622 } 623 Job.startingPage 624 ) 625 , describe "When fetching builds" 626 [ test "says no builds" <| 627 \_ -> 628 init { disabled = False, paused = False } () 629 |> Application.handleCallback 630 (JobBuildsFetched <| 631 Ok 632 ( Job.startingPage 633 , { pagination = 634 { previousPage = Nothing 635 , nextPage = Nothing 636 } 637 , content = [] 638 } 639 ) 640 ) 641 |> Tuple.first 642 |> queryView 643 |> Query.has [ text "no builds for job “job”" ] 644 ] 645 , test "JobBuildsFetched" <| 646 \_ -> 647 Expect.equal 648 { defaultModel 649 | currentPage = 650 { direction = Concourse.Pagination.To 123 651 , limit = 1 652 } 653 , buildsWithResources = 654 RemoteData.Success 655 { content = 656 [ { build = someBuild 657 , resources = Nothing 658 } 659 ] 660 , pagination = 661 { previousPage = Nothing 662 , nextPage = Nothing 663 } 664 } 665 } 666 <| 667 Tuple.first <| 668 Job.handleCallback 669 (JobBuildsFetched <| 670 Ok 671 ( Job.startingPage 672 , { content = [ someBuild ] 673 , pagination = 674 { previousPage = Nothing 675 , nextPage = Nothing 676 } 677 } 678 ) 679 ) 680 ( defaultModel, [] ) 681 , test "JobBuildsFetched error" <| 682 \_ -> 683 Expect.equal 684 defaultModel 685 <| 686 Tuple.first <| 687 Job.handleCallback 688 (JobBuildsFetched <| Err Http.NetworkError) 689 ( defaultModel, [] ) 690 , test "JobFetched" <| 691 \_ -> 692 Expect.equal 693 { defaultModel 694 | job = RemoteData.Success someJob 695 } 696 <| 697 Tuple.first <| 698 Job.handleCallback (JobFetched <| Ok someJob) ( defaultModel, [] ) 699 , test "JobFetched error" <| 700 \_ -> 701 Expect.equal 702 defaultModel 703 <| 704 Tuple.first <| 705 Job.handleCallback 706 (JobFetched <| Err Http.NetworkError) 707 ( defaultModel, [] ) 708 , test "BuildResourcesFetched" <| 709 \_ -> 710 let 711 buildInput = 712 { name = "some-input" 713 , version = Dict.fromList [ ( "version", "v1" ) ] 714 , firstOccurrence = True 715 } 716 717 buildOutput = 718 { name = "some-resource" 719 , version = Dict.fromList [ ( "version", "v2" ) ] 720 } 721 in 722 let 723 buildResources = 724 { inputs = [ buildInput ] 725 , outputs = [ buildOutput ] 726 } 727 in 728 Expect.equal 729 defaultModel 730 <| 731 Tuple.first <| 732 Job.handleCallback (BuildResourcesFetched (Ok ( 1, buildResources ))) 733 ( defaultModel, [] ) 734 , test "BuildResourcesFetched error" <| 735 \_ -> 736 Expect.equal 737 defaultModel 738 <| 739 Tuple.first <| 740 Job.handleCallback 741 (BuildResourcesFetched (Err Http.NetworkError)) 742 ( defaultModel, [] ) 743 , test "TogglePaused" <| 744 \_ -> 745 Expect.equal 746 { defaultModel 747 | job = RemoteData.Success { someJob | paused = True } 748 , pausedChanging = True 749 } 750 <| 751 Tuple.first <| 752 update 753 (Click ToggleJobButton) 754 ( { defaultModel | job = RemoteData.Success someJob }, [] ) 755 , test "PausedToggled" <| 756 \_ -> 757 Expect.equal 758 { defaultModel 759 | job = RemoteData.Success someJob 760 , pausedChanging = False 761 } 762 <| 763 Tuple.first <| 764 Job.handleCallback 765 (PausedToggled <| Ok ()) 766 ( { defaultModel | job = RemoteData.Success someJob }, [] ) 767 , test "PausedToggled error" <| 768 \_ -> 769 Expect.equal 770 { defaultModel | job = RemoteData.Success someJob } 771 <| 772 Tuple.first <| 773 Job.handleCallback 774 (PausedToggled <| Err Http.NetworkError) 775 ( { defaultModel | job = RemoteData.Success someJob }, [] ) 776 , test "PausedToggled unauthorized" <| 777 \_ -> 778 Expect.equal 779 { defaultModel | job = RemoteData.Success someJob } 780 <| 781 Tuple.first <| 782 Job.handleCallback (PausedToggled <| Data.httpUnauthorized) 783 ( { defaultModel | job = RemoteData.Success someJob }, [] ) 784 , test "page is subscribed to one and five second timers" <| 785 init { disabled = False, paused = False } 786 >> Application.subscriptions 787 >> Expect.all 788 [ Common.contains (Subscription.OnClockTick OneSecond) 789 , Common.contains (Subscription.OnClockTick FiveSeconds) 790 ] 791 , test "on five-second timer, refreshes job and builds" <| 792 init { disabled = False, paused = False } 793 >> Application.update 794 (Msgs.DeliveryReceived <| 795 ClockTicked FiveSeconds <| 796 Time.millisToPosix 0 797 ) 798 >> Tuple.second 799 >> Expect.all 800 [ Common.contains (Effects.FetchJobBuilds jobInfo { direction = ToMostRecent, limit = 100 }) 801 , Common.contains (Effects.FetchJob jobInfo) 802 ] 803 , test "on one-second timer, updates build timestamps" <| 804 init { disabled = False, paused = False } 805 >> Application.handleCallback 806 (Callback.JobBuildsFetched <| 807 Ok 808 ( Job.startingPage 809 , { content = [ someBuild ] 810 , pagination = 811 { nextPage = Nothing 812 , previousPage = Nothing 813 } 814 } 815 ) 816 ) 817 >> Tuple.first 818 >> Application.update 819 (Msgs.DeliveryReceived <| 820 ClockTicked OneSecond <| 821 Time.millisToPosix (2 * 1000) 822 ) 823 >> Tuple.first 824 >> queryView 825 >> Query.find [ class "js-build" ] 826 >> Query.has [ text "2s ago" ] 827 , test "shows build timestamps in current timezone" <| 828 init { disabled = False, paused = False } 829 >> Application.handleCallback 830 (Callback.GotCurrentTimeZone <| 831 Time.customZone (5 * 60) [] 832 ) 833 >> Tuple.first 834 >> Application.handleCallback 835 (Callback.JobBuildsFetched <| 836 Ok 837 ( Job.startingPage 838 , { content = [ someBuild ] 839 , pagination = 840 { nextPage = Nothing 841 , previousPage = Nothing 842 } 843 } 844 ) 845 ) 846 >> Tuple.first 847 >> Application.update 848 (Msgs.DeliveryReceived <| 849 ClockTicked OneSecond <| 850 Time.millisToPosix (24 * 60 * 60 * 1000) 851 ) 852 >> Tuple.first 853 >> queryView 854 >> Query.find [ class "js-build" ] 855 >> Query.has [ text "Jan 1 1970 05:00:00 AM" ] 856 ] 857 ] 858 859 860 darkGreen : String 861 darkGreen = 862 "#419867" 863 864 865 brightGreen : String 866 brightGreen = 867 "#11c560" 868 869 870 someJobInfo : Concourse.JobIdentifier 871 someJobInfo = 872 Data.jobId 873 |> Data.withJobName "some-job" 874 |> Data.withPipelineName "some-pipeline" 875 |> Data.withTeamName "some-team" 876 877 878 someBuild : Build 879 someBuild = 880 { id = 123 881 , name = "45" 882 , job = Just someJobInfo 883 , status = BuildStatusSucceeded 884 , duration = 885 { startedAt = Just <| Time.millisToPosix 0 886 , finishedAt = Just <| Time.millisToPosix 0 887 } 888 , reapTime = Just <| Time.millisToPosix 0 889 } 890 891 892 jobInfo : Concourse.JobIdentifier 893 jobInfo = 894 Data.jobId 895 896 897 builds : List Build 898 builds = 899 [ { id = 0 900 , name = "0" 901 , job = Just jobInfo 902 , status = BuildStatusSucceeded 903 , duration = 904 { startedAt = Nothing 905 , finishedAt = Nothing 906 } 907 , reapTime = Nothing 908 } 909 ] 910 911 912 buildsWithEmptyPagination : Paginated Build 913 buildsWithEmptyPagination = 914 { content = builds 915 , pagination = 916 { previousPage = Nothing 917 , nextPage = Nothing 918 } 919 } 920 921 922 someJob : Concourse.Job 923 someJob = 924 { name = "some-job" 925 , pipelineName = "some-pipeline" 926 , teamName = "some-team" 927 , nextBuild = Nothing 928 , finishedBuild = Just someBuild 929 , transitionBuild = Nothing 930 , paused = False 931 , disableManualTrigger = False 932 , inputs = [] 933 , outputs = [] 934 , groups = [] 935 } 936 937 938 defaultModel : Job.Model 939 defaultModel = 940 Job.init 941 { jobId = someJobInfo 942 , paging = Nothing 943 } 944 |> Tuple.first 945 946 947 init : { disabled : Bool, paused : Bool } -> () -> Application.Model 948 init { disabled, paused } _ = 949 Common.init "/teams/team/pipelines/pipeline/jobs/job" 950 |> Application.handleCallback 951 (JobFetched <| 952 Ok 953 { name = "job" 954 , pipelineName = "pipeline" 955 , teamName = "team" 956 , nextBuild = Nothing 957 , finishedBuild = Just someBuild 958 , transitionBuild = Nothing 959 , paused = paused 960 , disableManualTrigger = disabled 961 , inputs = [] 962 , outputs = [] 963 , groups = [] 964 } 965 ) 966 |> Tuple.first 967 968 969 loadingIndicatorSelector : List Selector.Selector 970 loadingIndicatorSelector = 971 [ style "animation" 972 "container-rotate 1568ms linear infinite" 973 , style "height" "14px" 974 , style "width" "14px" 975 , style "margin" "7px" 976 ]