Skip to content

Commit

Permalink
Enhancement/incremental community fields (#643)
Browse files Browse the repository at this point in the history
* Update elm-community/list-extra

* Add structure to query multiple community fields

* Create initial structure and validate it on Community page

* Make uploads a requestable field

* Fix objectives and uploads signatures

* Introduce FieldError

* Refactor Objectives page to reload only objectives field and use new structure

* Reload objectives when entering community page, better visuals for objectives loading and error

* Only reload objectives on ActionEditor

* Requesst objectives field

* Fix Join page for logged in users

* Set fields as loading while fetching them

* Only reload objectives on ObjectiveEditor

* Remove unnecessary comment

* Remove unnecessary comment

* Use UpdateResult in CommunityPage.init

* Add docs to Field, FieldValue and FieldError
  • Loading branch information
henriquecbuss committed Sep 29, 2021
1 parent 370cb13 commit 7bc58e0
Show file tree
Hide file tree
Showing 15 changed files with 591 additions and 152 deletions.
2 changes: 1 addition & 1 deletion elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"elm/svg": "1.0.1",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm-community/list-extra": "8.2.4",
"elm-community/list-extra": "8.5.1",
"elm-community/maybe-extra": "5.2.0",
"elm-community/string-extra": "4.0.1",
"jfmengels/elm-review": "2.3.2",
Expand Down
1 change: 1 addition & 0 deletions public/translations/amh-ETH.json
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@
"action_count": "{{actions}} ተግባራት",
"complete": "ተጠናቅቋል",
"edit": "አርትእ ዓላማዎችን",
"error_loading": "ግቦቹን ሲጭኑ አንድ የተሳሳተ ነገር ተከሰተ",
"editor": {
"not_found": "አልተገኘም",
"error": "ዉይ የሆነ ችግር ተከስቷል!",
Expand Down
1 change: 1 addition & 0 deletions public/translations/cat-CAT.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@
"action_count": "{{actions}} accions",
"complete": "Completat",
"edit": "Edita l'objectiu",
"error_loading": "S'ha produït un error en carregar els objectius",
"editor": {
"not_found": "No trobat",
"error": "Ui! Quelcom ha fallat",
Expand Down
1 change: 1 addition & 0 deletions public/translations/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@
"action_count": "{{actions}} actions",
"complete": "Complete",
"edit": "Edit objective",
"error_loading": "Something went wrong when loading objectives",
"editor": {
"not_found": "Not Found",
"error": "Ops something went wrong!",
Expand Down
1 change: 1 addition & 0 deletions public/translations/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@
"action_count": "{{actions}} acciones",
"complete": "Terminado",
"edit": "Editar objetivo",
"error_loading": "Ocurrió algo mal al cargar los objetivos",
"editor": {
"not_found": "No encontrado",
"error": "¡Huy! Algo salió mal",
Expand Down
1 change: 1 addition & 0 deletions public/translations/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@
"action_count": "{{actions}} ações",
"complete": "Completo",
"edit": "Editar objetivo",
"error_loading": "Algo de errado aconteceu ao carregar os objetivos",
"editor": {
"not_found": "Não encontrado",
"error": "Ops algo deu errado!",
Expand Down
197 changes: 193 additions & 4 deletions src/elm/Community.elm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ module Community exposing
, CommunityPreview
, CreateCommunityData
, CreateCommunityDataInput
, Field(..)
, FieldError(..)
, FieldValue(..)
, Invite
, Metadata
, Model
Expand All @@ -19,15 +22,24 @@ module Community exposing
, encodeCreateObjectiveAction
, encodeUpdateData
, encodeUpdateObjectiveAction
, getField
, inviteQuery
, isFieldLoading
, isNonExistingCommunityError
, logoBackground
, maybeFieldValue
, mergeFields
, newCommunitySubscription
, queryForField
, queryForFields
, setFieldAsLoading
, setFieldValue
, subdomainQuery
, symbolQuery
)

import Action exposing (Action)
import Api.Graphql
import Cambiatus.Mutation as Mutation
import Cambiatus.Object
import Cambiatus.Object.Community as Community
Expand All @@ -52,7 +64,9 @@ import Html.Attributes exposing (class, classList, src)
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (required)
import Json.Encode as Encode exposing (Value)
import List.Extra
import Profile
import RemoteData exposing (RemoteData)
import Session.Shared exposing (Shared)
import Time exposing (Posix)
import Utils
Expand Down Expand Up @@ -95,17 +109,143 @@ type alias Model =
, productCount : Int
, orderCount : Int
, members : List Profile.Minimal
, objectives : List Objective
, objectives : RemoteData (Graphql.Http.Error (List Objective)) (List Objective)
, hasObjectives : Bool
, hasShop : Bool
, hasKyc : Bool
, hasAutoInvite : Bool
, validators : List Eos.Name
, uploads : List String
, uploads : RemoteData (Graphql.Http.Error (List String)) (List String)
, website : Maybe String
}


{-| In order to be able to query for fields separately from the community (such
as objectives and uploads), we need to add a `Field` constructor to represent
that field. The constructor's name should be the name of the field followed by
`Field` (e.g. `ObjectivesField`).
In order to have a nice API, we also need types to wrap the success and error
cases. That's why we have `FieldValue` and `FieldError`.
If you want to add a new field that isn't loaded by default (usually Lists are
good candidates):
1. Add the field to the `Model`. It's type should be in the format
`RemoteData (Graphql.Http.Error field) field`
2. Add a new constructor to this `Field` type following the practices described above
3. Add a new constructor to `FieldValue` (see `FieldValue`'s documentation for
more info)
4. Fill in the functions that use `Field` and `FieldValue` (the compiler will
let you know which ones)
Pages can use this type to request separate fields from `LoggedIn`, using
`LoggedIn.External` messages, specifying which field they want. There are two
variants they can use to do so:
1. `RequestedCommunityField`: Checks if the field is loaded. If so, send a
`BroadcastMsg` with the field. If not, queries the backend for that field,
and once the result comes in, sends a `BroadcastMsg`
2. `RequestedReloadCommunityField`: Always queries for the field, and sends the
result as a `BroadcastMsg`
-}
type Field
= ObjectivesField
| UploadsField


{-| `FieldValue` is useful to wrap results of queries for fields that aren't
loaded in by default with the community (such as objectives and uploads). The
constructor's name should be the name of the field followed by `Value`, and
should hold the actual value of that field (e.g. `ObjectivesValue (List Objetive)`).
-}
type FieldValue
= ObjectivesValue (List Objective)
| UploadsValue (List String)


{-| When we want to extract a field that is not loaded by default with the
community, and there is an error, we need to know if it was an error when
fetching the community or when fetching the actual field.
-}
type FieldError a
= CommunityError (Graphql.Http.Error (Maybe Model))
| FieldError a


getField :
RemoteData (Graphql.Http.Error (Maybe Model)) Model
-> (Model -> RemoteData err field)
-> RemoteData (FieldError err) ( Model, field )
getField remoteDataModel accessor =
remoteDataModel
|> RemoteData.mapError CommunityError
|> RemoteData.andThen
(\model ->
accessor model
|> RemoteData.map (\field -> ( model, field ))
|> RemoteData.mapError FieldError
)


setFieldValue : FieldValue -> Model -> Model
setFieldValue fieldValue model =
case fieldValue of
ObjectivesValue objectives ->
{ model | objectives = RemoteData.Success objectives }

UploadsValue uploads ->
{ model | uploads = RemoteData.Success uploads }


setFieldAsLoading : Field -> Model -> Model
setFieldAsLoading field model =
case field of
ObjectivesField ->
{ model | objectives = RemoteData.Loading }

UploadsField ->
{ model | uploads = RemoteData.Loading }


isFieldLoading : Field -> Model -> Bool
isFieldLoading field model =
case field of
ObjectivesField ->
RemoteData.isLoading model.objectives

UploadsField ->
RemoteData.isLoading model.uploads


maybeFieldValue : Field -> Model -> Maybe FieldValue
maybeFieldValue field model =
case field of
ObjectivesField ->
model.objectives
|> RemoteData.toMaybe
|> Maybe.map ObjectivesValue

UploadsField ->
model.uploads
|> RemoteData.toMaybe
|> Maybe.map UploadsValue


mergeFields : RemoteData x Model -> Model -> Model
mergeFields loadedCommunity newCommunity =
case loadedCommunity of
RemoteData.Success oldCommunity ->
{ newCommunity
| objectives = oldCommunity.objectives
, uploads = oldCommunity.uploads
}

_ ->
newCommunity



-- GraphQL

Expand Down Expand Up @@ -141,13 +281,13 @@ communitySelectionSet =
|> with Community.productCount
|> with Community.orderCount
|> with (Community.members Profile.minimalSelectionSet)
|> with (Community.objectives objectiveSelectionSet)
|> SelectionSet.hardcoded RemoteData.NotAsked
|> with Community.hasObjectives
|> with Community.hasShop
|> with Community.hasKyc
|> with Community.autoInvite
|> with (Community.validators (Eos.nameSelectionSet Profile.account))
|> with (Community.uploads Upload.url)
|> SelectionSet.hardcoded RemoteData.NotAsked
|> with Community.website


Expand Down Expand Up @@ -175,6 +315,55 @@ newCommunitySubscription symbol =
Subscription.newcommunity args selectionSet


selectionSetForField : Field -> SelectionSet FieldValue Cambiatus.Object.Community
selectionSetForField field =
case field of
ObjectivesField ->
Community.objectives objectiveSelectionSet
|> SelectionSet.map ObjectivesValue

UploadsField ->
Community.uploads Upload.url
|> SelectionSet.map UploadsValue


queryForField :
Eos.Symbol
-> Shared
-> String
-> Field
-> (RemoteData (Graphql.Http.Error (Maybe FieldValue)) (Maybe FieldValue) -> msg)
-> Cmd msg
queryForField symbol shared authToken field toMsg =
Api.Graphql.query shared
(Just authToken)
(field
|> selectionSetForField
|> Query.community (\optionals -> { optionals | symbol = Present <| Eos.symbolToString symbol })
)
toMsg


queryForFields :
Eos.Symbol
-> Shared
-> String
-> List Field
-> (RemoteData (Graphql.Http.Error (List FieldValue)) (List FieldValue) -> msg)
-> Cmd msg
queryForFields symbol shared authToken fields toMsg =
Api.Graphql.query shared
(Just authToken)
(fields
|> List.Extra.unique
|> List.map selectionSetForField
|> SelectionSet.list
|> Query.community (\optionals -> { optionals | symbol = Present <| Eos.symbolToString symbol })
|> SelectionSet.withDefault []
)
toMsg


symbolQuery : Eos.Symbol -> SelectionSet (Maybe Model) RootQuery
symbolQuery symbol =
Query.community (\optionals -> { optionals | symbol = Present <| Eos.symbolToString symbol }) communitySelectionSet
Expand Down
9 changes: 4 additions & 5 deletions src/elm/Main.elm
Original file line number Diff line number Diff line change
Expand Up @@ -823,8 +823,7 @@ updateLoggedInUResult toStatus toMsg model uResult =
)
updateResult.afterAuthMsg
}
, Cmd.map toMsg updateResult.externalCmd
:: Cmd.map (Page.GotLoggedInMsg >> GotPageMsg) updateResult.cmd
, Cmd.map (Page.GotLoggedInMsg >> GotPageMsg) updateResult.cmd
:: broadcastCmd
:: cmds_
)
Expand Down Expand Up @@ -1225,7 +1224,7 @@ changeRouteTo maybeRoute model =

Just Route.Community ->
(\l -> CommunityPage.init l)
>> updateStatusWith Community GotCommunityMsg model
>> updateLoggedInUResult Community GotCommunityMsg model
|> withLoggedIn Route.Community

Just Route.CommunitySettings ->
Expand Down Expand Up @@ -1275,12 +1274,12 @@ changeRouteTo maybeRoute model =

Just (Route.NewAction objectiveId) ->
(\l -> ActionEditor.init l objectiveId Nothing)
>> updateStatusWith ActionEditor GotActionEditorMsg model
>> updateLoggedInUResult ActionEditor GotActionEditorMsg model
|> withLoggedIn (Route.NewAction objectiveId)

Just (Route.EditAction objectiveId actionId) ->
(\l -> ActionEditor.init l objectiveId (Just actionId))
>> updateStatusWith ActionEditor GotActionEditorMsg model
>> updateLoggedInUResult ActionEditor GotActionEditorMsg model
|> withLoggedIn (Route.EditAction objectiveId actionId)

Just (Route.Claim objectiveId actionId claimId) ->
Expand Down
Loading

0 comments on commit 7bc58e0

Please sign in to comment.