1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

[modules] Add new covid tracker module (#1037)

* Add draft of covid module

* Work on pointers

* Add country stats

* Remove recovered, stays at 0

* Handle response code

* One struct for both

* List of countries

* Add test

* Add test for countries

* Fix typos

* Format numbers based on language/locale
This commit is contained in:
David Bouchare 2020-12-29 22:33:14 +01:00 committed by GitHub
parent d7da659b8b
commit 2f2df04478
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 225 additions and 0 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/wtfutil/wtf/modules/circleci"
"github.com/wtfutil/wtf/modules/clocks"
"github.com/wtfutil/wtf/modules/cmdrunner"
"github.com/wtfutil/wtf/modules/covid"
"github.com/wtfutil/wtf/modules/cryptoexchanges/bittrex"
"github.com/wtfutil/wtf/modules/cryptoexchanges/blockfolio"
"github.com/wtfutil/wtf/modules/cryptoexchanges/cryptolive"
@ -137,6 +138,9 @@ func MakeWidget(
case "clocks":
settings := clocks.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = clocks.NewWidget(tviewApp, settings)
case "covid":
settings := covid.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = covid.NewWidget(tviewApp, settings)
case "cmdrunner":
settings := cmdrunner.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = cmdrunner.NewWidget(tviewApp, settings)

14
modules/covid/cases.go Normal file
View File

@ -0,0 +1,14 @@
package covid
// Cases holds the latest cases
type Cases struct {
Latest Latest `json:"latest"`
}
// Latest holds the number of global confirmed cases and deaths due to Covid
type Latest struct {
Confirmed int `json:"confirmed"`
Deaths int `json:"deaths"`
// Not currently used but holds information about the country
Locations []interface{} `json:"locations,omitempty"`
}

58
modules/covid/client.go Normal file
View File

@ -0,0 +1,58 @@
package covid
import (
"fmt"
"net/http"
"github.com/wtfutil/wtf/utils"
)
const covidTrackerAPIURL = "https://coronavirus-tracker-api.herokuapp.com/v2/"
// LatestCases queries the /latest endpoint, does not take any query parameters
func LatestCases() (*Cases, error) {
latestURL := covidTrackerAPIURL + "latest"
resp, err := http.Get(latestURL)
if resp.StatusCode != 200 {
return nil, fmt.Errorf(resp.Status)
}
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
var latestGlobalCases Cases
err = utils.ParseJSON(&latestGlobalCases, resp.Body)
if err != nil {
return nil, err
}
return &latestGlobalCases, nil
}
// LatestCountryCases queries the /locations endpoint, takes a query parameter: the country code
func (widget *Widget) LatestCountryCases(countries []interface{}) ([]*Cases, error) {
countriesCovidData := []*Cases{}
for _, name := range countries {
countryURL := covidTrackerAPIURL + "locations?source=jhu&country_code=" + name.(string)
resp, err := http.Get(countryURL)
if resp.StatusCode != 200 {
return nil, fmt.Errorf(resp.Status)
}
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
var latestCountryCases Cases
err = utils.ParseJSON(&latestCountryCases, resp.Body)
if err != nil {
return nil, err
}
// add stats for each country to the slice
countriesCovidData = append(countriesCovidData, &latestCountryCases)
}
return countriesCovidData, nil
}

View File

@ -0,0 +1,31 @@
package covid
import (
"testing"
)
func TestLatestCases(t *testing.T) {
latestCasesToAssert, err := LatestCases()
if err != nil {
t.Error("LatestCases() returned an error")
}
if latestCasesToAssert.Latest.Confirmed == 0 {
t.Error("LatestCases() should return a non 0 integer")
}
}
func (widget *Widget) TestCountryCases(t *testing.T) {
countryList := []string{"US", "FR"}
c := make([]interface{}, len(countryList))
for i, v := range countryList {
c[i] = v
}
latestCountryCasesToAssert, err := widget.LatestCountryCases(c)
if err != nil {
t.Error("LatestCountryCases() returned an error")
}
if len(latestCountryCasesToAssert) == 0 {
t.Error("LatestCountryCases() should not be empty")
}
}

31
modules/covid/settings.go Normal file
View File

@ -0,0 +1,31 @@
package covid
import (
"github.com/olebedev/config"
"github.com/wtfutil/wtf/cfg"
)
const (
defaultFocusable = false
defaultTitle = "Covid tracker"
)
// Settings is the struct for this module's settings
type Settings struct {
*cfg.Common
countries []interface{} `help:"Countries (codes) from which to retrieve stats."`
}
// NewSettingsFromYAML returns the settings from the config yaml file
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
settings := Settings{
Common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
// List of countries to retrieve stats from
countries: ymlConfig.UList("countries"),
}
return &settings
}

87
modules/covid/widget.go Normal file
View File

@ -0,0 +1,87 @@
package covid
import (
"fmt"
"github.com/rivo/tview"
"github.com/wtfutil/wtf/view"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
// Widget is the struct that defines this module widget
type Widget struct {
view.TextWidget
settings *Settings
err error
}
// NewWidget creates a new widget for this module
func NewWidget(app *tview.Application, settings *Settings) *Widget {
widget := &Widget{
TextWidget: view.NewTextWidget(app, nil, settings.Common),
settings: settings,
}
widget.View.SetScrollable(true)
return widget
}
// Refresh checks if this module widget is disabled
func (widget *Widget) Refresh() {
if widget.Disabled() {
return
}
widget.Redraw(widget.content)
}
// Render renders this module widget
func (widget *Widget) Render() {
widget.Redraw(widget.content)
}
// Display stats based on the user's locale
func (widget *Widget) displayStats(cases int) string {
prntr := message.NewPrinter(language.English)
str := fmt.Sprintf("%s", prntr.Sprintf("%d", cases))
return str
}
func (widget *Widget) content() (string, string, bool) {
title := defaultTitle
if widget.CommonSettings().Title != "" {
title = widget.CommonSettings().Title
}
cases, err := LatestCases()
var covidStats string
if err != nil {
widget.err = err
} else {
// Display global stats
covidStats = fmt.Sprintf("[%s]Global[white]\n", widget.settings.Colors.Subheading)
covidStats += fmt.Sprintf("%s: %s\n", "Confirmed", widget.displayStats(cases.Latest.Confirmed))
covidStats += fmt.Sprintf("%s: %s\n", "Deaths", widget.displayStats(cases.Latest.Deaths))
}
// Retrieve country stats if country codes are set in the config
if len(widget.settings.countries) > 0 {
countryCases, err := widget.LatestCountryCases(widget.settings.countries)
if err != nil {
widget.err = err
} else {
for i, name := range countryCases {
covidStats += fmt.Sprintf("[%s]Country[white]: %s\n", widget.settings.Colors.Subheading, widget.settings.countries[i])
covidStats += fmt.Sprintf("%s: %s\n", "Confirmed", widget.displayStats(name.Latest.Confirmed))
covidStats += fmt.Sprintf("%s: %s\n", "Deaths", widget.displayStats(name.Latest.Deaths))
}
}
}
return title, covidStats, true
}