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

     1  module Dashboard.Group exposing
     2      ( PipelineIndex
     3      , hdView
     4      , ordering
     5      , pipelineNotSetView
     6      , view
     7      , viewFavoritePipelines
     8      )
     9  
    10  import Concourse
    11  import Dashboard.Group.Models exposing (Group, Pipeline)
    12  import Dashboard.Group.Tag as Tag
    13  import Dashboard.Models exposing (DragState(..), DropState(..))
    14  import Dashboard.Pipeline as Pipeline
    15  import Dashboard.PipelineGrid as PipelineGrid
    16  import Dashboard.PipelineGrid.Constants as PipelineGridConstants
    17  import Dashboard.Styles as Styles
    18  import Dict exposing (Dict)
    19  import HoverState
    20  import Html exposing (Html)
    21  import Html.Attributes exposing (attribute, class, classList, draggable, id, style)
    22  import Html.Events exposing (on, onMouseOut, onMouseOver, preventDefaultOn, stopPropagationOn)
    23  import Html.Keyed
    24  import Json.Decode
    25  import Maybe.Extra
    26  import Message.Effects as Effects
    27  import Message.Message exposing (DomID(..), DropTarget(..), Message(..), PipelinesSection(..))
    28  import Ordering exposing (Ordering)
    29  import Set exposing (Set)
    30  import Time
    31  import UserState exposing (UserState(..))
    32  import Views.Spinner as Spinner
    33  import Views.Styles
    34  
    35  
    36  ordering : { a | userState : UserState } -> Ordering Group
    37  ordering session =
    38      Ordering.byFieldWith Tag.ordering (tag session)
    39          |> Ordering.breakTiesWith (Ordering.byField .teamName)
    40  
    41  
    42  type alias PipelineIndex =
    43      Int
    44  
    45  
    46  view :
    47      { a | userState : UserState, favoritedPipelines : Set Concourse.DatabaseID }
    48      ->
    49          { dragState : DragState
    50          , dropState : DropState
    51          , now : Maybe Time.Posix
    52          , hovered : HoverState.HoverState
    53          , pipelineRunningKeyframes : String
    54          , pipelinesWithResourceErrors : Set ( String, String )
    55          , pipelineLayers : Dict ( String, String ) (List (List Concourse.JobIdentifier))
    56          , pipelineCards : List PipelineGrid.PipelineCard
    57          , dropAreas : List PipelineGrid.DropArea
    58          , groupCardsHeight : Float
    59          , pipelineJobs : Dict ( String, String ) (List Concourse.JobIdentifier)
    60          , jobs : Dict ( String, String, String ) Concourse.Job
    61          }
    62      -> Group
    63      -> Html Message
    64  view session params g =
    65      let
    66          pipelineCardViews =
    67              if List.isEmpty params.pipelineCards then
    68                  [ ( "not-set", Pipeline.pipelineNotSetView ) ]
    69  
    70              else
    71                  params.pipelineCards
    72                      |> List.map
    73                          (\{ bounds, pipeline } ->
    74                              pipelineCardView session
    75                                  params
    76                                  AllPipelinesSection
    77                                  { bounds = bounds, pipeline = pipeline }
    78                                  g.teamName
    79                                  |> (\html -> ( String.fromInt pipeline.id, html ))
    80                          )
    81  
    82          dropAreaViews =
    83              params.dropAreas
    84                  |> List.map
    85                      (\{ bounds, target } ->
    86                          pipelineDropAreaView params.dragState g.teamName bounds target
    87                      )
    88      in
    89      Html.div
    90          [ id <| Effects.toHtmlID <| DashboardGroup g.teamName
    91          , class "dashboard-team-group"
    92          , attribute "data-team-name" g.teamName
    93          ]
    94          [ Html.div
    95              [ style "display" "flex"
    96              , style "align-items" "center"
    97              , style "margin-bottom" (String.fromInt PipelineGridConstants.padding ++ "px")
    98              , class <| .sectionHeaderClass Effects.stickyHeaderConfig
    99              ]
   100              (Html.div
   101                  [ class "dashboard-team-name"
   102                  , style "font-weight" Views.Styles.fontWeightBold
   103                  ]
   104                  [ Html.text g.teamName ]
   105                  :: (Maybe.Extra.toList <|
   106                          Maybe.map (Tag.view False) (tag session g)
   107                     )
   108                  ++ (if params.dropState == DroppingWhileApiRequestInFlight g.teamName then
   109                          [ Spinner.spinner { sizePx = 20, margin = "0 0 0 10px" } ]
   110  
   111                      else
   112                          []
   113                     )
   114              )
   115          , Html.Keyed.node "div"
   116              [ class <| .sectionBodyClass Effects.stickyHeaderConfig
   117              , style "position" "relative"
   118              , style "height" <| String.fromFloat params.groupCardsHeight ++ "px"
   119              ]
   120              (pipelineCardViews
   121                  ++ [ ( "drop-areas", Html.div [ style "position" "absolute" ] dropAreaViews ) ]
   122              )
   123          ]
   124  
   125  
   126  viewFavoritePipelines :
   127      { a | userState : UserState, favoritedPipelines : Set Concourse.DatabaseID }
   128      ->
   129          { dragState : DragState
   130          , dropState : DropState
   131          , now : Maybe Time.Posix
   132          , hovered : HoverState.HoverState
   133          , pipelineRunningKeyframes : String
   134          , pipelinesWithResourceErrors : Set ( String, String )
   135          , pipelineLayers : Dict ( String, String ) (List (List Concourse.JobIdentifier))
   136          , pipelineCards : List PipelineGrid.PipelineCard
   137          , headers : List PipelineGrid.Header
   138          , groupCardsHeight : Float
   139          , pipelineJobs : Dict ( String, String ) (List Concourse.JobIdentifier)
   140          , jobs : Dict ( String, String, String ) Concourse.Job
   141          }
   142      -> Html Message
   143  viewFavoritePipelines session params =
   144      let
   145          pipelineCardViews =
   146              params.pipelineCards
   147                  |> List.map
   148                      (\{ bounds, pipeline } ->
   149                          pipelineCardView session
   150                              params
   151                              FavoritesSection
   152                              { bounds = bounds, pipeline = pipeline }
   153                              pipeline.teamName
   154                              |> (\html -> ( String.fromInt pipeline.id, html ))
   155                      )
   156  
   157          headerViews =
   158              params.headers
   159                  |> List.map
   160                      (\{ bounds, header } ->
   161                          headerView bounds header
   162                      )
   163      in
   164      Html.Keyed.node "div"
   165          [ id <| "dashboard-favorite-pipelines"
   166          , style "position" "relative"
   167          , style "height" <| String.fromFloat params.groupCardsHeight ++ "px"
   168          ]
   169          (pipelineCardViews
   170              ++ [ ( "headers"
   171                   , Html.div
   172                      [ style "position" "absolute"
   173                      , class "headers"
   174                      ]
   175                      headerViews
   176                   )
   177                 ]
   178          )
   179  
   180  
   181  tag : { a | userState : UserState } -> Group -> Maybe Tag.Tag
   182  tag { userState } g =
   183      case userState of
   184          UserStateLoggedIn user ->
   185              Tag.tag user g.teamName
   186  
   187          _ ->
   188              Nothing
   189  
   190  
   191  hdView :
   192      { pipelineRunningKeyframes : String
   193      , pipelinesWithResourceErrors : Set ( String, String )
   194      , pipelineJobs : Dict ( String, String ) (List Concourse.JobIdentifier)
   195      , jobs : Dict ( String, String, String ) Concourse.Job
   196      }
   197      -> { a | userState : UserState }
   198      -> Group
   199      -> List (Html Message)
   200  hdView { pipelineRunningKeyframes, pipelinesWithResourceErrors, pipelineJobs, jobs } session g =
   201      let
   202          orderedPipelines =
   203              g.pipelines
   204  
   205          header =
   206              Html.div
   207                  [ class "dashboard-team-name" ]
   208                  [ Html.text g.teamName ]
   209                  :: (Maybe.Extra.toList <| Maybe.map (Tag.view True) (tag session g))
   210  
   211          teamPipelines =
   212              if List.isEmpty orderedPipelines then
   213                  [ pipelineNotSetView ]
   214  
   215              else
   216                  orderedPipelines
   217                      |> List.map
   218                          (\p ->
   219                              Pipeline.hdPipelineView
   220                                  { pipeline = p
   221                                  , pipelineRunningKeyframes = pipelineRunningKeyframes
   222                                  , resourceError =
   223                                      pipelinesWithResourceErrors
   224                                          |> Set.member ( p.teamName, p.name )
   225                                  , existingJobs =
   226                                      pipelineJobs
   227                                          |> Dict.get ( p.teamName, p.name )
   228                                          |> Maybe.withDefault []
   229                                          |> List.filterMap (lookupJob jobs)
   230                                  }
   231                          )
   232      in
   233      case teamPipelines of
   234          [] ->
   235              header
   236  
   237          p :: ps ->
   238              -- Wrap the team name and the first pipeline together so
   239              -- the team name is not the last element in a column
   240              Html.div
   241                  (class "dashboard-team-name-wrapper" :: Styles.teamNameHd)
   242                  (header ++ [ p ])
   243                  :: ps
   244  
   245  
   246  pipelineNotSetView : Html Message
   247  pipelineNotSetView =
   248      Html.div
   249          [ class "card" ]
   250          [ Html.div
   251              Styles.noPipelineCardHd
   252              [ Html.div
   253                  Styles.noPipelineCardTextHd
   254                  [ Html.text "no pipelines set" ]
   255              ]
   256          ]
   257  
   258  
   259  lookupJob : Dict ( String, String, String ) Concourse.Job -> Concourse.JobIdentifier -> Maybe Concourse.Job
   260  lookupJob jobs jobId =
   261      jobs
   262          |> Dict.get ( jobId.teamName, jobId.pipelineName, jobId.jobName )
   263  
   264  
   265  pipelineCardView :
   266      { a | userState : UserState, favoritedPipelines : Set Concourse.DatabaseID }
   267      ->
   268          { b
   269              | dragState : DragState
   270              , dropState : DropState
   271              , now : Maybe Time.Posix
   272              , hovered : HoverState.HoverState
   273              , pipelineRunningKeyframes : String
   274              , pipelinesWithResourceErrors : Set ( String, String )
   275              , pipelineLayers : Dict ( String, String ) (List (List Concourse.JobIdentifier))
   276              , pipelineJobs : Dict ( String, String ) (List Concourse.JobIdentifier)
   277              , jobs : Dict ( String, String, String ) Concourse.Job
   278          }
   279      -> PipelinesSection
   280      ->
   281          { bounds : PipelineGrid.Bounds
   282          , pipeline : Pipeline
   283          }
   284      -> String
   285      -> Html Message
   286  pipelineCardView session params section { bounds, pipeline } teamName =
   287      Html.div
   288          ([ class "pipeline-wrapper"
   289           , style "position" "absolute"
   290           , style "transform"
   291              ("translate("
   292                  ++ String.fromFloat bounds.x
   293                  ++ "px,"
   294                  ++ String.fromFloat bounds.y
   295                  ++ "px)"
   296              )
   297           , style
   298              "width"
   299              (String.fromFloat bounds.width
   300                  ++ "px"
   301              )
   302           , style "height"
   303              (String.fromFloat bounds.height
   304                  ++ "px"
   305              )
   306           , onMouseOver <|
   307              Hover <|
   308                  Just <|
   309                      PipelineWrapper
   310                          { pipelineName = pipeline.name
   311                          , teamName = pipeline.teamName
   312                          }
   313           , onMouseOut <| Hover Nothing
   314           ]
   315              ++ (if params.dragState /= NotDragging then
   316                      [ style "transition" "transform 0.2s ease-in-out" ]
   317  
   318                  else
   319                      []
   320                 )
   321              ++ (let
   322                      hoverStyle id =
   323                          if
   324                              (id.pipelineName == pipeline.name)
   325                                  && (id.teamName == pipeline.teamName)
   326                          then
   327                              [ style "z-index" "1" ]
   328  
   329                          else
   330                              []
   331                  in
   332                  case HoverState.hoveredElement params.hovered of
   333                      Just (JobPreview _ jobID) ->
   334                          hoverStyle jobID
   335  
   336                      Just (PipelineWrapper pipelineID) ->
   337                          hoverStyle pipelineID
   338  
   339                      _ ->
   340                          []
   341                 )
   342          )
   343          [ Html.div
   344              ([ class "card"
   345               , style "width" "100%"
   346               , style "height" "100%"
   347               , attribute "data-pipeline-name" pipeline.name
   348               ]
   349                  ++ (if section == AllPipelinesSection && not pipeline.stale then
   350                          [ attribute
   351                              "ondragstart"
   352                              "event.dataTransfer.setData('text/plain', '');"
   353                          , draggable "true"
   354                          , on "dragstart"
   355                              (Json.Decode.succeed (DragStart pipeline.teamName pipeline.name))
   356                          , on "dragend" (Json.Decode.succeed DragEnd)
   357                          ]
   358  
   359                      else
   360                          []
   361                     )
   362                  ++ (if params.dragState == Dragging pipeline.teamName pipeline.name then
   363                          [ style "width" "0"
   364                          , style "margin" "0 12.5px"
   365                          , style "overflow" "hidden"
   366                          ]
   367  
   368                      else
   369                          []
   370                     )
   371                  ++ (if params.dropState == DroppingWhileApiRequestInFlight teamName then
   372                          [ style "opacity" "0.45", style "pointer-events" "none" ]
   373  
   374                      else
   375                          [ style "opacity" "1" ]
   376                     )
   377              )
   378              [ Pipeline.pipelineView
   379                  { now = params.now
   380                  , pipeline = pipeline
   381                  , resourceError =
   382                      params.pipelinesWithResourceErrors
   383                          |> Set.member ( pipeline.teamName, pipeline.name )
   384                  , existingJobs =
   385                      params.pipelineJobs
   386                          |> Dict.get ( pipeline.teamName, pipeline.name )
   387                          |> Maybe.withDefault []
   388                          |> List.filterMap (lookupJob params.jobs)
   389                  , layers =
   390                      params.pipelineLayers
   391                          |> Dict.get ( pipeline.teamName, pipeline.name )
   392                          |> Maybe.withDefault []
   393                          |> List.map (List.filterMap <| lookupJob params.jobs)
   394                  , hovered = params.hovered
   395                  , pipelineRunningKeyframes = params.pipelineRunningKeyframes
   396                  , userState = session.userState
   397                  , favoritedPipelines = session.favoritedPipelines
   398                  , section = section
   399                  }
   400              ]
   401          ]
   402  
   403  
   404  pipelineDropAreaView : DragState -> String -> PipelineGrid.Bounds -> DropTarget -> Html Message
   405  pipelineDropAreaView dragState name { x, y, width, height } target =
   406      let
   407          active =
   408              case dragState of
   409                  Dragging team _ ->
   410                      team == name
   411  
   412                  _ ->
   413                      False
   414      in
   415      Html.div
   416          [ classList
   417              [ ( "drop-area", True )
   418              , ( "active", active )
   419              ]
   420          , style "position" "absolute"
   421          , style "transform" <|
   422              "translate("
   423                  ++ String.fromFloat x
   424                  ++ "px,"
   425                  ++ String.fromFloat y
   426                  ++ "px)"
   427          , style "width" <| String.fromFloat width ++ "px"
   428          , style "height" <| String.fromFloat height ++ "px"
   429          , on "dragenter" (Json.Decode.succeed (DragOver target))
   430  
   431          -- preventDefault is required so that the card will not appear to
   432          -- "float" or "snap" back to its original position when dropped.
   433          , preventDefaultOn "dragover" (Json.Decode.succeed ( DragOver target, True ))
   434          , stopPropagationOn "drop" (Json.Decode.succeed ( DragEnd, True ))
   435          ]
   436          []
   437  
   438  
   439  headerView : PipelineGrid.Bounds -> String -> Html Message
   440  headerView { x, y, width, height } header =
   441      Html.div
   442          [ class "header"
   443          , style "position" "absolute"
   444          , style "transform" <|
   445              "translate("
   446                  ++ String.fromFloat x
   447                  ++ "px,"
   448                  ++ String.fromFloat y
   449                  ++ "px)"
   450          , style "width" <| String.fromFloat width ++ "px"
   451          , style "height" <| String.fromFloat height ++ "px"
   452          , style "font-size" "18px"
   453          , style "padding-left" "12.5px"
   454          , style "padding-top" "17.5px"
   455          , style "box-sizing" "border-box"
   456          , style "text-overflow" "ellipsis"
   457          , style "overflow" "hidden"
   458          , style "white-space" "nowrap"
   459          , style "font-weight" Views.Styles.fontWeightBold
   460          ]
   461          [ Html.text header ]