...
 
Commits (21)
{
"$schema": "http://json.schemastore.org/prettierrc",
"semi": false,
"useTabs": false,
"singleQuote": false,
"printWidth": 120
}
......@@ -16,7 +16,7 @@ nvm install --lts
nvm use --lts
```
Install the development server and the Elm dependencies with:
Install the local development server and the Elm dependencies with:
```sh
npm install
......@@ -24,23 +24,17 @@ npm install
## Development
Run the development server with:
Run the local development server with...
```sh
npm start
```
Then open http://localhost:8000/
...then open http://localhost:8000/
### Use production infrastructure while developing
It's not easy to install a complete DBnomics platform, so you can use the production infrastructure while developing on DBnomics UI.
To change the web API URL, edit `src/config/development.js`:
```
apiBaseUrl: "https://api.db.nomics.world/" OR "http://localhost:5000"
```
To change the web API URL, edit `src/config/local.js` and change `apiBaseUrl` config property.
It's also possible to use a local web API using the remote Solr index and reading remote Git repositories. To follow this path, please read the [web API README](https://git.nomics.world/dbnomics/dbnomics-api).
......@@ -61,10 +55,10 @@ Successfully generated /tmp/app.js
## Deploy
Deploy in integration enviroment:
Deploy in pre-production enviroment:
```sh
TARGET_ENV=integration npm run build && scp -r dist/* dbnomics@hebe.nomics.world:dbnomics-ui-static
TARGET_ENV=pre npm run build && scp -r dist/* dbnomics@ioke.nomics.world:dbnomics-ui-static
```
Deploy in production enviroment:
......
......@@ -10,15 +10,18 @@
"dependencies": {
"NoRedInk/view-extra": "2.0.0 <= v < 3.0.0",
"cuducos/elm-format-number": "6.0.0 <= v < 7.0.0",
"dividat/elm-semver": "1.0.0 <= v < 2.0.0",
"elm-community/dict-extra": "2.3.0 <= v < 3.0.0",
"elm-community/html-extra": "2.2.0 <= v < 3.0.0",
"elm-community/json-extra": "2.3.0 <= v < 3.0.0",
"elm-community/list-extra": "7.1.0 <= v < 8.0.0",
"elm-community/string-extra": "1.5.0 <= v < 2.0.0",
"elm-lang/core": "5.0.0 <= v < 6.0.0",
"elm-lang/html": "2.0.0 <= v < 3.0.0",
"elm-lang/http": "1.0.0 <= v < 2.0.0",
"elm-lang/navigation": "2.1.0 <= v < 3.0.0",
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
"inkuzmin/elm-multiselect": "2.0.0 <= v < 2.0.1",
"rnons/ordered-containers": "1.0.0 <= v < 2.0.0",
"rtfeldman/elm-css": "13.1.1 <= v < 14.0.0",
"rundis/elm-bootstrap": "4.0.0 <= v <= 5.0.0",
"ryannhg/elm-date-format": "2.1.1 <= v < 3.0.0"
......
// Development server config
var prodConfig = require("./production.js")
module.exports = Object.assign(prodConfig, {
apiBaseUrl: 'https://api.dev.nomics.world/v21',
apiBaseUrl: 'https://api.dev.db.nomics.world/v22',
})
// Local development server
var prodConfig = require("./production.js")
module.exports = Object.assign(prodConfig, {
apiBaseUrl: 'http://localhost:5000',
apiBaseUrl: "http://localhost:5000"
})
// Pre-production server config
var prodConfig = require("./production.js")
module.exports = Object.assign(prodConfig, {
apiBaseUrl: 'http://api.pre.db.nomics.world/v21',
})
// Production server config
module.exports = {
apiBaseUrl: 'https://api.db.nomics.world/v21',
apiVersionRequirements: { greaterThanOrEqualTo: '0.21.6', lessThan: '0.22.0' },
apiBaseUrl: "https://api.db.nomics.world/v21",
seriesEndpointLimit: 1000,
}
module ActivePage exposing (..)
import Bootstrap.Navbar as Navbar
import Html exposing (..)
import Html.Attributes exposing (..)
import I18n exposing (formatUSInt)
import Route exposing (Route(..))
import View.Extra exposing (viewIf, viewJust)
import Views
{-| Determines which navbar link (if any) will be rendered as active.
Note that we don't enumerate every page here, because the navbar doesn't
have links for every page. Anything that's not part of the navbar falls
under Other.
-}
type ActivePage
= Home
| Cart
| LastUpdates
| Other
frame : ActivePage -> Bool -> Int -> Navbar.State -> (Navbar.State -> msg) -> Html msg -> Html msg
frame activePage isLoading nbSeriesInCart navbarState navbarMsg content =
div [ class "page-frame" ]
[ Navbar.config navbarMsg
|> Navbar.withAnimation
|> Navbar.collapseSmall
|> Navbar.dark
|> Navbar.brand [ Route.href <| Route.Home "" ]
[ img
[ src "/static/img/dbnomics-logo.png"
, attribute "srcset" "/static/img/dbnomics-logo.png 1x, /static/img/dbnomics-logo@2x.png 2x"
, alt "DBnomics, the world's economic database"
, class "d-inline-block align-top"
]
[]
]
|> Navbar.items
[ Navbar.itemLink [ Route.href Route.Providers ]
[ text "Providers" ]
]
|> Navbar.customItems
((if isLoading then
[ Navbar.customItem Views.spinner ]
else
[]
)
++ [ Navbar.customItem <|
div [ class "navbar-nav" ]
[ div [ class "nav-item" ]
[ a
[ Route.href Route.LastUpdates
, classList
[ ( "nav-link", True )
, ( "active", activePage == LastUpdates )
]
]
[ text "Last updates" ]
]
]
]
++ if nbSeriesInCart >= 1 then
[ Navbar.customItem <|
div [ class "navbar-nav" ]
[ div [ class "nav-item" ]
[ a
[ Route.href Route.Cart
, classList
[ ( "nav-link", True )
, ( "active", activePage == Cart )
]
]
[ text "Cart"
, span [ class "badge badge-light ml-2" ]
[ text <| formatUSInt nbSeriesInCart ]
]
]
]
]
else
[]
)
|> Navbar.view navbarState
, main_
[ class <|
if activePage == Home then
""
else
"mt-5"
, style
[ ( "min-height", "50vw" ) -- Makes the footer work with short pages like errors or not-found.
]
]
[ if activePage == Home then
content
else
div [ class "container" ]
[ content ]
]
, viewIf (\() -> Views.footer) <| not isLoading
]
isActive : ActivePage -> Route -> Bool
isActive page route =
case ( page, route ) of
( Home, Route.Home _ ) ->
True
( Cart, Route.Cart ) ->
True
_ ->
False
module Data.Cart exposing (..)
import Data.Types exposing (..)
import Json.Decode exposing (..)
import Set exposing (Set)
-- DECODERS
seriesId : Decoder SeriesId
seriesId =
list string
|> andThen
(\strings ->
case strings of
[ providerCode, datasetCode, seriesCode ] ->
succeed ( providerCode, datasetCode, seriesCode )
_ ->
fail "Expected 3 strings: provider code, dataset code and series code"
)
seriesTuple : Decoder ( SeriesId, SeriesName )
seriesTuple =
map2 (,)
(index 0 seriesId)
(index 1 string)
seriesTuples : Decoder (Set ( SeriesId, SeriesName ))
seriesTuples =
list seriesTuple
|> map Set.fromList
module Data.Dataset exposing (..)
import Data.Solr exposing (..)
import Date exposing (Date)
import Data.Types exposing (..)
import Dict exposing (Dict)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
import Json.Encode as Encode exposing (encode)
import Set exposing (Set)
import String
import Url
type alias ProviderCode =
String
type alias DatasetCode =
String
type alias SeriesCode =
String
type alias SeriesName =
String
type alias SeriesId =
( ProviderCode, DatasetCode, SeriesCode )
seriesIdToString : SeriesId -> String
seriesIdToString ( providerCode, datasetCode, seriesCode ) =
String.join "/" [ providerCode, datasetCode, seriesCode ]
type alias DimensionCode =
String
type alias DimensionValueCode =
String
type alias SeriesResults =
{ dimensionsFacets : Dict String DimensionFacet
, provider : Provider
, dataset : Dataset
, series : SolrResults Series
}
type alias DimensionFacet =
{ label : Maybe String
, values : List DimensionFacetValue
}
type alias SeriesDimensionsFacets =
Dict DimensionCode (List DimensionFacetValue)
type alias DimensionFacetValue =
{ code : String
, label : Maybe String
{ code : DimensionValueCode
, count : Int
}
type alias Dataset =
{ code : String
, name : Maybe String
, description : Maybe String
, dimensionsCodesOrder : Maybe (List String)
, convertedAt : Maybe Date
, nbSeries : Int
}
getDimensionsCodesOrder : SeriesResults -> List String
getDimensionsCodesOrder seriesResults =
seriesResults.dataset.dimensionsCodesOrder
|> Maybe.withDefault (Dict.keys seriesResults.dimensionsFacets |> List.sort)
type alias Provider =
{ code : String
, name : Maybe String
}
type alias Series =
{ code : SeriesCode
, name : Maybe SeriesName
}
type alias SelectedDimensions =
Dict DimensionCode (List DimensionValueCode)
type ApiOutputFormat
= CsvFormat
| JsonFormat
| XlsxFormat
apiOutputFormatToString : ApiOutputFormat -> String
apiOutputFormatToString apiOutputFormat =
case apiOutputFormat of
CsvFormat ->
"csv"
JsonFormat ->
"json"
XlsxFormat ->
"xlsx"
type Orientation
= Column
| Row
orientationToString : Orientation -> String
orientationToString orientation =
case orientation of
Column ->
"column"
Row ->
"row"
datasetApiLinkUrl : String -> ProviderCode -> DatasetCode -> SelectedDimensions -> ApiOutputFormat -> String
datasetApiLinkUrl apiBaseUrl providerCode datasetCode selectedDimensions apiOutputFormat =
Url.build
(Url.Absolute apiBaseUrl)
[ "series" ]
<|
dimensionsQueryStringParam selectedDimensions
++ [ ( "provider_code", providerCode )
, ( "dataset_code", datasetCode )
, ( "format", apiOutputFormatToString apiOutputFormat )
]
type GitDataRepository
= SourceRepository ProviderCode
| JsonRepository ProviderCode DatasetCode
......@@ -152,135 +30,25 @@ type GitDataRepository
datasetGitLabLink : GitDataRepository -> String
datasetGitLabLink repo =
"https://git.nomics.world/"
++ (case repo of
SourceRepository providerCode ->
"dbnomics-source-data/" ++ String.toLower providerCode ++ "-source-data"
JsonRepository providerCode datasetCode ->
"dbnomics-json-data/" ++ String.toLower providerCode ++ "-json-data/blob/master/" ++ datasetCode
)
seriesApiLinkUrl : String -> Set SeriesId -> ApiOutputFormat -> String
seriesApiLinkUrl apiBaseUrl seriesIds apiOutputFormat =
Url.build
(Url.Absolute apiBaseUrl)
[ "series" ]
[ ( "series_ids"
, seriesIds
|> Set.toList
|> List.map seriesIdToString
|> String.join ","
)
, ( "format", apiOutputFormatToString apiOutputFormat )
]
++ case repo of
SourceRepository providerCode ->
"dbnomics-source-data/" ++ String.toLower providerCode ++ "-source-data"
oneSeriesApiLinkUrl : String -> SeriesId -> Orientation -> ApiOutputFormat -> String
oneSeriesApiLinkUrl apiBaseUrl ( providerCode, datasetCode, seriesCode ) orientation apiOutputFormat =
Url.build
(Url.Absolute apiBaseUrl)
[ "series" ]
[ ( "provider_code", providerCode )
, ( "dataset_code", datasetCode )
, ( "series_code", seriesCode )
, ( "format", apiOutputFormatToString apiOutputFormat )
, ( "orientation", orientationToString orientation )
]
JsonRepository providerCode datasetCode ->
"dbnomics-json-data/" ++ String.toLower providerCode ++ "-json-data/blob/master/" ++ datasetCode
-- DECODERS
nonEmptyString : Decoder String
nonEmptyString =
string
|> andThen
(\s ->
if String.isEmpty s then
fail "string must not be empty"
else
succeed s
)
seriesFacetsResults : Decoder SeriesResults
seriesFacetsResults =
succeed SeriesResults
|: (field "series_dimensions_facets" <| dict dimensionFacet)
|: field "provider" provider
|: field "dataset" dataset
|: (field "series" <| solrResults (list series))
dimensionFacet : Decoder DimensionFacet
dimensionFacet =
succeed DimensionFacet
|: (maybe <| field "label" string)
|: (field "values" <| list dimensionFacetValue)
dimensionFacetValue : Decoder DimensionFacetValue
dimensionFacetValue =
succeed DimensionFacetValue
|: field "code" string
|: (maybe <| field "label" string)
|: field "count" int
dataset : Decoder Dataset
dataset =
succeed Dataset
|: field "code" string
|: (maybe <| field "name" nonEmptyString)
|: (maybe <| field "description" nonEmptyString)
|: (maybe <| field "dimensions_codes_order" (list string))
|: (maybe <| field "converted_at" date)
|: field "nb_series" int
provider : Decoder Provider
provider =
succeed Provider
|: field "code" string
|: (maybe <| field "name" string)
series : Decoder Series
series =
succeed Series
|: field "code" string
|: (maybe <| field "name" string)
seriesId : Decoder SeriesId
seriesId =
list string
|> andThen
(\strings ->
case strings of
[ providerCode, datasetCode, seriesCode ] ->
succeed ( providerCode, datasetCode, seriesCode )
_ ->
fail "Expected 3 strings (provider code, dataset code and series code)"
)
seriesTuple : Decoder ( SeriesId, SeriesName )
seriesTuple =
map2 (,)
(index 0 seriesId)
(index 1 string)
seriesTuples : Decoder (Set ( SeriesId, SeriesName ))
seriesTuples =
list seriesTuple
|> map Set.fromList
-- ENCODERS
......@@ -298,21 +66,6 @@ encodeSelectedDimensions selectedDimensions =
|> Encode.object
encodeSeriesId : SeriesId -> Encode.Value
encodeSeriesId ( providerCode, datasetCode, seriesCode ) =
[ providerCode, datasetCode, seriesCode ]
|> List.map Encode.string
|> Encode.list
encodeSeriesTuple : ( SeriesId, SeriesName ) -> Encode.Value
encodeSeriesTuple ( seriesId, seriesName ) =
Encode.list
[ encodeSeriesId seriesId
, Encode.string seriesName
]
-- ROUTE
......
......@@ -2,18 +2,10 @@ module Data.Flags exposing (..)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
import Semver exposing (Version)
type alias VersionRequirement =
{ greaterThanOrEqualTo : Version
, lessThan : Version
}
type alias Flags =
{ apiBaseUrl : String
, apiVersionRequirements : VersionRequirement
, seriesEndpointLimit : Int
}
......@@ -21,13 +13,5 @@ type alias Flags =
flags : Decoder Flags
flags =
succeed Flags
|: (field "apiBaseUrl" string)
|: (field "apiVersionRequirements" versionRequirement)
|: (field "seriesEndpointLimit" int)
versionRequirement : Decoder VersionRequirement
versionRequirement =
succeed VersionRequirement
|: (field "greaterThanOrEqualTo" Semver.decode)
|: (field "lessThan" Semver.decode)
|: field "apiBaseUrl" string
|: field "seriesEndpointLimit" int
module Data.Home exposing (..)
import Data.Solr exposing (..)
import Date exposing (Date)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
type alias ProvidersResults =
{ nbDatasets : Int
, providers : SolrResults ProviderResult
, nbSeries : Int
}
type alias ProviderResult =
{ code : String
, convertedAt : Maybe Date
, name : Maybe String
, region : Maybe String
, termsOfUse : Maybe String
, website : String
}
type alias DatasetResult =
{ code : String
, convertedAt : Maybe Date
, name : Maybe String
, providerCode : String
, providerName : Maybe String
, nbSeries : Int
, nbMatchingSeries : Int
}
type alias SeriesResult =
{ code : String
, name : Maybe String
, datasetCode : String
, datasetName : Maybe String
, providerCode : String
, providerName : Maybe String
}
providersResults : Decoder ProvidersResults
providersResults =
succeed ProvidersResults
|: at [ "datasets", "num_found" ] int
|: (field "providers" <| solrResults (list providerResult))
|: at [ "series", "num_found" ] int
providerResult : Decoder ProviderResult
providerResult =
succeed ProviderResult
|: field "code" string
|: (maybe <| field "converted_at" date)
|: (maybe <| field "name" string)
|: (maybe <| field "region" string)
|: (maybe <| field "terms_of_use" string)
|: field "website" string
datasetResult : Decoder DatasetResult
datasetResult =
succeed DatasetResult
|: (field "code" string)
|: (maybe <| field "converted_at" date)
|: (maybe <| field "name" string)
|: (field "provider_code" string)
|: (maybe <| field "provider_name" string)
|: (field "nb_series" int)
|: (field "nb_matching_series" int)
seriesResult : Decoder SeriesResult
seriesResult =
succeed SeriesResult
|: (field "code" string)
|: (maybe <| field "name" string)
|: (field "dataset_code" string)
|: (maybe <| field "dataset_name" string)
|: (field "provider_code" string)
|: (maybe <| field "provider_name" string)
module Data.LastUpdates exposing (..)
import Data.Types exposing (..)
import Date exposing (Date)
import Data.Dataset exposing (DatasetCode, ProviderCode)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
......@@ -9,7 +9,7 @@ import Json.Decode.Extra exposing (..)
type alias DatasetUpdate =
{ code : DatasetCode
, name : Maybe String
, providerCode : DatasetCode
, providerCode : ProviderCode
, providerName : Maybe String
, indexedAt : Date
, convertedAt : Date
......
module Data.PaginatedList exposing (..)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
type alias PaginatedList a =
{ docs : List a
, numFound : Int
}
append : PaginatedList a -> PaginatedList a -> PaginatedList a
append a b =
{ docs = a.docs ++ b.docs
, numFound = b.numFound
}
paginatedList : Decoder a -> Decoder (PaginatedList a)
paginatedList docs =
succeed PaginatedList
|: (field "docs" <| list docs)
|: field "num_found" int
module Data.Provider exposing (..)
import Data.Home exposing (ProviderResult, providerResult)
import Data.Types exposing (..)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
codesBlacklist : List String
codesBlacklist =
-- Some provider codes should not be displayed in the UI,
-- because they are not more meaningful to the user than the name.
[ "bank-of-england", "Eurostat", "pole-emploi", "world-bank" ]
type alias ProviderResults =
{ categoryTree : List CategoryTreeNode
, provider : ProviderResult
}
type alias CategoryTree =
List CategoryTreeNode
type CategoryTreeNode
= CategoryNode Category
| DatasetNode Dataset
= CategoryTreeCategoryNode CategoryNode
| CategoryTreeDatasetNode DatasetNode
isCategoryNode : CategoryTreeNode -> Bool
isCategoryNode node =
case node of
DatasetNode _ ->
CategoryTreeDatasetNode _ ->
False
CategoryNode _ ->
CategoryTreeCategoryNode _ ->
True
type alias Category =
type alias CategoryNode =
{ code : Maybe String
, name : Maybe String
, docHref : Maybe String
......@@ -41,36 +32,16 @@ type alias Category =
}
type alias Dataset =
{ code : String
type alias DatasetNode =
{ code : DatasetCode
, name : Maybe String
}
title : { a | code : String, name : Maybe String } -> String
title { code, name } =
case name of
Nothing ->
code
Just name ->
if List.member code codesBlacklist then
name
else
code ++ " – " ++ name
-- DECODERS
providerResults : Decoder ProviderResults
providerResults =
succeed ProviderResults
|: (field "category_tree" <| list categoryTreeNode)
|: (field "provider" providerResult)
categoryTreeNode : Decoder CategoryTreeNode
categoryTreeNode =
-- Just test the presence of the property by using the "value" decoder, don't decode it.
......@@ -79,24 +50,24 @@ categoryTreeNode =
(\childrenValue ->
case childrenValue of
Just _ ->
map CategoryNode category
map CategoryTreeCategoryNode categoryNode
Nothing ->
map DatasetNode dataset
map CategoryTreeDatasetNode datasetNode
)
category : Decoder Category
category =
succeed Category
categoryNode : Decoder CategoryNode
categoryNode =
succeed CategoryNode
|: (maybe <| field "code" string)
|: (maybe <| field "name" string)
|: (maybe <| field "doc_href" string)
|: (field "children" <| list categoryTreeNode)
dataset : Decoder Dataset
dataset =
succeed Dataset
|: (field "code" string)
datasetNode : Decoder DatasetNode
datasetNode =
succeed DatasetNode
|: field "code" string
|: (maybe <| field "name" string)
module Data.Search exposing (..)
import Data.Types exposing (..)
import Date exposing (Date)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
type alias SearchResult =
{ code : DatasetCode
, convertedAt : Maybe Date
, name : Maybe String
, providerCode : ProviderCode
, providerName : Maybe String
, nbSeries : Int
, nbMatchingSeries : Int
}
searchResult : Decoder SearchResult
searchResult =
succeed SearchResult
|: field "code" string
|: (maybe <| field "converted_at" date)
|: (maybe <| field "name" string)
|: field "provider_code" string
|: (maybe <| field "provider_name" string)
|: field "nb_series" int
|: field "nb_matching_series" int
module Data.Series exposing (..)
import Data.Dataset exposing (..)
import Date exposing (Date)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
type alias Period =
String
type alias Provider =
{ code : ProviderCode
, name : Maybe String
}
type alias Dataset =
{ code : DatasetCode
, name : Maybe String
, indexedAt : Date
, convertedAt : Maybe Date
}
type alias Observation =
{ period : Period
, value : ObservationValue
, attributes : List String
}
type ObservationValue
= FloatValue Float
| NotAvailable
| StringValue String
observationValueToString : ObservationValue -> String
observationValueToString v =
case v of
FloatValue f ->
toString f
NotAvailable ->
"NA"
StringValue s ->
s
type alias Series =
{ code : SeriesCode
, name : String
, observations : List Observation
, observationsAttributesCodes : List String
, dimensionsWithLabels : List Dimension
}
type alias Dimension =
{ code : String
, label : String
, valueCode : String
, valueLabel : String
}
oneSeriesResult : Decoder ( Provider, Dataset, Series )
oneSeriesResult =
map3 (,,)
(field "provider" provider)
(field "dataset" dataset)
(field "series" series)
series : Decoder Series
series =
succeed Series
|: field "code" string
|: field "name" string
|: field "rows" observations
|: field "rows" (header |> map (List.drop 2))
|: (field "dimensions_with_labels" (list dimension) |> withDefault [])
dimension : Decoder Dimension
dimension =
succeed Dimension
|: field "code" string
|: field "label" string
|: field "value_code" string
|: field "value_label" string
header : Decoder (List String)
header =
field "0" (list string)
observations : Decoder (List Observation)
observations =
header
|> andThen (\header -> drop 1 { period = "", value = NotAvailable, attributes = [] } (observation header))
drop : Int -> a -> Decoder a -> Decoder (List a)
drop n emptyItem itemDecoder =
indexedList
(\index ->
if index < n then
succeed emptyItem
else
itemDecoder
)
|> map (List.drop n)
observation : List String -> Decoder Observation
observation header =
succeed Observation
|: field "0" string
|: field "1" observationValue
|: drop 2 "" string
observationValue : Decoder ObservationValue
observationValue =
oneOf
[ map FloatValue float
, map
(\s ->
if s == "NA" then
NotAvailable
else
StringValue s
)
string
]
dataset : Decoder Dataset
dataset =
succeed Dataset
|: field "code" string
|: (maybe <| field "name" string)
|: field "indexed_at" date
|: (maybe <| field "converted_at" date)
provider : Decoder Provider
provider =
succeed Provider
|: field "code" string
|: (maybe <| field "name" string)
module Data.Solr exposing (..)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
type alias SolrResults a =
{ numFound : Int
, docs : List a
}
solrResults : Decoder (List a) -> Decoder (SolrResults a)
solrResults docs =
succeed SolrResults
|: (field "num_found" int)
|: (field "docs" docs)
module Data.Types exposing (..)
import Date exposing (Date)
import Dict exposing (Dict)
import I18n exposing (formatUSInt)
import Json.Decode exposing (..)
import Json.Decode.Extra exposing (..)
import Json.Encode as Encode
import OrderedDict exposing (OrderedDict)
-- Types shared by many Data.* modules.
type alias ProviderCode =
String
type alias Provider =
{ code : ProviderCode
, convertedAt : Maybe Date
, name : Maybe String
, region : Maybe String
, termsOfUse : Maybe String
, website : String
}
formatTitle : Provider -> String
formatTitle { code, name } =
case name of
Nothing ->
code
Just name ->
if List.member code hiddenProviderCodes then
name
else
code ++ " – " ++ name
hiddenProviderCodes : List ProviderCode
hiddenProviderCodes =
-- Some provider codes should not be displayed in the UI,
-- because they are not more meaningful to the user than the name.
[ "bank-of-england", "Eurostat", "pole-emploi", "world-bank" ]
starProviders : List ProviderCode
starProviders =
[ "BIS", "ECB", "Eurostat", "IMF", "OECD", "ILO", "WTO", "WB" ]
isStarProvider : Provider -> Bool
isStarProvider provider =
List.member provider.code starProviders
type alias DatasetCode =
String
type alias SeriesCode =
String
type alias SeriesName =
String
type alias SeriesId =
( ProviderCode, DatasetCode, SeriesCode )
formatSeriesId : SeriesId -> String
formatSeriesId ( providerCode, datasetCode, seriesCode ) =
String.join "/" [ providerCode, datasetCode, seriesCode ]
type alias AttributeCode =
String
type alias DimensionCode =
String
type alias DimensionValueCode =
String
type alias DimensionsLabels =
Dict DimensionCode String
type alias DimensionsValuesLabels =
Dict DimensionCode (OrderedDict DimensionValueCode String)
formatDimensionValueLabel : DimensionValueCode -> Maybe String -> Int -> String
formatDimensionValueLabel dimensionValueCode dimensionValueLabel count =
(case dimensionValueLabel of
Nothing ->
""
Just dimensionValueLabel ->
dimensionValueLabel ++ " "
)
++ "["
++ dimensionValueCode
++ "] ("
++ formatUSInt count
++ ")"
getDimensionValueLabel : DimensionsValuesLabels -> DimensionCode -> DimensionValueCode -> Maybe String
getDimensionValueLabel dimensionsValuesLabels dimensionCode dimensionValueCode =
dimensionsValuesLabels
|> Dict.get dimensionCode
|> Maybe.andThen (OrderedDict.get dimensionValueCode)
type alias Dataset =
{ code : DatasetCode
, name : Maybe String
, description : Maybe String
, dimensionsCodesOrder : Maybe (List String)
, dimensionsLabels : DimensionsLabels
, dimensionsValuesLabels : DimensionsValuesLabels
, convertedAt : Maybe Date
, indexedAt : Maybe Date
, nbSeries : Int
}
type ObservationValue
= FloatValue Float
| NotAvailable
| StringValue String
formatObservationValue : ObservationValue -> String
formatObservationValue v =
case v of
FloatValue f ->
toString f
NotAvailable ->
"NA"
StringValue s ->
s
type alias Period =
String
type alias ObservationsAttribute =
( AttributeCode, List String )
type alias Series =
{ code : SeriesCode
, name : SeriesName
, providerCode : ProviderCode
, datasetCode : DatasetCode
, detectedFrequency : Maybe String
, dimensions : Dict DimensionCode DimensionValueCode
, observationsAttributes : List ObservationsAttribute
, period : List Period
, periodStartDay : List Period
, value : List ObservationValue
}
idFromSeries : Series -> SeriesId
idFromSeries { providerCode, datasetCode, code } =
( providerCode, datasetCode, code )
type ApiOutputFormat
= CsvFormat
| JsonFormat
| XlsxFormat
formatApiOutputFormat : ApiOutputFormat -> String
formatApiOutputFormat apiOutputFormat =
case apiOutputFormat of
CsvFormat ->
"csv"
JsonFormat ->
"json"
XlsxFormat ->
"xlsx"
-- DECODERS
dataset : Decoder Dataset
dataset =
succeed Dataset
|: field "code" string
|: (maybe <| field "name" string)
|: (maybe <| field "description" string)
|: (maybe <| field "dimensions_codes_order" (list string))
|: (field "dimensions_labels" (dict string)
|> withDefault Dict.empty
)
|: (optionalField "dimensions_values_labels" dimensionsValuesLabels |> map (Maybe.withDefault Dict.empty))
|: (maybe <| field "converted_at" date)
|: (maybe <| field "indexed_at" date)
|: field "nb_series" int
dimensionsValuesLabels : Decoder DimensionsValuesLabels
dimensionsValuesLabels =
oneOf
[ dict <| (list pairOfString |> map OrderedDict.fromList)
, dict (dict string |> map (Dict.toList >> OrderedDict.fromList))
]
pairOfString : Decoder ( String, String )
pairOfString =
map2 (,)
(index 0 string)
(index 1 string)
observationValue : Decoder ObservationValue
observationValue =
oneOf
[ map FloatValue float
, map
(\s ->
if s == "NA" then
NotAvailable
else
StringValue s
)
string
]
provider : Decoder Provider
provider =
succeed Provider
|: field "code" string
|: (maybe <| field "converted_at" date)
|: (maybe <| field "name" string)
|: (maybe <| field "region" string)
|: (maybe <| field "terms_of_use" string)
|: field "website" string
series : Decoder Series
series =
succeed Series
|: field "series_code" string
|: field "series_name" string
|: field "provider_code" string
|: field "dataset_code" string
|: (maybe <| field "@frequency" string)
|: field "dimensions" (dict string)
|: (field "observations_attributes" (list observationsAttribute)
|> withDefault []
)
|: (field "period" (list string)
|> withDefault []
)
|: (field "period_start_day" (list string)
|> withDefault []
)
|: (field "value" (list observationValue)
|> withDefault []
)
observationsAttribute : Decoder ObservationsAttribute
observationsAttribute =
map2 (,) (index 0 string) (index 1 <| list string)
-- ENCODERS
encodeObservationValue : ObservationValue -> Encode.Value
encodeObservationValue observationValue =
case observationValue of
FloatValue f ->
Encode.float f
NotAvailable ->
Encode.string "NA"
StringValue s ->
Encode.string s
encodeSeries : Series -> Encode.Value
encodeSeries { code, name, period, periodStartDay, value } =
Encode.object
[ ( "series_code", Encode.string code )
, ( "series_name", Encode.string name )
, ( "period", Encode.list <| List.map Encode.string period )
, ( "period_start_day", Encode.list <| List.map Encode.string periodStartDay )
, ( "value", Encode.list <| List.map encodeObservationValue value )
]
encodeSeriesId : SeriesId -> Encode.Value
encodeSeriesId ( providerCode, datasetCode, seriesCode ) =
[ providerCode, datasetCode, seriesCode ]
|> List.map Encode.string
|> Encode.list
encodeSeriesTuple : ( SeriesId, SeriesName ) -> Encode.Value
encodeSeriesTuple ( seriesId, seriesName ) =
Encode.list
[ encodeSeriesId seriesId
, Encode.string seriesName
]
module Data.WebApi exposing (..)
import Data.Flags exposing (..)
import Json.Decode exposing (..)
import Semver
checkVersion : VersionRequirement -> Decoder ()
checkVersion { greaterThanOrEqualTo, lessThan } =
at [ "_meta", "python_project_version" ] Semver.decode
|> andThen
(\version ->
if
((version == greaterThanOrEqualTo)
|| (Semver.greaterThan version greaterThanOrEqualTo)
)
&& (Semver.lessThan version lessThan)
then
succeed ()
else
fail <|
"unexpected Web API version (received: "
++ Semver.print version
++ " but expected >= "
++ Semver.print greaterThanOrEqualTo
++ ", < "
++ Semver.print lessThan
++ ")"
)
This diff is collapsed.
......@@ -2,7 +2,8 @@ port module OutsideInfo exposing (..)
-- from https://github.com/splodingsocks/a-very-im-port-ant-topic
import Data.Dataset exposing (SeriesId, SeriesName, encodeSeriesId, encodeSeriesTuple)
import Data.Cart
import Data.Types exposing (..)
import Json.Decode as Decode exposing (decodeValue)
import Json.Encode as Encode
import Set exposing (Set)
......@@ -18,7 +19,8 @@ type InfoForOutside
= ErrorLogRequested String
| OpenAllDetailElements
| CloseAllDetailElements
| RenderGraph String String
| RenderGraph String (List Series) Bool
| RenderGraphFromUrl String String Bool
| SetDocumentTitle String
| AddToCart ( SeriesId, SeriesName )
| RemoveFromCart SeriesId
......@@ -51,12 +53,23 @@ sendInfoOutside info =
CloseAllDetailElements ->
{ tag = "CloseAllDetailElements", data = Encode.null }
RenderGraph graphElementId seriesUrl ->
RenderGraph graphElementId seriesList showLegend ->
{ tag = "RenderGraph"
, data =
Encode.object
[ ( "graphElementId", Encode.string graphElementId )
, ( "seriesList", Encode.list <| List.map encodeSeries seriesList )
, ( "showLegend", Encode.bool showLegend )
]
}
RenderGraphFromUrl graphElementId seriesUrl showLegend ->
{ tag = "RenderGraphFromUrl"
, data =
Encode.object
[ ( "graphElementId", Encode.string graphElementId )
, ( "seriesUrl", Encode.string seriesUrl )
, ( "showLegend", Encode.bool showLegend )
]
}
......@@ -91,7 +104,7 @@ getInfoFromOutside tagger onError =
\outsideInfo ->
case outsideInfo.tag of
"CartChanged" ->
case decodeValue Data.Dataset.seriesTuples outsideInfo.data of
case decodeValue Data.Cart.seriesTuples outsideInfo.data of
Ok seriesTuples ->
tagger <| CartChanged seriesTuples
......
......@@ -191,7 +191,7 @@ viewFeatures =
[ text "An open platform" ]
, p []
[ text "One website aggregating global data and "
, a [ href "https://api.db.nomics.world/apidocs" ]
, a [ href "https://api.db.nomics.world/v21/apidocs" ]
[ text "one API" ]
, text """. Data series delivered in various formats (CSV, Excel XLSX, JSON).
Direct access from your statistical software ("""
......
module Page.Errored exposing (..)
module Pages.Errored exposing (..)
{-| The page that renders when there was an error trying to load another page,
for example a "Ressource" Not Found error.
......@@ -10,6 +10,6 @@ import Html exposing (..)
view : String -> Html msg
view errorMessage =
div []
[ h1 [] [ text "Error Loading Page" ]
[ h1 [] [ text "Error loading page" ]
, p [] [ text errorMessage ]
]
module Pages.Home exposing (Model, Msg, init, update, view)
import Data.PaginatedList as PaginatedList exposing (PaginatedList)
import Data.Types exposing (..)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Http