diff --git a/src/elm/Api/Operations_.elm b/src/elm/Api/Operations.elm similarity index 55% rename from src/elm/Api/Operations_.elm rename to src/elm/Api/Operations.elm index b3668c293..e0af823a1 100644 --- a/src/elm/Api/Operations_.elm +++ b/src/elm/Api/Operations.elm @@ -3,7 +3,7 @@ SPDX-License-Identifier: Apache-2.0 --} -module Api.Operations_ exposing +module Api.Operations exposing ( enableRepo , finishAuthentication , getCurrentUser @@ -17,46 +17,12 @@ module Api.Operations_ exposing ) import Api.Api exposing (Request, delete, get, patch, post, put, withAuth) -import Api.Endpoint as Endpoint exposing (Endpoint) -import Api.Pagination as Pagination -import Auth.Jwt exposing (JwtAccessToken, decodeJwtAccessToken) +import Api.Endpoint +import Auth.Jwt exposing (JwtAccessToken) import Auth.Session exposing (Session(..)) import Http import Json.Decode import Vela - exposing - ( AuthParams - , BuildGraph - , BuildNumber - , Builds - , CurrentUser - , Deployment - , DeploymentId - , Engine - , Event - , Hook - , HookNumber - , Hooks - , Key - , Log - , Name - , Org - , PipelineConfig - , Ref - , Repo - , Schedule - , ScheduleName - , Schedules - , Secret - , Secrets - , Service - , ServiceNumber - , SourceRepositories - , Step - , StepNumber - , Templates - , Type - ) @@ -67,46 +33,46 @@ import Vela -} getToken : String -> Request JwtAccessToken getToken baseUrl = - get baseUrl Endpoint.Token decodeJwtAccessToken + get baseUrl Api.Endpoint.Token Auth.Jwt.decodeJwtAccessToken {-| finishAuthentication : complete authentication by supplying code and state to the authenciate endpoint which will also set the refresh token cookie -} -finishAuthentication : String -> AuthParams -> Request JwtAccessToken +finishAuthentication : String -> Vela.AuthParams -> Request JwtAccessToken finishAuthentication baseUrl { code, state } = - get baseUrl (Endpoint.Authenticate { code = code, state = state }) decodeJwtAccessToken + get baseUrl (Api.Endpoint.Authenticate { code = code, state = state }) Auth.Jwt.decodeJwtAccessToken {-| logout: logs the user out by deleting the refresh token cookie -} logout : String -> Session -> Request String logout baseUrl session = - get baseUrl Endpoint.Logout Json.Decode.string + get baseUrl Api.Endpoint.Logout Json.Decode.string |> withAuth session {-| getCurrentUser : retrieves the currently authenticated user with the current user endpoint -} -getCurrentUser : String -> Session -> Request CurrentUser +getCurrentUser : String -> Session -> Request Vela.CurrentUser getCurrentUser baseUrl session = - get baseUrl Endpoint.CurrentUser Vela.decodeCurrentUser + get baseUrl Api.Endpoint.CurrentUser Vela.decodeCurrentUser |> withAuth session {-| updateCurrentUser : updates the currently authenticated user with the current user endpoint -} -updateCurrentUser : String -> Session -> Http.Body -> Request CurrentUser +updateCurrentUser : String -> Session -> Http.Body -> Request Vela.CurrentUser updateCurrentUser baseUrl session body = - put baseUrl Endpoint.CurrentUser body Vela.decodeCurrentUser + put baseUrl Api.Endpoint.CurrentUser body Vela.decodeCurrentUser |> withAuth session {-| getUserSourceRepos : retrieves the current users source repositories -} -getUserSourceRepos : String -> Session -> Request SourceRepositories +getUserSourceRepos : String -> Session -> Request Vela.SourceRepositories getUserSourceRepos baseUrl session = - get baseUrl Endpoint.UserSourceRepositories Vela.decodeSourceRepositories + get baseUrl Api.Endpoint.UserSourceRepositories Vela.decodeSourceRepositories |> withAuth session @@ -114,7 +80,7 @@ getUserSourceRepos baseUrl session = -} enableRepo : String -> Session -> Http.Body -> Request Vela.Repository enableRepo baseUrl session body = - post baseUrl (Endpoint.Repositories Nothing Nothing) body Vela.decodeRepository + post baseUrl (Api.Endpoint.Repositories Nothing Nothing) body Vela.decodeRepository |> withAuth session @@ -122,7 +88,7 @@ enableRepo baseUrl session body = -} getOrgRepos : String -> Session -> { a | org : String } -> Request (List Vela.Repository) getOrgRepos baseUrl session { org } = - get baseUrl (Endpoint.OrgRepositories Nothing Nothing org) Vela.decodeRepositories + get baseUrl (Api.Endpoint.OrgRepositories Nothing Nothing org) Vela.decodeRepositories |> withAuth session @@ -130,7 +96,7 @@ getOrgRepos baseUrl session { org } = -} getRepoBuilds : String -> Session -> { a | org : String, repo : String, pageNumber : Maybe Int, perPage : Maybe Int, maybeEvent : Maybe String } -> Request (List Vela.Build) getRepoBuilds baseUrl session options = - get baseUrl (Endpoint.Builds options.pageNumber options.perPage options.maybeEvent options.org options.repo) Vela.decodeBuilds + get baseUrl (Api.Endpoint.Builds options.pageNumber options.perPage options.maybeEvent options.org options.repo) Vela.decodeBuilds |> withAuth session @@ -138,5 +104,5 @@ getRepoBuilds baseUrl session options = -} getRepoDeployments : String -> Session -> { a | org : String, repo : String, pageNumber : Maybe Int, perPage : Maybe Int } -> Request (List Vela.Deployment) getRepoDeployments baseUrl session options = - get baseUrl (Endpoint.Deployments options.pageNumber options.perPage options.org options.repo) Vela.decodeDeployments + get baseUrl (Api.Endpoint.Deployments options.pageNumber options.perPage options.org options.repo) Vela.decodeDeployments |> withAuth session diff --git a/src/elm/Auth.elm b/src/elm/Auth.elm index 2309b1e49..1d7123f0c 100644 --- a/src/elm/Auth.elm +++ b/src/elm/Auth.elm @@ -32,7 +32,7 @@ onPageLoad shared route = Unauthenticated -> Auth.Action.pushRoute - { path = Route.Path.Login_ + { path = Route.Path.AccountLogin_ , query = Dict.fromList [ ( "from", Route.toString route ) ] diff --git a/src/elm/Components/Crumbs.elm b/src/elm/Components/Crumbs.elm index c728fbcfd..eb3c5904c 100644 --- a/src/elm/Components/Crumbs.elm +++ b/src/elm/Components/Crumbs.elm @@ -333,21 +333,21 @@ toCrumbs path = -- ( "#" ++ buildNumber, Nothing ) -- in -- [ overviewCrumbLink, orgReposCrumbLink, repoBuildsCrumbLink, buildNumberCrumbStatic ] - Route.Path.Login_ -> + Route.Path.AccountLogin_ -> let loginCrumbStatic = ( "Login", Nothing ) in [ accountCrumbStatic, loginCrumbStatic ] - Route.Path.Logout_ -> + Route.Path.AccountLogout_ -> let logoutCrumbStatic = ( "Logout", Nothing ) in [ accountCrumbStatic, logoutCrumbStatic ] - Route.Path.Authenticate_ -> + Route.Path.AccountAuthenticate_ -> let loginCrumbStatic = ( "Login", Nothing ) diff --git a/src/elm/Components/Header.elm b/src/elm/Components/Header.elm index b3fc990e7..d35c8a774 100644 --- a/src/elm/Components/Header.elm +++ b/src/elm/Components/Header.elm @@ -61,7 +61,7 @@ view { session, feedbackLink, docsLink, theme, setTheme, help, showId, showHideI , li [ class "identity-menu-item" ] [ a [ Util.testAttribute "logout-link" - , Route.Path.href Route.Path.Logout_ + , Route.Path.href Route.Path.AccountLogout_ , attribute "role" "menuitem" ] [ text "Logout" ] diff --git a/src/elm/Effect.elm b/src/elm/Effect.elm index f56a9c265..d9df1a201 100644 --- a/src/elm/Effect.elm +++ b/src/elm/Effect.elm @@ -9,7 +9,7 @@ module Effect exposing , sendCmd, sendMsg , pushRoute, replaceRoute, loadExternalUrl , map, toCmd - , addAlertError, addAlertSuccess, alertsUpdate, enableRepo, focusOn, getCurrentUser, getOrgRepos, getRepoBuilds, getRepoDeployments, gotoPage, handleHttpError, logout, pushPath, setTheme, updateFavorites + , addAlertError, addAlertSuccess, alertsUpdate, clearRedirect, enableRepo, finishAuthentication, focusOn, getCurrentUser, getOrgRepos, getRepoBuilds, getRepoDeployments, gotoPage, handleHttpError, logout, pushPath, setRedirect, setTheme, updateFavorites ) {-| @@ -24,7 +24,7 @@ module Effect exposing -} import Api.Api as Api -import Api.Operations_ +import Api.Operations import Auth.Session exposing (Session(..)) import Browser.Navigation import Components.Alerts exposing (Alert) @@ -32,6 +32,8 @@ import Components.Favorites as Favorites import Dict exposing (Dict) import Http import Http.Detailed +import Interop +import Json.Encode import Route import Route.Path import Shared.Model @@ -222,6 +224,21 @@ setTheme options = SendSharedMsg <| Shared.Msg.SetTheme options +setRedirect : { redirect : String } -> Effect msg +setRedirect options = + sendCmd <| Interop.setRedirect <| Json.Encode.string options.redirect + + +clearRedirect : {} -> Effect msg +clearRedirect _ = + sendCmd <| Interop.setRedirect <| Json.Encode.null + + +finishAuthentication : { code : Maybe String, state : Maybe String } -> Effect msg +finishAuthentication options = + SendSharedMsg <| Shared.Msg.FinishAuthentication options + + logout : {} -> Effect msg logout _ = SendSharedMsg <| Shared.Msg.Logout @@ -256,7 +273,7 @@ enableRepo options = in Api.try options.onResponse - (Api.Operations_.enableRepo options.baseUrl options.session body) + (Api.Operations.enableRepo options.baseUrl options.session body) |> sendCmd @@ -274,7 +291,7 @@ getRepoBuilds : getRepoBuilds options = Api.try options.onResponse - (Api.Operations_.getRepoBuilds options.baseUrl options.session options) + (Api.Operations.getRepoBuilds options.baseUrl options.session options) |> sendCmd @@ -288,7 +305,7 @@ getOrgRepos : getOrgRepos options = Api.try options.onResponse - (Api.Operations_.getOrgRepos options.baseUrl options.session options) + (Api.Operations.getOrgRepos options.baseUrl options.session options) |> sendCmd @@ -305,7 +322,7 @@ getRepoDeployments : getRepoDeployments options = Api.try options.onResponse - (Api.Operations_.getRepoDeployments options.baseUrl options.session options) + (Api.Operations.getRepoDeployments options.baseUrl options.session options) |> sendCmd diff --git a/src/elm/Interop.elm b/src/elm/Interop.elm index 8bb18998d..7376f91e7 100644 --- a/src/elm/Interop.elm +++ b/src/elm/Interop.elm @@ -3,10 +3,17 @@ SPDX-License-Identifier: Apache-2.0 --} -port module Interop exposing (onGraphInteraction, onThemeChange, renderBuildGraph, setFavicon, setRedirect, setTheme) +port module Interop exposing + ( onGraphInteraction + , onThemeChange + , renderBuildGraph + , setFavicon + , setRedirect + , setTheme + ) -import Json.Decode as Decode -import Json.Encode as Encode +import Json.Decode +import Json.Encode @@ -15,7 +22,7 @@ import Json.Encode as Encode {-| outbound -} -port setRedirect : Encode.Value -> Cmd msg +port setRedirect : Json.Encode.Value -> Cmd msg @@ -24,12 +31,12 @@ port setRedirect : Encode.Value -> Cmd msg {-| inbound -} -port onThemeChange : (Decode.Value -> msg) -> Sub msg +port onThemeChange : (Json.Decode.Value -> msg) -> Sub msg {-| outbound -} -port setTheme : Encode.Value -> Cmd msg +port setTheme : Json.Encode.Value -> Cmd msg @@ -38,7 +45,7 @@ port setTheme : Encode.Value -> Cmd msg {-| outbound -} -port setFavicon : Encode.Value -> Cmd msg +port setFavicon : Json.Encode.Value -> Cmd msg @@ -47,9 +54,9 @@ port setFavicon : Encode.Value -> Cmd msg {-| outbound -} -port renderBuildGraph : Encode.Value -> Cmd msg +port renderBuildGraph : Json.Encode.Value -> Cmd msg {-| inbound -} -port onGraphInteraction : (Decode.Value -> msg) -> Sub msg +port onGraphInteraction : (Json.Decode.Value -> msg) -> Sub msg diff --git a/src/elm/Main.elm b/src/elm/Main.elm index 74768d87f..567eccea0 100644 --- a/src/elm/Main.elm +++ b/src/elm/Main.elm @@ -5,20 +5,13 @@ SPDX-License-Identifier: Apache-2.0 module Main exposing (main) -import Api.Api -import Api.Operations_ import Auth import Auth.Action -import Auth.Jwt exposing (JwtAccessToken, JwtAccessTokenClaims, extractJwtClaims) -import Auth.Session exposing (Session(..), SessionDetails, refreshAccessToken) import Browser import Browser.Events exposing (Visibility(..)) import Browser.Navigation -import Components.Alerts as Alerts exposing (Alert) import Dict import Effect exposing (Effect) -import Http -import Http.Detailed import Interop import Json.Decode import Json.Encode @@ -42,22 +35,11 @@ import Pages.NotFound_ import Pages.Org_.Repo_ import Pages.Org_.Repo_.Deployments_ import Pages.Org_Repos -import RemoteData exposing (RemoteData(..), WebData) import Route exposing (Route) import Route.Path import Shared import Task -import Time - exposing - ( Posix - , Zone - , every - , here - ) -import Toasty as Alerting exposing (Stack) import Url exposing (Url) -import Utils.Errors as Errors exposing (Error, addErrorString) -import Utils.Interval as Interval exposing (Interval(..), RefreshData) import Vela import View exposing (View) @@ -99,21 +81,6 @@ init json url key = { page, layout } = initPageAndLayout { key = key, url = url, shared = sharedModel, layout = Nothing } - - setTimeZone : Cmd Msg - setTimeZone = - Task.perform AdjustTimeZone here - - setTime : Cmd Msg - setTime = - Task.perform AdjustTime Time.now - - fetchInitialTokenCmd = - if String.length sharedModel.velaRedirect == 0 then - Api.Api.try TokenResponse <| Api.Operations_.getToken sharedModel.velaAPI - - else - Cmd.none in ( { url = url , key = key @@ -126,11 +93,10 @@ init json url key = , layout |> Maybe.map Tuple.second |> Maybe.withDefault Cmd.none , fromSharedEffect { key = key, url = url, shared = sharedModel } sharedEffect - -- custom initialization effects - , Interop.setTheme <| Vela.encodeTheme sharedModel.theme - , setTimeZone - , setTime - , fetchInitialTokenCmd + -- need to reference these interops to let the app load properly + -- this should be removed when build graph and refresh logic are implemented + , Interop.renderBuildGraph Json.Encode.null + , Interop.setFavicon Json.Encode.null ] ) @@ -269,7 +235,7 @@ initPageAndLayout : } initPageAndLayout model = case Route.Path.fromUrl model.url of - Route.Path.Login_ -> + Route.Path.AccountLogin_ -> let page : Page.Page Pages.Account.Login_.Model Pages.Account.Login_.Msg page = @@ -337,7 +303,7 @@ initPageAndLayout model = } ) - Route.Path.Logout_ -> + Route.Path.AccountLogout_ -> { page = ( Main.Pages.Model.Redirecting_ , Effect.logout {} |> Effect.toCmd { key = model.key, url = model.url, shared = model.shared, fromSharedMsg = Shared, batch = Batch, toCmd = Task.succeed >> Task.perform identity } @@ -345,7 +311,7 @@ initPageAndLayout model = , layout = Nothing } - Route.Path.Authenticate_ -> + Route.Path.AccountAuthenticate_ -> let route = Route.fromUrl () model.url @@ -358,11 +324,8 @@ initPageAndLayout model = in { page = ( Main.Pages.Model.Redirecting_ - , Cmd.batch - [ Api.Api.try TokenResponse <| - Api.Operations_.finishAuthentication model.shared.velaAPI <| - Vela.AuthParams code state - ] + , Effect.finishAuthentication { code = code, state = state } + |> Effect.toCmd { key = model.key, url = model.url, shared = model.shared, fromSharedMsg = Shared, batch = Batch, toCmd = Task.succeed >> Task.perform identity } ) , layout = Nothing } @@ -556,22 +519,13 @@ runWhenAuthenticatedWithLayout model toRecord = type Msg - = UrlRequested Browser.UrlRequest + = NoOp + | UrlRequested Browser.UrlRequest | UrlChanged Url | Page Main.Pages.Msg.Msg | Layout Main.Layouts.Msg.Msg | Shared Shared.Msg | Batch (List Msg) - -- AUTH - | TokenResponse (Result (Http.Detailed.Error String) ( Http.Metadata, JwtAccessToken )) - | RefreshAccessToken - -- Time - | AdjustTimeZone Zone - | AdjustTime Posix - | Tick Interval Posix - -- Other - | HandleError Error - | AlertsUpdate (Alerting.Msg Alert) update : Msg -> Model -> ( Model, Cmd Msg ) @@ -581,6 +535,9 @@ update msg model = model.shared in case msg of + NoOp -> + ( model, Cmd.none ) + UrlRequested (Browser.Internal url) -> ( model , Browser.Navigation.pushUrl model.key (Url.toString url) @@ -704,188 +661,6 @@ update msg model = |> Cmd.batch ) - TokenResponse response -> - let - route = - Route.fromUrl () model.url - - -- todo: how do we capture the scenario where - -- the user is logged off a page and we need to - -- redirect them back to the page they were on? - velaRedirect = - case shared.velaRedirect of - "" -> - case Dict.get "from" route.query of - Just f -> - f - - Nothing -> - "/" - - _ -> - shared.velaRedirect - in - case response of - Ok ( _, token ) -> - let - currentSession : Session - currentSession = - model.shared.session - - payload : JwtAccessTokenClaims - payload = - extractJwtClaims token - - newSessionDetails : SessionDetails - newSessionDetails = - SessionDetails token payload.exp payload.sub - - actions : List (Cmd Msg) - actions = - case currentSession of - Unauthenticated -> - [ Browser.Navigation.pushUrl model.key velaRedirect - ] - - Authenticated _ -> - [] - in - ( { model - | shared = - { shared - | session = Authenticated newSessionDetails - , velaRedirect = "" - } - } - , Cmd.batch <| - actions - ++ [ Interop.setRedirect Json.Encode.null - , refreshAccessToken RefreshAccessToken newSessionDetails - ] - ) - - Err error -> - let - redirectPage = - case model.page of - Main.Pages.Model.AccountLogin_ _ -> - Cmd.none - - _ -> - Browser.Navigation.pushUrl model.key <| Route.Path.toString Route.Path.Login_ - in - case error of - Http.Detailed.BadStatus meta _ -> - case meta.statusCode of - 401 -> - let - actions : List (Cmd Msg) - actions = - case model.shared.session of - Unauthenticated -> - [ redirectPage - , Interop.setRedirect <| Json.Encode.string velaRedirect - ] - - Authenticated _ -> - [ addErrorString "Your session has expired or you logged in somewhere else, please log in again." HandleError - , redirectPage - , Interop.setRedirect <| Json.Encode.string velaRedirect - ] - in - ( { model - | shared = - { shared - | session = - Unauthenticated - , velaRedirect = velaRedirect - } - } - , Cmd.batch actions - ) - - _ -> - ( { model - | shared = - { shared - | session = Unauthenticated - , velaRedirect = velaRedirect - } - } - , Cmd.batch - [ Errors.addError HandleError error - , redirectPage - ] - ) - - _ -> - ( { model - | shared = - { shared - | session = Unauthenticated - , velaRedirect = velaRedirect - } - } - , Cmd.batch - [ Errors.addError HandleError error - , redirectPage - ] - ) - - RefreshAccessToken -> - ( model, Api.Api.try TokenResponse <| Api.Operations_.getToken shared.velaAPI ) - - -- Time - AdjustTimeZone newZone -> - ( { model | shared = { shared | zone = newZone } } - , Cmd.none - ) - - AdjustTime newTime -> - ( { model | shared = { shared | time = newTime } } - , Cmd.none - ) - - Tick interval time -> - ( model, Cmd.none ) - - -- case interval of - -- OneSecond -> - -- let - -- ( favicon, updateFavicon ) = - -- refreshFavicon model.legacyPage model.shared.favicon rm.build.build - -- in - -- ( { model | shared = { shared | time = time, favicon = favicon } } - -- , Cmd.batch - -- [ updateFavicon - -- , refreshRenderBuildGraph model - -- ] - -- ) - -- FiveSecond -> - -- ( model, refreshPage model ) - -- OneSecondHidden -> - -- let - -- ( favicon, cmd ) = - -- refreshFavicon model.legacyPage model.shared.favicon rm.build.build - -- in - -- ( { model | shared = { shared | time = time, favicon = favicon } }, cmd ) - -- FiveSecondHidden data -> - -- ( model, refreshPageHidden model data ) - -- Other - HandleError error -> - let - ( sharedWithAlert, cmd ) = - Alerting.addToastIfUnique Alerts.errorConfig AlertsUpdate (Alerts.Error "Error" error) ( model.shared, Cmd.none ) - in - ( { model | shared = sharedWithAlert }, cmd ) - - AlertsUpdate subMsg -> - let - ( sharedWithAlert, cmd ) = - Alerting.update Alerts.successConfig AlertsUpdate subMsg model.shared - in - ( { model | shared = sharedWithAlert }, cmd ) - updateFromPage : Main.Pages.Msg.Msg -> Model -> ( Main.Pages.Model.Model, Cmd Msg ) updateFromPage msg model = @@ -1614,13 +1389,13 @@ hasNavigatedWithinNewLayout { from, to } = isAuthProtected : Route.Path.Path -> Bool isAuthProtected routePath = case routePath of - Route.Path.Login_ -> + Route.Path.AccountLogin_ -> False - Route.Path.Logout_ -> + Route.Path.AccountLogout_ -> True - Route.Path.Authenticate_ -> + Route.Path.AccountAuthenticate_ -> False Route.Path.AccountSettings_ -> diff --git a/src/elm/Pages/Account/SourceRepos_.elm b/src/elm/Pages/Account/SourceRepos_.elm index cfe4777eb..5152fa9fa 100644 --- a/src/elm/Pages/Account/SourceRepos_.elm +++ b/src/elm/Pages/Account/SourceRepos_.elm @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 module Pages.Account.SourceRepos_ exposing (..) import Api.Api as Api -import Api.Operations_ +import Api.Operations import Auth import Components.Favorites as Favorites import Components.Search as Search @@ -151,7 +151,7 @@ update shared msg model = } , Api.try GetUserSourceReposResponse - (Api.Operations_.getUserSourceRepos shared.velaAPI shared.session) + (Api.Operations.getUserSourceRepos shared.velaAPI shared.session) |> Effect.sendCmd ) diff --git a/src/elm/Pages/Home_.elm b/src/elm/Pages/Home_.elm index f10225719..9179245e6 100644 --- a/src/elm/Pages/Home_.elm +++ b/src/elm/Pages/Home_.elm @@ -305,8 +305,7 @@ viewFavorite favorites filtered favorite = , a [ class "button" , Util.testAttribute "repo-view" - - -- , Routes.href <| Routes.RepositoryBuilds org repo Nothing Nothing Nothing + , Route.Path.href <| Route.Path.Org_Repo_ { org = org, repo = repo } ] [ text "View" ] ] diff --git a/src/elm/Pages/Org_/Repo_.elm b/src/elm/Pages/Org_/Repo_.elm index 65e34487b..96c39e9ed 100644 --- a/src/elm/Pages/Org_/Repo_.elm +++ b/src/elm/Pages/Org_/Repo_.elm @@ -11,6 +11,7 @@ import Components.Builds import Components.Pager import Dict import Effect exposing (Effect) +import Html exposing (text) import Http import Http.Detailed import Layouts @@ -145,8 +146,8 @@ update shared route msg model = -- todo: -- 1. write func in Effect.elm for "approveBuild" - -- 2. write Api.Operations_.approveBuild that uses it - -- look at the code in Api.Operations and the other funcs in Api.Operations_ for inspiration + -- 2. write Api.Operations.approveBuild that uses it + -- look at the code in Api.Operations and the other funcs in Api.Operations for inspiration -- 3. write ApproveBuildResponse Msg in this file -- 4. in ApproveBuildResponse, create a toasty? -- look at how it's done in Main.elm @@ -251,12 +252,16 @@ view shared route model = in { title = route.params.org ++ "/" ++ route.params.repo , body = - [ Components.Builds.viewHeader - { maybeEvent = Dict.get "event" route.query - , showFullTimestamps = model.showFullTimestamps - , filterByEvent = FilterByEvent - , showHideFullTimestamps = ShowHideFullTimestamps - } + [ if List.length (RemoteData.withDefault [] model.builds) > 0 then + Components.Builds.viewHeader + { maybeEvent = Dict.get "event" route.query + , showFullTimestamps = model.showFullTimestamps + , filterByEvent = FilterByEvent + , showHideFullTimestamps = ShowHideFullTimestamps + } + + else + text "" , Components.Pager.view model.pager Components.Pager.defaultLabels GotoPage , Components.Builds.view shared { msgs = msgs diff --git a/src/elm/Route/Path.elm b/src/elm/Route/Path.elm index 897fd1640..662de4947 100644 --- a/src/elm/Route/Path.elm +++ b/src/elm/Route/Path.elm @@ -17,9 +17,9 @@ import Url.Parser exposing (()) type Path = Home_ - | Login_ - | Logout_ - | Authenticate_ + | AccountLogin_ + | AccountLogout_ + | AccountAuthenticate_ | AccountSettings_ | AccountSourceRepos_ | Org_Repos { org : String } @@ -48,13 +48,13 @@ fromString urlPath = Just Home_ [ "account", "login" ] -> - Just Login_ + Just AccountLogin_ [ "account", "logout" ] -> - Just Logout_ + Just AccountLogout_ [ "account", "authenticate" ] -> - Just Authenticate_ + Just AccountAuthenticate_ [ "account", "settings" ] -> Just AccountSettings_ @@ -100,13 +100,13 @@ toString path = Home_ -> [] - Login_ -> + AccountLogin_ -> [ "account", "login" ] - Logout_ -> + AccountLogout_ -> [ "account", "logout" ] - Authenticate_ -> + AccountAuthenticate_ -> [ "account", "authenticate" ] AccountSettings_ -> diff --git a/src/elm/Shared.elm b/src/elm/Shared.elm index 7ccc014fd..3b221173d 100644 --- a/src/elm/Shared.elm +++ b/src/elm/Shared.elm @@ -8,7 +8,8 @@ module Shared exposing (Flags, Model, Msg, decoder, init, subscriptions, update) -- todo: these need to be refined, only expose what is needed import Api.Api as Api -import Api.Operations_ +import Api.Operations +import Auth.Jwt import Auth.Session exposing (..) import Browser.Dom exposing (..) import Browser.Events exposing (Visibility(..)) @@ -17,8 +18,9 @@ import Components.Favorites as Favorites import Dict exposing (..) import Effect exposing (Effect) import Http +import Http.Detailed import Interop -import Json.Decode as Decode exposing (..) +import Json.Decode import Json.Decode.Pipeline exposing (required) import RemoteData exposing (RemoteData(..)) import Route exposing (Route) @@ -26,7 +28,7 @@ import Route.Path import Shared.Model import Shared.Msg import Task -import Time exposing (..) +import Time import Toasty as Alerting import Url exposing (..) import Utils.Errors as Errors @@ -51,10 +53,6 @@ type alias Model = Shared.Model.Model - --- todo: comments, what goes in here, why - - type alias Flags = { isDev : Bool , velaAPI : String @@ -68,28 +66,27 @@ type alias Flags = } -decoder : Decode.Decoder Flags +decoder : Json.Decode.Decoder Flags decoder = - Decode.succeed Flags - |> required "isDev" Decode.bool - |> required "velaAPI" Decode.string - |> required "velaFeedbackURL" Decode.string - |> required "velaDocsURL" Decode.string - |> required "velaTheme" Decode.string - |> required "velaRedirect" Decode.string - |> required "velaLogBytesLimit" Decode.int - |> required "velaMaxBuildLimit" Decode.int - |> required "velaScheduleAllowlist" Decode.string + Json.Decode.succeed Flags + |> required "isDev" Json.Decode.bool + |> required "velaAPI" Json.Decode.string + |> required "velaFeedbackURL" Json.Decode.string + |> required "velaDocsURL" Json.Decode.string + |> required "velaTheme" Json.Decode.string + |> required "velaRedirect" Json.Decode.string + |> required "velaLogBytesLimit" Json.Decode.int + |> required "velaMaxBuildLimit" Json.Decode.int + |> required "velaScheduleAllowlist" Json.Decode.string -- todo: comments -init : Result Decode.Error Flags -> Route () -> ( Model, Effect Msg ) +init : Result Json.Decode.Error Flags -> Route () -> ( Model, Effect Msg ) init flagsResult route = let - flags : Flags flags = case flagsResult of Ok value -> @@ -109,6 +106,28 @@ init flagsResult route = , velaMaxBuildLimit = 0 , velaScheduleAllowlist = "" } + + setTimeZone = + Task.perform (\zone -> Shared.Msg.AdjustTimeZone { zone = zone }) Time.here + |> Effect.sendCmd + + setTime = + Task.perform (\time -> Shared.Msg.AdjustTime { time = time }) Time.now + |> Effect.sendCmd + + setTheme = + flags.velaTheme + |> stringToTheme + |> Vela.encodeTheme + |> Interop.setTheme + |> Effect.sendCmd + + fetchInitialToken = + if String.length flags.velaRedirect == 0 then + Effect.sendCmd <| Api.try Shared.Msg.TokenResponse <| Api.Operations.getToken flags.velaAPI + + else + Effect.none in -- todo: these need to be logically ordered (flags, session, user, data models, etc) ( { session = Unauthenticated @@ -121,8 +140,8 @@ init flagsResult route = , velaMaxBuildLimit = flags.velaMaxBuildLimit , velaScheduleAllowlist = Util.stringToAllowlist flags.velaScheduleAllowlist , toasties = Alerting.initialState - , zone = utc - , time = millisToPosix 0 + , zone = Time.utc + , time = Time.millisToPosix 0 , repo = defaultRepoModel , theme = stringToTheme flags.velaTheme , shift = False @@ -131,13 +150,13 @@ init flagsResult route = , favicon = defaultFavicon , pipeline = defaultPipeline , templates = defaultPipelineTemplates - - -- todo: these need to be refactored with Msg - -- , schedulesModel = initSchedulesModel - -- , secretsModel = initSecretsModel - -- , deploymentModel = initDeploymentsModel } - , Effect.none + , Effect.batch + [ setTimeZone + , setTime + , setTheme + , fetchInitialToken + ] ) @@ -157,18 +176,41 @@ update route msg model = , Effect.none ) + -- TIME + Shared.Msg.AdjustTimeZone options -> + ( { model | zone = options.zone } + , Effect.none + ) + + Shared.Msg.AdjustTime options -> + ( { model | time = options.time } + , Effect.none + ) + + -- REFRESH + Shared.Msg.Tick options -> + ( model, Effect.none ) + -- AUTH + Shared.Msg.FinishAuthentication options -> + ( model + , Effect.sendCmd <| + Api.try Shared.Msg.TokenResponse <| + Api.Operations.finishAuthentication model.velaAPI <| + Vela.AuthParams options.code options.state + ) + Shared.Msg.Logout -> ( model , Api.try Shared.Msg.LogoutResponse - (Api.Operations_.logout model.velaAPI model.session) + (Api.Operations.logout model.velaAPI model.session) |> Effect.sendCmd ) Shared.Msg.LogoutResponse _ -> ( { model | session = Unauthenticated } - , Effect.pushPath <| Route.Path.Login_ + , Effect.pushPath <| Route.Path.AccountLogin_ ) -- USER @@ -176,7 +218,7 @@ update route msg model = ( { model | user = Loading } , Api.try Shared.Msg.CurrentUserResponse - (Api.Operations_.getCurrentUser model.velaAPI model.session) + (Api.Operations.getCurrentUser model.velaAPI model.session) |> Effect.sendCmd ) @@ -220,7 +262,7 @@ update route msg model = ( model , Api.try (Shared.Msg.RepoFavoriteResponse { favorite = favorite, favorited = favorited }) - (Api.Operations_.updateCurrentUser model.velaAPI model.session body) + (Api.Operations.updateCurrentUser model.velaAPI model.session body) |> Effect.sendCmd ) @@ -244,6 +286,10 @@ update route msg model = , Effect.handleHttpError { httpError = error } ) + -- BUILD GRAPH + Shared.Msg.BuildGraphInteraction _ -> + ( model, Effect.none ) + -- PAGINATION Shared.Msg.GotoPage options -> let @@ -330,7 +376,128 @@ update route msg model = -- successfully focus the dom ( model, Effect.none ) + Shared.Msg.TokenResponse response -> + let + -- todo: how do we capture the scenario where + -- the user is logged off a page and we need to + -- redirect them back to the page they were on? + velaRedirect = + case model.velaRedirect of + "" -> + case Dict.get "from" route.query of + Just f -> + f + + Nothing -> + "/" + + _ -> + model.velaRedirect + in + case response of + Ok ( _, token ) -> + let + currentSession = + model.session + + payload = + Auth.Jwt.extractJwtClaims token + + newSessionDetails = + SessionDetails token payload.exp payload.sub + + actions = + case currentSession of + Unauthenticated -> + velaRedirect + |> Route.Path.fromString + |> Maybe.withDefault Route.Path.Home_ + |> Effect.pushPath + |> List.singleton + + Authenticated _ -> + [] + in + ( { model + | session = Authenticated newSessionDetails + , velaRedirect = "" + } + , Effect.batch <| + actions + ++ [ Effect.clearRedirect {} + , refreshAccessToken Shared.Msg.RefreshAccessToken newSessionDetails |> Effect.sendCmd + ] + ) + + Err error -> + let + redirectPage = + Effect.none + + -- case model.page of + -- Main.Pages.Model.AccountLogin_ _ -> + -- Cmd.none + -- _ -> + -- Browser.Navigation.pushUrl model.key <| Route.Path.toString Route.Path.Login_ + in + case error of + Http.Detailed.BadStatus meta _ -> + case meta.statusCode of + 401 -> + let + actions = + case model.session of + Unauthenticated -> + [ redirectPage + , Effect.setRedirect { redirect = velaRedirect } + ] + + Authenticated _ -> + [ Effect.addAlertError { content = "Your session has expired or you logged in somewhere else, please log in again.", addToastIfUnique = True } + , redirectPage + , Effect.setRedirect { redirect = velaRedirect } + ] + in + ( { model + | session = + Unauthenticated + , velaRedirect = velaRedirect + } + , Effect.batch actions + ) + + _ -> + ( { model + | session = Unauthenticated + , velaRedirect = velaRedirect + } + , Effect.batch + [ Effect.handleHttpError { httpError = error } + , redirectPage + ] + ) + + _ -> + ( { model + | session = Unauthenticated + , velaRedirect = velaRedirect + } + , Effect.batch + [ Effect.handleHttpError { httpError = error } + , redirectPage + ] + ) + + Shared.Msg.RefreshAccessToken -> + ( model + , Effect.sendCmd <| Api.try Shared.Msg.TokenResponse <| Api.Operations.getToken model.velaAPI + ) + subscriptions : Route () -> Model -> Sub Msg subscriptions _ model = - Sub.none + Sub.batch + [ -- todo: move this to the build graph page, when we have one + Interop.onGraphInteraction + (Vela.decodeOnGraphInteraction Shared.Msg.BuildGraphInteraction Shared.Msg.NoOp) + ] diff --git a/src/elm/Shared/Model.elm b/src/elm/Shared/Model.elm index 9abb48355..2ed8a5c78 100644 --- a/src/elm/Shared/Model.elm +++ b/src/elm/Shared/Model.elm @@ -49,9 +49,4 @@ type alias Model = , pipeline : PipelineModel , templates : PipelineTemplates , buildMenuOpen : List Int - - -- todo: these need to be refactored with Msg - -- , schedulesModel : Pages.Schedules.Model.Model Msg - -- , secretsModel : Pages.Secrets.Model.Model Msg - -- , deploymentModel : Pages.Deployments.Model.Model Msg } diff --git a/src/elm/Shared/Msg.elm b/src/elm/Shared/Msg.elm index 9cf62ccd8..059cd5ed9 100644 --- a/src/elm/Shared/Msg.elm +++ b/src/elm/Shared/Msg.elm @@ -5,18 +5,29 @@ SPDX-License-Identifier: Apache-2.0 module Shared.Msg exposing (Msg(..)) +import Auth.Jwt import Browser.Dom import Components.Alerts exposing (Alert) import Components.Favorites as Favorites import Http import Http.Detailed +import Time import Toasty as Alerting +import Utils.Interval as Interval import Vela exposing (CurrentUser) type Msg = NoOp + -- TIME + | AdjustTimeZone { zone : Time.Zone } + | AdjustTime { time : Time.Posix } + --REFRESH + | Tick { interval : Interval.Interval, time : Time.Posix } -- AUTH + | TokenResponse (Result (Http.Detailed.Error String) ( Http.Metadata, Auth.Jwt.JwtAccessToken )) + | RefreshAccessToken + | FinishAuthentication { code : Maybe String, state : Maybe String } | Logout | LogoutResponse (Result (Http.Detailed.Error String) ( Http.Metadata, String )) -- USER @@ -25,6 +36,8 @@ type Msg -- FAVORITES | UpdateFavorites { org : String, maybeRepo : Maybe String, updateType : Favorites.UpdateType } | RepoFavoriteResponse { favorite : String, favorited : Bool } (Result (Http.Detailed.Error String) ( Http.Metadata, CurrentUser )) + -- BUILD GRAPH + | BuildGraphInteraction Vela.BuildGraphInteraction -- PAGINATION | GotoPage { pageNumber : Int } -- THEME diff --git a/src/elm/Vela.elm b/src/elm/Vela.elm index 0e6df988e..69ca124e0 100644 --- a/src/elm/Vela.elm +++ b/src/elm/Vela.elm @@ -9,6 +9,7 @@ module Vela exposing , Build , BuildGraph , BuildGraphEdge + , BuildGraphInteraction , BuildGraphModel , BuildGraphNode , BuildModel @@ -34,7 +35,6 @@ module Vela exposing , Favorites , Field , FocusFragment - , GraphInteraction , Hook , HookNumber , Hooks @@ -101,6 +101,7 @@ module Vela exposing , decodeGraphInteraction , decodeHooks , decodeLog + , decodeOnGraphInteraction , decodePipelineConfig , decodePipelineExpand , decodePipelineTemplates @@ -187,14 +188,14 @@ module Vela exposing import Api.Pagination as Pagination import Bytes.Encode import Dict exposing (Dict) -import Json.Decode as Decode exposing (Decoder, andThen, bool, int, string, succeed) +import Json.Decode exposing (Decoder, andThen, bool, int, string, succeed) import Json.Decode.Extra exposing (dict2) import Json.Decode.Pipeline exposing (hardcoded, optional, required) -import Json.Encode as Encode exposing (Value) +import Json.Encode import LinkHeader exposing (WebLink) import RemoteData exposing (RemoteData(..), WebData) import Url.Builder as UB -import Utils.Errors as Errors exposing (Error) +import Utils.Errors as Errors import Visualization.DOT as DOT @@ -303,21 +304,21 @@ stringToTheme theme = decodeTheme : Decoder Theme decodeTheme = - Decode.string - |> Decode.andThen + Json.Decode.string + |> Json.Decode.andThen (\str -> - Decode.succeed <| stringToTheme str + Json.Decode.succeed <| stringToTheme str ) -encodeTheme : Theme -> Encode.Value +encodeTheme : Theme -> Json.Encode.Value encodeTheme theme = case theme of Light -> - Encode.string "theme-light" + Json.Encode.string "theme-light" _ -> - Encode.string "theme-dark" + Json.Encode.string "theme-dark" @@ -339,10 +340,10 @@ type alias Favorites = decodeCurrentUser : Decoder CurrentUser decodeCurrentUser = - Decode.succeed CurrentUser + Json.Decode.succeed CurrentUser |> required "id" int |> required "name" string - |> optional "favorites" (Decode.list string) [] + |> optional "favorites" (Json.Decode.list string) [] |> required "active" bool |> required "admin" bool @@ -358,10 +359,10 @@ defaultUpdateUserPayload = UpdateUserPayload Nothing Nothing -encodeUpdateUser : UpdateUserPayload -> Encode.Value +encodeUpdateUser : UpdateUserPayload -> Json.Encode.Value encodeUpdateUser user = - Encode.object - [ ( "favorites", encodeOptionalList Encode.string user.favorites ) + Json.Encode.object + [ ( "favorites", encodeOptionalList Json.Encode.string user.favorites ) ] @@ -843,7 +844,7 @@ updateBuildServicesLogs update rm = { rm | build = { b | services = { s | logs = update } } } -updateBuildPipelineConfig : ( WebData PipelineConfig, Error ) -> PipelineModel -> PipelineModel +updateBuildPipelineConfig : ( WebData PipelineConfig, Errors.Error ) -> PipelineModel -> PipelineModel updateBuildPipelineConfig update pipeline = { pipeline | config = update } @@ -965,12 +966,12 @@ type Enabling decodeRepositories : Decoder (List Repository) decodeRepositories = - Decode.list decodeRepository + Json.Decode.list decodeRepository decodeRepository : Decoder Repository decodeRepository = - Decode.succeed Repository + Json.Decode.succeed Repository |> optional "id" int -1 |> optional "user_id" int -1 |> required "org" string @@ -1094,25 +1095,25 @@ type alias SourceRepositories = decodeSourceRepositories : Decoder SourceRepositories decodeSourceRepositories = - Decode.dict (Decode.list decodeRepository) + Json.Decode.dict (Json.Decode.list decodeRepository) -encodeEnableRepository : EnableRepositoryPayload -> Encode.Value +encodeEnableRepository : EnableRepositoryPayload -> Json.Encode.Value encodeEnableRepository repo = - Encode.object - [ ( "org", Encode.string <| repo.org ) - , ( "name", Encode.string <| repo.name ) - , ( "full_name", Encode.string <| repo.full_name ) - , ( "link", Encode.string <| repo.link ) - , ( "clone", Encode.string <| repo.clone ) - , ( "private", Encode.bool <| repo.private ) - , ( "trusted", Encode.bool <| repo.trusted ) - , ( "active", Encode.bool <| repo.active ) - , ( "allow_pull", Encode.bool <| repo.allow_pull ) - , ( "allow_push", Encode.bool <| repo.allow_push ) - , ( "allow_deploy", Encode.bool <| repo.allow_deploy ) - , ( "allow_tag", Encode.bool <| repo.allow_tag ) - , ( "allow_comment", Encode.bool <| repo.allow_comment ) + Json.Encode.object + [ ( "org", Json.Encode.string <| repo.org ) + , ( "name", Json.Encode.string <| repo.name ) + , ( "full_name", Json.Encode.string <| repo.full_name ) + , ( "link", Json.Encode.string <| repo.link ) + , ( "clone", Json.Encode.string <| repo.clone ) + , ( "private", Json.Encode.bool <| repo.private ) + , ( "trusted", Json.Encode.bool <| repo.trusted ) + , ( "active", Json.Encode.bool <| repo.active ) + , ( "allow_pull", Json.Encode.bool <| repo.allow_pull ) + , ( "allow_push", Json.Encode.bool <| repo.allow_push ) + , ( "allow_deploy", Json.Encode.bool <| repo.allow_deploy ) + , ( "allow_tag", Json.Encode.bool <| repo.allow_tag ) + , ( "allow_comment", Json.Encode.bool <| repo.allow_comment ) ] @@ -1178,44 +1179,44 @@ defaultUpdateRepositoryPayload = UpdateRepositoryPayload Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing -encodeUpdateRepository : UpdateRepositoryPayload -> Encode.Value +encodeUpdateRepository : UpdateRepositoryPayload -> Json.Encode.Value encodeUpdateRepository repo = - Encode.object - [ ( "active", encodeOptional Encode.bool repo.active ) - , ( "private", encodeOptional Encode.bool repo.private ) - , ( "trusted", encodeOptional Encode.bool repo.trusted ) - , ( "allow_pull", encodeOptional Encode.bool repo.allow_pull ) - , ( "allow_push", encodeOptional Encode.bool repo.allow_push ) - , ( "allow_deploy", encodeOptional Encode.bool repo.allow_deploy ) - , ( "allow_tag", encodeOptional Encode.bool repo.allow_tag ) - , ( "allow_comment", encodeOptional Encode.bool repo.allow_comment ) - , ( "visibility", encodeOptional Encode.string repo.visibility ) - , ( "approve_build", encodeOptional Encode.string repo.approve_build ) - , ( "build_limit", encodeOptional Encode.int repo.limit ) - , ( "timeout", encodeOptional Encode.int repo.timeout ) - , ( "counter", encodeOptional Encode.int repo.counter ) - , ( "pipeline_type", encodeOptional Encode.string repo.pipeline_type ) + Json.Encode.object + [ ( "active", encodeOptional Json.Encode.bool repo.active ) + , ( "private", encodeOptional Json.Encode.bool repo.private ) + , ( "trusted", encodeOptional Json.Encode.bool repo.trusted ) + , ( "allow_pull", encodeOptional Json.Encode.bool repo.allow_pull ) + , ( "allow_push", encodeOptional Json.Encode.bool repo.allow_push ) + , ( "allow_deploy", encodeOptional Json.Encode.bool repo.allow_deploy ) + , ( "allow_tag", encodeOptional Json.Encode.bool repo.allow_tag ) + , ( "allow_comment", encodeOptional Json.Encode.bool repo.allow_comment ) + , ( "visibility", encodeOptional Json.Encode.string repo.visibility ) + , ( "approve_build", encodeOptional Json.Encode.string repo.approve_build ) + , ( "build_limit", encodeOptional Json.Encode.int repo.limit ) + , ( "timeout", encodeOptional Json.Encode.int repo.timeout ) + , ( "counter", encodeOptional Json.Encode.int repo.counter ) + , ( "pipeline_type", encodeOptional Json.Encode.string repo.pipeline_type ) ] -encodeOptional : (a -> Encode.Value) -> Maybe a -> Encode.Value +encodeOptional : (a -> Json.Encode.Value) -> Maybe a -> Json.Encode.Value encodeOptional encoder value = case value of Just value_ -> encoder value_ Nothing -> - Encode.null + Json.Encode.null -encodeOptionalList : (a -> Encode.Value) -> Maybe (List a) -> Encode.Value +encodeOptionalList : (a -> Json.Encode.Value) -> Maybe (List a) -> Json.Encode.Value encodeOptionalList encoder value = case value of Just value_ -> - Encode.list encoder value_ + Json.Encode.list encoder value_ Nothing -> - Encode.null + Json.Encode.null buildUpdateRepoBoolPayload : Field -> Bool -> UpdateRepositoryPayload @@ -1286,7 +1287,7 @@ buildUpdateRepoIntPayload field value = type alias PipelineModel = - { config : ( WebData PipelineConfig, Error ) + { config : ( WebData PipelineConfig, Errors.Error ) , expanded : Bool , expanding : Bool , expand : Maybe String @@ -1308,7 +1309,7 @@ type alias PipelineConfig = type alias PipelineTemplates = { data : WebData Templates - , error : Error + , error : Errors.Error , show : Bool } @@ -1330,9 +1331,9 @@ defaultPipelineTemplates = PipelineTemplates NotAsked "" True -decodePipelineConfig : Decode.Decoder PipelineConfig +decodePipelineConfig : Json.Decode.Decoder PipelineConfig decodePipelineConfig = - Decode.succeed + Json.Decode.succeed (\data -> PipelineConfig data @@ -1342,19 +1343,19 @@ decodePipelineConfig = |> optional "data" string "" -decodePipelineExpand : Decode.Decoder String +decodePipelineExpand : Json.Decode.Decoder String decodePipelineExpand = - Decode.string + Json.Decode.string -decodePipelineTemplates : Decode.Decoder Templates +decodePipelineTemplates : Json.Decode.Decoder Templates decodePipelineTemplates = - Decode.dict decodeTemplate + Json.Decode.dict decodeTemplate -decodeTemplate : Decode.Decoder Template +decodeTemplate : Json.Decode.Decoder Template decodeTemplate = - Decode.succeed Template + Json.Decode.succeed Template |> optional "link" string "" |> optional "name" string "" |> optional "source" string "" @@ -1412,7 +1413,7 @@ type alias Build = decodeBuild : Decoder Build decodeBuild = - Decode.succeed Build + Json.Decode.succeed Build |> optional "id" int -1 |> optional "repository_id" int -1 |> optional "number" int -1 @@ -1454,16 +1455,16 @@ defaultBuildGraph = BuildGraph -1 -1 "" "" Dict.empty [] -encodeBuildGraphRenderData : BuildGraphRenderInteropData -> Encode.Value +encodeBuildGraphRenderData : BuildGraphRenderInteropData -> Json.Encode.Value encodeBuildGraphRenderData graphData = - Encode.object - [ ( "dot", Encode.string graphData.dot ) - , ( "buildID", Encode.int graphData.buildID ) - , ( "filter", Encode.string graphData.filter ) - , ( "focusedNode", Encode.int graphData.focusedNode ) - , ( "showServices", Encode.bool graphData.showServices ) - , ( "showSteps", Encode.bool graphData.showSteps ) - , ( "freshDraw", Encode.bool graphData.freshDraw ) + Json.Encode.object + [ ( "dot", Json.Encode.string graphData.dot ) + , ( "buildID", Json.Encode.int graphData.buildID ) + , ( "filter", Json.Encode.string graphData.filter ) + , ( "focusedNode", Json.Encode.int graphData.focusedNode ) + , ( "showServices", Json.Encode.bool graphData.showServices ) + , ( "showSteps", Json.Encode.bool graphData.showSteps ) + , ( "freshDraw", Json.Encode.bool graphData.freshDraw ) ] @@ -1522,32 +1523,32 @@ type alias BuildGraphEdge = decodeBuildGraph : Decoder BuildGraph decodeBuildGraph = - Decode.succeed BuildGraph + Json.Decode.succeed BuildGraph |> required "build_id" int |> required "build_number" int |> required "org" string |> required "repo" string |> required "nodes" (dict2 int decodeBuildGraphNode) - |> optional "edges" (Decode.list decodeEdge) [] + |> optional "edges" (Json.Decode.list decodeEdge) [] decodeBuildGraphNode : Decoder BuildGraphNode decodeBuildGraphNode = - Decode.succeed BuildGraphNode + Json.Decode.succeed BuildGraphNode |> required "cluster" int |> required "id" int - |> required "name" Decode.string + |> required "name" Json.Decode.string |> optional "status" string "" |> required "started_at" int |> required "finished_at" int - |> optional "steps" (Decode.list decodeStep) [] + |> optional "steps" (Json.Decode.list decodeStep) [] -- focused |> hardcoded False decodeEdge : Decoder BuildGraphEdge decodeEdge = - Decode.succeed BuildGraphEdge + Json.Decode.succeed BuildGraphEdge |> required "cluster" int |> required "source" int |> required "destination" int @@ -1556,16 +1557,26 @@ decodeEdge = |> hardcoded False -type alias GraphInteraction = +type alias BuildGraphInteraction = { eventType : String , href : String , nodeID : String } -decodeGraphInteraction : Decoder GraphInteraction +decodeOnGraphInteraction : (BuildGraphInteraction -> msg) -> msg -> Json.Decode.Value -> msg +decodeOnGraphInteraction msg noop interaction = + case Json.Decode.decodeValue decodeGraphInteraction interaction of + Ok interaction_ -> + msg interaction_ + + Err _ -> + noop + + +decodeGraphInteraction : Decoder BuildGraphInteraction decodeGraphInteraction = - Decode.succeed GraphInteraction + Json.Decode.succeed BuildGraphInteraction |> required "eventType" string |> optional "href" string "" |> optional "nodeID" string "-1" @@ -1575,7 +1586,7 @@ decodeGraphInteraction = -} decodeBuilds : Decoder Builds decodeBuilds = - Decode.list decodeBuild + Json.Decode.list decodeBuild {-| buildStatusDecoder : decodes string field "status" to the union type BuildStatus @@ -1824,7 +1835,7 @@ defaultStep = -} decodeStep : Decoder Step decodeStep = - Decode.succeed Step + Json.Decode.succeed Step |> optional "id" int -1 |> optional "build_id" int -1 |> optional "repo_id" int -1 @@ -1880,7 +1891,7 @@ type alias Service = -} decodeService : Decoder Service decodeService = - Decode.succeed Service + Json.Decode.succeed Service |> optional "id" int -1 |> optional "build_id" int -1 |> optional "repo_id" int -1 @@ -1949,7 +1960,7 @@ type alias Log = -} decodeLog : Decoder Log decodeLog = - Decode.succeed + Json.Decode.succeed (\id step_id service_id build_id repository_id data -> Log id @@ -2016,7 +2027,7 @@ type alias Hook = decodeHook : Decoder Hook decodeHook = - Decode.succeed Hook + Json.Decode.succeed Hook |> optional "id" int -1 |> optional "repo_id" int -1 |> optional "build_id" int -1 @@ -2035,7 +2046,7 @@ decodeHook = -} decodeHooks : Decoder Hooks decodeHooks = - Decode.list decodeHook + Json.Decode.list decodeHook type alias Hooks = @@ -2105,7 +2116,7 @@ buildUpdateSchedulePayload org repo name entry enabled branch = decodeSchedule : Decoder Schedule decodeSchedule = - Decode.succeed Schedule + Json.Decode.succeed Schedule |> optional "id" int -1 |> optional "repo.org" string "" |> optional "repo.repo" string "" @@ -2122,16 +2133,16 @@ decodeSchedule = decodeSchedules : Decoder Schedules decodeSchedules = - Decode.list decodeSchedule + Json.Decode.list decodeSchedule -encodeUpdateSchedule : UpdateSchedulePayload -> Encode.Value +encodeUpdateSchedule : UpdateSchedulePayload -> Json.Encode.Value encodeUpdateSchedule schedule = - Encode.object - [ ( "name", encodeOptional Encode.string schedule.name ) - , ( "entry", encodeOptional Encode.string schedule.entry ) - , ( "active", encodeOptional Encode.bool schedule.enabled ) - , ( "branch", encodeOptional Encode.string schedule.branch ) + Json.Encode.object + [ ( "name", encodeOptional Json.Encode.string schedule.name ) + , ( "entry", encodeOptional Json.Encode.string schedule.entry ) + , ( "active", encodeOptional Json.Encode.bool schedule.enabled ) + , ( "branch", encodeOptional Json.Encode.string schedule.branch ) ] @@ -2183,7 +2194,7 @@ toSecretTypeDecoder type_ = succeed RepoSecret _ -> - Decode.fail "unrecognized secret type" + Json.Decode.fail "unrecognized secret type" {-| secretTypeToString : helper to convert SecretType to string @@ -2251,7 +2262,7 @@ secretToKey secret = decodeSecret : Decoder Secret decodeSecret = - Decode.succeed Secret + Json.Decode.succeed Secret |> optional "id" int -1 |> optional "org" string "" |> optional "repo" string "" @@ -2259,8 +2270,8 @@ decodeSecret = |> optional "key" string "" |> optional "name" string "" |> optional "type" secretTypeDecoder RepoSecret - |> optional "images" (Decode.list string) [] - |> optional "events" (Decode.list string) [] + |> optional "images" (Json.Decode.list string) [] + |> optional "events" (Json.Decode.list string) [] |> optional "allow_command" bool False @@ -2268,7 +2279,7 @@ decodeSecret = -} decodeSecrets : Decoder Secrets decodeSecrets = - Decode.list decodeSecret + Json.Decode.list decodeSecret type alias Secrets = @@ -2288,18 +2299,18 @@ type alias UpdateSecretPayload = } -encodeUpdateSecret : UpdateSecretPayload -> Encode.Value +encodeUpdateSecret : UpdateSecretPayload -> Json.Encode.Value encodeUpdateSecret secret = - Encode.object - [ ( "type", encodeOptional Encode.string <| maybeSecretTypeToMaybeString secret.type_ ) - , ( "org", encodeOptional Encode.string secret.org ) - , ( "repo", encodeOptional Encode.string secret.repo ) - , ( "team", encodeOptional Encode.string secret.team ) - , ( "name", encodeOptional Encode.string secret.name ) - , ( "value", encodeOptional Encode.string secret.value ) - , ( "events", encodeOptionalList Encode.string secret.events ) - , ( "images", encodeOptionalList Encode.string secret.images ) - , ( "allow_command", encodeOptional Encode.bool secret.allowCommand ) + Json.Encode.object + [ ( "type", encodeOptional Json.Encode.string <| maybeSecretTypeToMaybeString secret.type_ ) + , ( "org", encodeOptional Json.Encode.string secret.org ) + , ( "repo", encodeOptional Json.Encode.string secret.repo ) + , ( "team", encodeOptional Json.Encode.string secret.team ) + , ( "name", encodeOptional Json.Encode.string secret.name ) + , ( "value", encodeOptional Json.Encode.string secret.value ) + , ( "events", encodeOptionalList Json.Encode.string secret.events ) + , ( "images", encodeOptionalList Json.Encode.string secret.images ) + , ( "allow_command", encodeOptional Json.Encode.bool secret.allowCommand ) ] @@ -2332,7 +2343,7 @@ type alias DeploymentsModel = decodeDeployment : Decoder Deployment decodeDeployment = - Decode.succeed Deployment + Json.Decode.succeed Deployment |> optional "id" int -1 |> optional "repo_id" int -1 |> optional "url" string "" @@ -2347,26 +2358,26 @@ decodeDeployment = decodeDeployments : Decoder (List Deployment) decodeDeployments = - Decode.list decodeDeployment + Json.Decode.list decodeDeployment {- payload -} -encodeKeyValuePair : KeyValuePair -> ( String, Value ) +encodeKeyValuePair : KeyValuePair -> ( String, Json.Encode.Value ) encodeKeyValuePair kvp = - ( kvp.key, Encode.string kvp.value ) + ( kvp.key, Json.Encode.string kvp.value ) -encodeOptionalKeyValuePairList : Maybe (List KeyValuePair) -> Encode.Value +encodeOptionalKeyValuePairList : Maybe (List KeyValuePair) -> Json.Encode.Value encodeOptionalKeyValuePairList value = case value of Just value_ -> - Encode.object (List.map encodeKeyValuePair value_) + Json.Encode.object (List.map encodeKeyValuePair value_) Nothing -> - Encode.null + Json.Encode.null decodeKeyValuePair : ( String, String ) -> KeyValuePair @@ -2385,7 +2396,7 @@ decodeKeyValuePairs o = decodeDeploymentParameters : Decoder (Maybe (List KeyValuePair)) decodeDeploymentParameters = - Decode.map decodeKeyValuePairs <| Decode.keyValuePairs Decode.string + Json.Decode.map decodeKeyValuePairs <| Json.Decode.keyValuePairs Json.Decode.string type alias DeploymentPayload = @@ -2400,16 +2411,16 @@ type alias DeploymentPayload = } -encodeDeploymentPayload : DeploymentPayload -> Encode.Value +encodeDeploymentPayload : DeploymentPayload -> Json.Encode.Value encodeDeploymentPayload deployment = - Encode.object - [ ( "org", encodeOptional Encode.string deployment.org ) - , ( "repo", encodeOptional Encode.string deployment.repo ) - , ( "commit", encodeOptional Encode.string deployment.commit ) - , ( "description", encodeOptional Encode.string deployment.description ) - , ( "ref", encodeOptional Encode.string deployment.ref ) - , ( "target", encodeOptional Encode.string deployment.target ) - , ( "task", encodeOptional Encode.string deployment.task ) + Json.Encode.object + [ ( "org", encodeOptional Json.Encode.string deployment.org ) + , ( "repo", encodeOptional Json.Encode.string deployment.repo ) + , ( "commit", encodeOptional Json.Encode.string deployment.commit ) + , ( "description", encodeOptional Json.Encode.string deployment.description ) + , ( "ref", encodeOptional Json.Encode.string deployment.ref ) + , ( "target", encodeOptional Json.Encode.string deployment.target ) + , ( "task", encodeOptional Json.Encode.string deployment.task ) , ( "payload", encodeOptionalKeyValuePairList deployment.payload ) ] diff --git a/src/static/index.ts b/src/static/index.ts index 9fe55f94f..8df18ab98 100644 --- a/src/static/index.ts +++ b/src/static/index.ts @@ -139,6 +139,10 @@ var opts = { }; app.ports.renderBuildGraph.subscribe(function (graphData) { + if (!graphData) { + return; + } + const graphviz = Graphviz.load().then(res => { var content = res.layout(graphData.dot, 'svg', 'dot');