šŸŽ‡ Gitlab pipelines monitor – in Elm

Posted October 2020

ā„¹ļø This is adapted from a talk I gave last year at Elm London Finally got round to bloggifyingā€¦ You can also just check out the project on Gitlab.

screenshot

Background

What?

  • Itā€™s a CI visualisation for Gitlab, now hosted at https://gitlab.com/declension
  • Uses Elm 0.19, ParcelJS, Gitlab v4 API
  • Designed for always-on bigscreens in the office (EDIT: remember them?)
  • Pretty alpha right now EDIT: seems to be stable, some missing thingsā€¦

Why?

  • Elm! Good reasons! Elm!
  • šŸ†™ Exciting FP use-cases
  • šŸ¤” Also: learning experience on complex REST APIs, mutable data, and asynchronicity
  • šŸŽØ A focus on smooth animations and dark UIs
  • ā›™ Need to get default and other merge request branches, too

Existing Apps

Letā€™s face it ā€“ this is not new territory #### gitlab-monitor timoschwarzer/gitlab-monitor

gitlab-ci-monitor

globocom/gitlab-ci-monitor

Issues

  • Not registered as apps
  • ā€œNot made hereā€, letā€™s be honest.
  • šŸ˜± Dangerous lack of emoji on existing products
  • ā›” This has to stop!

Challenges

The Gitlab API

Everythingā€™s Changing

  • Projectsā€™ metadata gets updated
  • Ordering is (currently) on last update, so order changesā€¦
  • New branches appearā€¦
  • New pipelines appear in various branchesā€¦
  • Previously ā“ become āœ”ļø or āœ–ļøā€¦

Data fan-out

  • Each group has many projects
  • ā€¦each project has many pipelines
  • ā€¦each pipeline can have many stages
  • ā€¦each stage has many jobsā€¦
  • ā€¦each job has many stepsā€¦
  • šŸ˜±

A word about GraphQL

Back to authentication

oAuth 2 Flows

  • šŸ” Web application ā€“ server side
  • šŸŒ Implicit grant ā€“ SPAs etc
  • šŸ˜ž Resource owner password credentials
    ā€“ secured host

Getting this to work

Elm setup

  • Elm! Simple Elm project
  • šŸŽ Yarn to build, Parcel.JS to do all the magic
  • šŸ’… SASS for styling

Current Data Model

type alias Model =
    { config : Flags
    , key : Nav.Key
    , token : Maybe Token
    , data : GitlabData
    , url : Url.Url
    }

Data structure itself

type alias ProjectId =
    Int

type alias GitRef =
    String

type alias GitlabData =
    { projects : List Project
    , pipelines : Dict ProjectId (List Pipeline)
    }

Records

type alias Pipeline =
    { ref : String
    , id : Int
    , status : Status
    , url : String
    }

type alias Project =
    { id : Int
    , namespace : String
    , name : String
    , description : Maybe String
    , url : String
    , lastActivity : Posix
    }

type alias PipelineStore =
    Dict ProjectId (Dict GitRef (List Pipeline))

Implicit Grant Flow

  • One needs to register an app first:
  • Gitlab applications

Implicit Grant URL

  • Then redirect user to a URL like
  • https://gitlab.example.com/oauth/authorize ?client_id=APP_ID &redirect_uri=REDIRECT_URI &state=UNIQUE_STATE_HASH &scope=REQUESTED_SCOPES
  • Theyā€™ll come back at REDIRECT_URI, with a token in the URL

HTTP with auth

getUrl : Token -> Url -> (Result Http.Error a -> Msg) -> Decoder a -> Cmd Msg
getUrl token url msg decoder =
    Http.request
        { method = "GET"
        , url = Url.toString url
        , body = emptyBody
        , expect = expectJson msg decoder
        , headers = [ header "Authorization" ("Bearer " ++ token) ]
        , timeout = Just (httpTimeout * 1000.0)
        , tracker = Nothing
        }

Parse that token!

  • Thisā€¦ worksā€¦

    extractToken : Url -> Maybe Token
    extractToken url =
        url.fragment
            |> Maybe.map (String.append "http://DUMMY?")
            |> Maybe.andThen Url.fromString
            |> Maybe.andThen (Url.Parser.parse (query toToken))
            |> Maybe.withDefault Nothing

Simple Subscriptions

subscriptions : Model -> Sub Msg
subscriptions _ =
    Time.every (10 * 1000) Tick
  • Weā€™ll use chained messages though, so this isnā€™t everythingā€¦

Less Simple Messages

type Msg
    = Tick Posix
    | LinkClicked Browser.UrlRequest
    | GotPipelinesFor ProjectId (Result Http.Error (List Pipeline))
    | GotProjects (Result Http.Error (List Project))
    | UrlChanged Url.Url

Lessons learned

Nested mutable data is hard

  • Incomplete data makes for some challenges of its own in FP
  • Caching is key (arguably the whole data structure is a cache)
  • This is a case study for GraphQL in many ways

Parcel.JS

  • ā€¦is insanely fast šŸš€
  • Elm integration ā€œjust worksā€
  • Supports .env, SASS out of the box
  • I / we wonā€™t be using Webpack much now šŸ˜ (EDIT: I havenā€™t and thatā€™s great)

Obvious notes to self

  • Donā€™t leave slides to the day before itself
  • Especially if you also custom-build the slide frameworkā€¦
  • Especially if thatā€™s in Haskell šŸ˜†

But wait, thereā€™s more

  • Mutable nested data can beā€¦ hard
  • Lenses arenā€™t really a thing in Elm: Elm Packages on ā€œlensā€
  • Which is probably good, I guess..
  • (though elm-monocle looks interesting)

Tags