Fix searchHistory if search term is empty

module Daedalus.ClientApi where

import Prelude
import Control.Monad.Aff (liftEff')
import Daedalus.BackendApi as B
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Unsafe (unsafeInterleaveEff)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Exception (EXCEPTION)
import Control.Monad.Eff.Ref (newRef, REF)
import Control.Promise (Promise, fromAff)
import Daedalus.Types (mkCAddress, mkCoin, mkCWalletMeta, mkCTxId, mkCTxMeta, mkCCurrency, mkCProfile, mkCWalletInit)
import Daedalus.WS (WSConnection(WSNotConnected), mkWSState, ErrorCb, NotifyCb, openConn)
import Data.Argonaut (Json)
import Data.Argonaut.Generic.Aeson (encodeJson)
import Data.Function.Uncurried (Fn2, mkFn2, Fn4, mkFn4, Fn3, runFn3, mkFn3, Fn6, mkFn6, Fn7, mkFn7)
import Data.Function.Eff (EffFn1, runEffFn1)
import Data.String (null)
import Network.HTTP.Affjax (AJAX)
import WebSocket (WEBSOCKET)
import Control.Monad.Error.Class (throwError)
import Data.Either (either)
import Daedalus.Crypto as Crypto

getProfile :: forall eff. Eff(ajax :: AJAX | eff) (Promise Json)
getProfile = fromAff $ map encodeJson B.getProfile

updateProfile :: forall eff. Fn7 String String String String Number String String (Eff(ajax :: AJAX | eff) (Promise Json))
updateProfile = mkFn7 \name email phone pass date locale picture -> fromAff <<< map encodeJson <<< B.updateProfile $ mkCProfile name email phone pass date locale picture

getWallets :: forall eff. Eff(ajax :: AJAX | eff) (Promise Json)
getWallets = fromAff $ map encodeJson B.getWallets

getWallet :: forall eff. String -> (Eff(ajax :: AJAX | eff) (Promise Json))
getWallet = fromAff <<< map encodeJson <<< B.getWallet <<< mkCAddress

getHistory :: forall eff. Fn3 String Int Int (Eff (ajax :: AJAX | eff) (Promise Json))
getHistory = mkFn3 \addr skip limit -> fromAff <<< map encodeJson $
    B.getHistory
        (mkCAddress addr)
        skip
        limit

searchHistory :: forall eff. Fn4 String String Int Int (Eff(ajax :: AJAX | eff) (Promise Json))
searchHistory = mkFn4 \addr search skip limit ->
    if null search
        then runFn3 getHistory addr skip limit
        else fromAff <<< map encodeJson $ B.searchHistory (mkCAddress addr) search skip limit

send :: forall eff. Fn3 String String Int (Eff(ajax :: AJAX | eff) (Promise Json))
send = mkFn3 \addrFrom addrTo amount -> fromAff <<< map encodeJson $
    B.send
        (mkCAddress addrFrom)
        (mkCAddress addrTo)
        (mkCoin amount)

sendExtended :: forall eff. Fn6 String String Int String String String (Eff(ajax :: AJAX | eff) (Promise Json))
sendExtended = mkFn6 \addrFrom addrTo amount curr title desc -> fromAff <<< map encodeJson $
    B.sendExtended
        (mkCAddress addrFrom)
        (mkCAddress addrTo)
        (mkCoin amount)
        (mkCCurrency curr)
        title
        desc

generateMnemonic :: forall eff. Eff (crypto :: Crypto.CRYPTO | eff) String
generateMnemonic = Crypto.generateMnemonic

newWallet :: forall eff . Fn4 String String String String
  (Eff(ajax :: AJAX, crypto :: Crypto.CRYPTO | eff) (Promise Json))
newWallet = mkFn4 \wType wCurrency wName mnemonic -> fromAff <<< map encodeJson <<<
    either throwError B.newWallet $ mkCWalletInit wType wCurrency wName mnemonic

-- NOTE: https://issues.serokell.io/issue/DAE-33#comment=96-1798
-- Daedalus.ClientApi.newWallet(
--     'CWTPersonal'
--   , 'ADA'
--   , 'wallet name'
--   , function(mnemonics) {
--     // if this function finishes we will send request for wallet
--     // creation to the backend. That means user validated and
--     // stored mnemonics.
--     // if an exception is thrown new wallet request will be aborted
--     // and promise should return thrown error
--     if(userSavedMnemonics) {
--       // do nothing
--     } else {
--       throw new Error("Wallet canceled")
--     }
--   }
--   )()
--   .then(function(value) {
--     console.log('SUCCESS', value);
--   }, function(reason) {
--     console.log('ERROR', reason);
--   })
newWalletDeprecated :: forall eff . Fn4 String String String (EffFn1 (err :: EXCEPTION | eff) String Unit)
  (Eff(ajax :: AJAX, crypto :: Crypto.CRYPTO | eff) (Promise Json))
newWalletDeprecated = mkFn4 \wType wCurrency wName wConfirmMnemonic -> fromAff $ map encodeJson $ do

    mnemonic <- liftEff Crypto.generateMnemonic
    -- FIXME: @jens how did we satisfy this with notify? I am having trouble again
    isConfirmed <- liftEff' $ unsafeInterleaveEff $ runEffFn1 wConfirmMnemonic mnemonic
    either throwError B.newWallet $ do
        isConfirmed
        mkCWalletInit wType wCurrency wName mnemonic

updateWallet :: forall eff. Fn4 String String String String
  (Eff (ajax :: AJAX | eff) (Promise Json))
updateWallet = mkFn4 \addr wType wCurrency wName -> fromAff <<< map encodeJson <<<
    B.updateWallet
        (mkCAddress addr)
        $ mkCWalletMeta wType wCurrency wName

updateTransaction :: forall eff. Fn6 String String String String String Number (Eff (ajax :: AJAX | eff) (Promise Unit))
updateTransaction = mkFn6 \addr ctxId ctmCurrency ctmTitle ctmDescription ctmDate -> fromAff <<<
    B.updateTransaction
        (mkCAddress addr)
        (mkCTxId ctxId)
        $ mkCTxMeta ctmCurrency ctmTitle ctmDescription ctmDate

deleteWallet :: forall eff. String -> (Eff(ajax :: AJAX | eff) (Promise Unit))
deleteWallet = fromAff <<< B.deleteWallet <<< mkCAddress

isValidAddress :: forall eff. Fn2 String String (Eff(ajax :: AJAX | eff) (Promise Boolean))
isValidAddress = mkFn2 \currency -> fromAff <<< B.isValidAddress (mkCCurrency currency)

notify :: forall eff. Fn2 (NotifyCb eff) (ErrorCb eff) (Eff (ref :: REF, ws :: WEBSOCKET, err :: EXCEPTION | eff) Unit)
notify = mkFn2 \messageCb errorCb -> do
    -- TODO (akegalj) grab global (mutable) state of  here
    -- instead of creating newRef
    conn <- newRef WSNotConnected
    openConn $ mkWSState conn messageCb errorCb

blockchainSlotDuration :: forall eff. Eff (ajax :: AJAX | eff) (Promise Int)
blockchainSlotDuration = fromAff B.blockchainSlotDuration

module Daedalus.ClientApi where

import Prelude
import Control.Monad.Aff (liftEff')
import Daedalus.BackendApi as B
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Unsafe (unsafeInterleaveEff)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Exception (EXCEPTION)
import Control.Monad.Eff.Ref (newRef, REF)
import Control.Promise (Promise, fromAff)
import Daedalus.Types (mkCAddress, mkCoin, mkCWalletMeta, mkCTxId, mkCTxMeta, mkCCurrency, mkCProfile, mkCWalletInit)
import Daedalus.WS (WSConnection(WSNotConnected), mkWSState, ErrorCb, NotifyCb, openConn)
import Data.Argonaut (Json)
import Data.Argonaut.Generic.Aeson (encodeJson)
import Data.Function.Uncurried (Fn2, mkFn2, Fn4, mkFn4, Fn3, mkFn3, Fn6, mkFn6, Fn7, mkFn7)
import Data.Function.Eff (EffFn1, runEffFn1)
import Network.HTTP.Affjax (AJAX)
import WebSocket (WEBSOCKET)
import Control.Monad.Error.Class (throwError)
import Data.Either (either)
import Daedalus.Crypto as Crypto

getProfile :: forall eff. Eff(ajax :: AJAX | eff) (Promise Json)
getProfile = fromAff $ map encodeJson B.getProfile

updateProfile :: forall eff. Fn7 String String String String Number String String (Eff(ajax :: AJAX | eff) (Promise Json))
updateProfile = mkFn7 \name email phone pass date locale picture -> fromAff <<< map encodeJson <<< B.updateProfile $ mkCProfile name email phone pass date locale picture

getWallets :: forall eff. Eff(ajax :: AJAX | eff) (Promise Json)
getWallets = fromAff $ map encodeJson B.getWallets

getWallet :: forall eff. String -> (Eff(ajax :: AJAX | eff) (Promise Json))
getWallet = fromAff <<< map encodeJson <<< B.getWallet <<< mkCAddress

getHistory :: forall eff. Fn3 String Int Int (Eff (ajax :: AJAX | eff) (Promise Json))
getHistory = mkFn3 \addr skip limit -> fromAff <<< map encodeJson $
    B.getHistory
        (mkCAddress addr)
        skip
        limit

searchHistory :: forall eff. Fn4 String String Int Int (Eff(ajax :: AJAX | eff) (Promise Json))
searchHistory = mkFn4 \addr search skip limit -> fromAff <<< map encodeJson $
    B.searchHistory
        (mkCAddress addr)
        search
        skip
        limit

send :: forall eff. Fn3 String String Int (Eff(ajax :: AJAX | eff) (Promise Json))
send = mkFn3 \addrFrom addrTo amount -> fromAff <<< map encodeJson $
    B.send
        (mkCAddress addrFrom)
        (mkCAddress addrTo)
        (mkCoin amount)

sendExtended :: forall eff. Fn6 String String Int String String String (Eff(ajax :: AJAX | eff) (Promise Json))
sendExtended = mkFn6 \addrFrom addrTo amount curr title desc -> fromAff <<< map encodeJson $
    B.sendExtended
        (mkCAddress addrFrom)
        (mkCAddress addrTo)
        (mkCoin amount)
        (mkCCurrency curr)
        title
        desc

generateMnemonic :: forall eff. Eff (crypto :: Crypto.CRYPTO | eff) String
generateMnemonic = Crypto.generateMnemonic

newWallet :: forall eff . Fn4 String String String String
  (Eff(ajax :: AJAX, crypto :: Crypto.CRYPTO | eff) (Promise Json))
newWallet = mkFn4 \wType wCurrency wName mnemonic -> fromAff <<< map encodeJson <<<
    either throwError B.newWallet $ mkCWalletInit wType wCurrency wName mnemonic

-- NOTE: https://issues.serokell.io/issue/DAE-33#comment=96-1798
-- Daedalus.ClientApi.newWallet(
--     'CWTPersonal'
--   , 'ADA'
--   , 'wallet name'
--   , function(mnemonics) {
--     // if this function finishes we will send request for wallet
--     // creation to the backend. That means user validated and
--     // stored mnemonics.
--     // if an exception is thrown new wallet request will be aborted
--     // and promise should return thrown error
--     if(userSavedMnemonics) {
--       // do nothing
--     } else {
--       throw new Error("Wallet canceled")
--     }
--   }
--   )()
--   .then(function(value) {
--     console.log('SUCCESS', value);
--   }, function(reason) {
--     console.log('ERROR', reason);
--   })
newWalletDeprecated :: forall eff . Fn4 String String String (EffFn1 (err :: EXCEPTION | eff) String Unit)
  (Eff(ajax :: AJAX, crypto :: Crypto.CRYPTO | eff) (Promise Json))
newWalletDeprecated = mkFn4 \wType wCurrency wName wConfirmMnemonic -> fromAff $ map encodeJson $ do

    mnemonic <- liftEff Crypto.generateMnemonic
    -- FIXME: @jens how did we satisfy this with notify? I am having trouble again
    isConfirmed <- liftEff' $ unsafeInterleaveEff $ runEffFn1 wConfirmMnemonic mnemonic
    either throwError B.newWallet $ do
        isConfirmed
        mkCWalletInit wType wCurrency wName mnemonic

updateWallet :: forall eff. Fn4 String String String String
  (Eff (ajax :: AJAX | eff) (Promise Json))
updateWallet = mkFn4 \addr wType wCurrency wName -> fromAff <<< map encodeJson <<<
    B.updateWallet
        (mkCAddress addr)
        $ mkCWalletMeta wType wCurrency wName

updateTransaction :: forall eff. Fn6 String String String String String Number (Eff (ajax :: AJAX | eff) (Promise Unit))
updateTransaction = mkFn6 \addr ctxId ctmCurrency ctmTitle ctmDescription ctmDate -> fromAff <<<
    B.updateTransaction
        (mkCAddress addr)
        (mkCTxId ctxId)
        $ mkCTxMeta ctmCurrency ctmTitle ctmDescription ctmDate

deleteWallet :: forall eff. String -> (Eff(ajax :: AJAX | eff) (Promise Unit))
deleteWallet = fromAff <<< B.deleteWallet <<< mkCAddress

isValidAddress :: forall eff. Fn2 String String (Eff(ajax :: AJAX | eff) (Promise Boolean))
isValidAddress = mkFn2 \currency -> fromAff <<< B.isValidAddress (mkCCurrency currency)

notify :: forall eff. Fn2 (NotifyCb eff) (ErrorCb eff) (Eff (ref :: REF, ws :: WEBSOCKET, err :: EXCEPTION | eff) Unit)
notify = mkFn2 \messageCb errorCb -> do
    -- TODO (akegalj) grab global (mutable) state of  here
    -- instead of creating newRef
    conn <- newRef WSNotConnected
    openConn $ mkWSState conn messageCb errorCb

blockchainSlotDuration :: forall eff. Eff (ajax :: AJAX | eff) (Promise Int)
blockchainSlotDuration = fromAff B.blockchainSlotDuration


Gauge view improvements

Gauge view improvements

module Gauge where

import Prelude
import Data.List
import Data.Maybe
import Data.Functor
import Control.Monad.Eff.Exception
import Pux.Html as H
import Pux.Html.Attributes as A
import Pux.Html.Events as E
import Control.Bind ((=<<))
import Data.Foldable (sum)
import Data.Eq (Eq)

import React.DOM.Props (onChange)

data ViewType = PerNode | PerBroker | PerNodeAndBroker | Aggregate deriving Eq

data Action = UpdateStats (List NodeStats) | ChangeView ViewType

type TimedVal a = { time::Time, value::a }
type State =
    { viewType:: ViewType
    , stats:: List NodeStats }

-- /todo: Add Node = String | IPAddress
type Node = String
type Broker =
    { name :: Node
    , port :: Int }
type BStats = Int
type BrokerStats =
    { broker ::Broker
    , connections :: BStats}

type NodeStats =
    { node ::  Node
    , timedSeries:: List (TimedVal (List BrokerStats))}

instance viewTypeShow :: Show ViewType where
  show PerNode = "PerNode"
  show PerBroker = "PerBroker"
  show PerNodeAndBroker = "PerNodeAndBroker"
  show Aggregate = "Aggregate"

numConnections :: List NodeStats -> Int
numConnections =
    concatMap latestStatsPerBroker
    >>> map _.connections
    >>> sum

-- numConnections :: List BrokerStats -> Int
-- numConnections =
--     map _.connections >>> sum

latestStatsPerBroker :: NodeStats -> List BrokerStats
latestStatsPerBroker =
     _.timedSeries
        >>> head
        >>> map _.value
        >>> emptyIfNothing

-- latestStatsPerBroker :: List(NodeStats) -> List BrokerStats
-- latestStatsPerBroker =
--     map latestStatsPerBroker
--     >>> group _.broker
--     >>> {broker : b, connections : }

emptyIfNothing :: forall a. Maybe (List a) -> List a
emptyIfNothing (Just x) = x
emptyIfNothing Nothing = Nil

init :: State
init = { viewType : PerBroker, stats : Nil }

update :: Action -> State -> State
update (UpdateStats us) s = s {stats = us}
update (ChangeView vt) s = s {viewType = vt}

view:: State -> H.Html Action
view {viewType,stats} =
    H.div
    []
    [ H.h1 [] [numConnectionsView stats]
    , select viewType
    , stats # viewAs viewType
    ]

numConnectionsView :: (List NodeStats) -> H.Html Action
numConnectionsView stats =
    H.text $ stats # show <<< numConnections

viewAs :: ViewType ->  (List NodeStats) -> H.Html Action
viewAs PerNode s = H.div [] []
viewAs PerBroker s = H.div [] []
viewAs PerNodeAndBroker s = H.div [] []
viewAs Aggregate s = H.div [] []

select :: ViewType -> H.Html Action
select viewType =
    H.select
     [E.onSelect changeView]
     (optionView <$> availableViews)

optionView :: ViewType -> ViewType -> H.Html Action
optionView v sel =
    let strVal = viewTypeToStr v in
    H.option [A.value strVal, A.selected (v == sel) ] [H.text strVal]

changeView :: E.SelectionEvent -> Action
changeView se =
    ChangeView (toViewType se.target.value)

availableViews :: Array ViewType
availableViews =
   [PerNode, PerBroker, PerNodeAndBroker, Aggregate]

viewTypeToStr:: ViewType -> String
viewTypeToStr = show

toViewType :: String -> ViewType
toViewType "PerNode" = PerNode
toViewType "PerBroker" = PerBroker
toViewType "PerNodeAndBroker" = PerNodeAndBroker
toViewType "Aggregate" = Aggregate
toViewType _ = Aggregate

module Gauge where

import Prelude
import Pux.Html as H
import Control.Bind ((=<<))
import Control.Category (id)
import Data.Field (div)
import Data.Foldable (sum)
import Data.List
import Data.StrMap (empty)

data Action = UpdateStats (List NodeStats)
type State = (List NodeStats)

-- /todo: Add Node = String | IPAddress
type Node = String
type Broker =
    { name :: Node
    , port :: Int }
type BStats = Int
type BrokerStats =
    { broker ::Broker
    , stats :: BStats}

type NodeStats =
    { node ::  Node
    , brokers :: List BrokerStats}

numConnections :: List NodeStats -> Int
numConnections =
    concatMap _.brokers
    >>> map _.stats
    >>> sum

init :: State
init = Nil

update :: Action -> State -> State
update (UpdateStats us) s = us

view:: State -> H.Html Action
view state =
    H.div
    []
    [H.h1 [] [H.text "test" ]]