Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Weather module #504

Closed
wants to merge 14 commits into from
Closed

[WIP] Weather module #504

wants to merge 14 commits into from

Conversation

lsicca
Copy link

@lsicca lsicca commented Jul 1, 2019

Pull Request check-list

To ensure your Pull Request can be accepted as fast as possible, make sure to review and check all of these items:

  • If your changes affects code, did your write the tests?
  • Are tests passing? (npm test on both front/server)
  • Is the linter passing? (npm run eslint on both front/server)
  • Did you run prettier? (npm run prettier on both front/server)
  • If your changes modify the API (REST or Node.js), did you modify the documentation? (Documentation is based on comments in code)

NOTE: these things are not required to open a PR and can be done afterwards / while the PR is open.

Description of change

  • Modification de la date pour avoir un affichage du type ‘Monday 01 July’
  • Ajout du nom de la maison servant de point de géolocalisation
  • Modification de la température (précision à 2 décimales)
  • Ajout des icônes météo manquantes
  • Ajout du taux d’humidité et de la vitesse du vent
  • Ajout des prévisions météo horaires pour les 8 prochaines heures avec icônes et températures ressenties
  • Affichage d’icônes de nuit lorsque le temps est clair et que l’heure est avant le lever du soleil ou après son coucher
  • Affichage d’alertes météo lorsqu’une alerte est renvoyée par darksky
  • Mise à jour des données météo toutes les 10 min au lieu de 30

Screenshots

Without and with forecast alert

CaptureGladysForecast-1

CaptureGladysForecast-2

Copy link
Contributor

@Pierre-Gilles Pierre-Gilles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello!

Merci pour la PR! Très beau travail, j'aime beaucoup le design que tu proposes 👏

Je t'ai mis dans cette review plusieurs feedback. N'y voit rien de négatif, juste des commentaires pour améliorer cette PR avant de la merger :)

Un feedback plus général:

Serait-il possible d'ajouter une option pour activer la box plus complète que tu as créé?Je pense que les deux boxs ont une raison d'exister (une box minimale, et une box complète). Tout le monde ne veut pas forcément les prévisions météos complète sur le dashboard. Dans l'UI de personnalisation du dashboard, on pourrait mettre un sélection radio genre ("box complète"/"box minimale")

* @example
* const icon = translateWeatherToFeIcon(weather);
*/
const translateWeatherToFeIcon = (weather) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je ne suis pas pour coupler l'API de météo au frontend.

Dans ce front on utilise feather icons, mais dans une appli mobile on pourrait utiliser une autre gamme d'icône par exemple. Pour moi l'API de météo doit renvoyer la météo dans un format simple et standard, et ensuite c'est au frontend de gérer la correspondance entre météo et UI/icônes, pas le backend.

Cette API est utilisée à d'autres endroits!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oui, mais mais la on ne couple pas au front ou a FE, on change juste la chaine de caratères renvoyée par l'API dans un format compatible (mais non dépendant) de FE.

Si on veut utiliser ce service dans un autre front/app avec une autre libraire que FE, on pourra remettre la fonction de parsing initiale (que j'ai supprimé du front).

En faisant ainsi, on améliore les perfs actuelles en supprimant un bloc de conditions et de traitements, sans dommager les éventuels autres front/app, qui, s'ils n'utilisent pas FE, devront de toute façon parser cette chaine de caractères.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si je ne m'abuse la fonction s'appelle bien translateWeatherToFeIcon non? ^^ "ToFeIcon". Donc si, dans cette PR il y a coupling des deux. Pour moi il ne devrait pas y avoir côté serveur de mention de feather icons, on doit renvoyer un attribut weather qui est neutre ("rain", "sunny", des trucs comme ça)

Cette API sert à plein de choses, pas juste le frontend!

Dans les scénarios, tu peux faire "si il est 11H ET que le temps est ensoleillé ALORS ouvrir le store". Ce serait un peu illogique de faire un test sur if "fe-rain", non? Plutôt if "rain" ?

return (
<div style={{ width: '10%', margin: '0.25em 1.25%' }}>
<p style={{ margin: 'auto', textAlign: 'center', fontSize: '10px', color: 'grey' }}>
{format24Hours(new Date(hour.datetime).getHours())}h
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essaie de privilégier la librairie de formatage des heures qu'on utilise déjà et qui est testée solidement, pas la peine de créer une fonction ici :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

dataToReturn.weather = 'unknown';
if (result.alerts) {
dataToReturn.alert = {
title: result.alerts[0].title,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Est-on sûr que si result.alerts exist, le tableau est rempli?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oui, tous les champs de l'object alerts sont required (cf doc darksky)

Copy link
Contributor

@Pierre-Gilles Pierre-Gilles Jul 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oui mais est-il possible d'avoir un tableau vide?

Style:

{
alerts: []
} 

La documentation DarkSky ne spécifie pas que le tableau est plein si il est précisé. Un test sur la taille du tableau en plus de son existence fera sens avant d'aller piocher dans le tableau.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://darksky.net/dev/docs#alerts

alerts est optionnel, mais s'il existe, toutes les variables sont required

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'avais bien lu la doc, et de mon interprétation ils parlent des objets présent dans le tableau: oui, si le tableau a une entrée, alors l'entrée possède tous ces attributs. Par contre le tableau n'a pas nécessairement d'entrée. Je mettrais un test pour être sur.

weather: 'fe-cloud',
},
],
temperature: '54.87',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pourquoi la température est un string?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

la fonction ToFixed(N) (utilisée pour forcer un nombre à N décimales après la virgule) renvoie une chaine de caractères (bizarrerie de JS).

C'est chelou mais vu que ça n'impacte pas le front, j'ai préféré laissé comme ça plutôt que de forcer une conversion en nombre qui aurait été inutile (comme dans la première remarque, pour une question de performance tout d'abord, mais aussi de modélisation : on ne fait aucun traitement qui n'apporte pas une plus-value).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le front est loin d'être le seul endroit qui utilise cette API!

L'API est utilisée dans les scénarios pour faire des règles "Si la température > XX ALORS YY" par exemple.

Cela n'a pas de sens de renvoyer une température sous forme de string, et si il y a des plus value à renvoyer un number qui peut être comparé à un autre number.

Je ne vois pas en quoi c'est moins performant de faire les choses bien.

Attention, j'ai l'impression que tu design cette API pour ton frontend uniquement. C'est très loin d'être le cas, cette API sert avant tout aux scénarios, dans Telegram, et à plein d'autres endroits.

Le formattage ne doit pas être fait côté serveur, mais côté client à mon sens. Ton toFixed devrait être fait côté client selon moi.

units: 'si',
wind_speed: 5.25,
weather: 'cloud',
wind_speed: '5.25',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

La vitesse du vent est un string aussi?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem que pour la température, j'utilise cette fonction pour arrondir ces 2 champs, ainsi que la température apparente.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem que pour la réponse précédente.

@codecov
Copy link

codecov bot commented Jul 5, 2019

Codecov Report

Merging #504 into master will increase coverage by 0.39%.
The diff coverage is 90.9%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #504      +/-   ##
==========================================
+ Coverage   91.14%   91.54%   +0.39%     
==========================================
  Files         334      334              
  Lines        3794     3784      -10     
==========================================
+ Hits         3458     3464       +6     
+ Misses        336      320      -16
Flag Coverage Δ
#server 91.54% <90.9%> (+0.39%) ⬆️
Impacted Files Coverage Δ
server/models/dashboard.js 100% <ø> (ø) ⬆️
server/api/controllers/weather.controller.js 100% <ø> (ø) ⬆️
server/services/darksky/index.js 89.65% <83.33%> (-2.35%) ⬇️
server/services/darksky/lib/formatResults.js 95.23% <93.75%> (+46.66%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d48fb2f...c692c88. Read the comment docs.

@lsicca
Copy link
Author

lsicca commented Jul 5, 2019

J'ai ajusté selon tes remarques et ajouté une option de choix dans la configuration pour garder une version basique ou avancée.

Par contre, je suis bloqué sur le test unitaire du service (j'ai volontairement laissé un test non réussi), car je ne vois pas comment changer une variable du service pour indiquer quelle version utiliser.

Pour être plus clair, une variable service permet d'indiquer s'il faut traiter la réponse selon un affichage basique ou avancé, et je ne vois pas comment simuler cette variable dans le test.

@Boimb
Copy link
Contributor

Boimb commented Jul 5, 2019

Regarde du côté des tests du service zwave.
Tu verras que la var gladys est surchargée avant le test. Ça permet de forcer des valeurs.
Plus d’infos sur les hooks de mocha ici : https://mochajs.org/#hooks
Y’a plein de trucs sympas de ce côté là pour faire varier tout en maîtrisant l’environnement.

@lsicca
Copy link
Author

lsicca commented Jul 7, 2019

La configuration du mode d'affichage (basic ou avancé) se fait maintenant dans la configuration de la box, et non dans l'encart intégration du service (ce qui n'était pas très logique)

CaptureGladysForecast-3

@Pierre-Gilles
Copy link
Contributor

La configuration du mode d'affichage (basic ou avancé) se fait maintenant dans la configuration de la box, et non dans l'encart intégration du service (ce qui n'était pas très logique)

C'est très propre comme ça! Beau boulot :)

As tu résolu tes problèmes de tests?

Il manque un peu de code coverage à ce que je vois.

@lsicca
Copy link
Author

lsicca commented Jul 9, 2019

En fait entre temps, j'ai découvert l'encart chat, que je n'avais vraiment vraiment regardé jusque la

J'ai vu que ma dernière PR cassait la demande de météo via le chat, sans que les tests me l'indiquent, donc j'ai commencé par fixer ça, et pendant que j'y étais, j'ai ajouté une fonctionnalité pour demander via le chat des prévisions météo pour une date future.

J'ai bien galéré avec les dates JS, les différents formats pour demander une date, et le module brain, mais ça a été assez instructif )

Je viens tout juste de terminer le dev (tout fonctionne, me reste juste un peu d'optimisation de code/perfs et d'UX), et le chat répond maintenant aux questions du type :

  • What will be the weather in 4 hours / days ?
  • What will be the weather at 7PM / AM ?
  • What will be the weather at 19:30 ?
  • What will be the weather friday ?
  • What will be the weather friday at 17:00 / 5PM ?

Concrètement, le service météo peut maintenant renvoyer la prévision météo selon un timestamp passé en option (j'ai aussi fix l'option offset existante).

L’idée finale est de pouvoir utiliser ce service simplement dans d'autres services
Par exemple :

  • Afficher une icône météo dans le calendrier caldav en fonction de la prévision au moment du RDV
  • Fermer les volets 30min avant le début annoncé d'un orage, sans attendre qu'un capteur ou une prévision en temps réel te dise que l'orage est déjà la
  • Activer le chauffage si la température extérieure baissera en dessous de 20 degré dans la prochaine heure (notamment pour ceux qui n'ont pas de capteurs, ou de regulateur)
  • Jouer une playlist de noel sur spotify s'il neige dehors

Sinon pour les tests, travailles tu en TDD ? j'ai pas vraiment l'impression en voyant tes tests, et j'ai un peu le sentiment que ceux ci sont plus développés pour avoir un code coverage le plus proche possible des 100%, que pour tester tous les uses cases (notamment d'erreurs) pouvant arriver.

Bref, je suis pas vraiment à l'aise avec les tests pour le moment, je pense passer mon week end dessus pour bien comprendre et finaliser ce module (je t'avoue que j'avais clairement pas prévu de passer autant de temps sur celui ci pour au final une plus value qui est plus de l'ordre du confort que de la nécessité)

Copy link
Contributor

@Pierre-Gilles Pierre-Gilles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super cool tout ça! C'est des vrais features utiles, c'est vraiment top. Beau boulot 👏

Sinon pour les tests, travailles tu en TDD ?

Alors oui je travaille principalement en TDD, après je t'avoue que ce service je l'ai développé le jour même de la sortie de l'alpha en urgence, ça m'étonne pas qu'il y a encore des petites approximations dans les tests. Ce service n'a pas été développé en TDD.

server/test/services/darksky/darsky.test.js Outdated Show resolved Hide resolved
server/services/darksky/index.js Outdated Show resolved Hide resolved
server/api/controllers/weather.controller.js Outdated Show resolved Hide resolved
server/api/controllers/weather.controller.js Outdated Show resolved Hide resolved
front/src/utils/consts.js Outdated Show resolved Hide resolved
@lsicca
Copy link
Author

lsicca commented Jul 10, 2019

Ok je comprends mieux.

Je ferai les tests ce week end, et je pullerai les devs pour le chat une fois que tous les tests seront validés.

@Pierre-Gilles
Copy link
Contributor

Top! Tiens moi au courant et n'hésite pas en cas de questions :)

@lsicca
Copy link
Author

lsicca commented Jul 14, 2019

Ok, je viens de balancer la dernière version.

CaptureGladysForecast-4
CaptureGladysForecast-5

N'hésites pas a faire une une code review ;)

Copy link
Contributor

@Pierre-Gilles Pierre-Gilles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello!

Très beau boulot, on commence à voir le bout! :)

Pour moi quasi pas de blocages sur cette PR à part des petits problèmes mineurs.

Je t'ai tout mis en commentaire.

J'adopte volontairement un ton naïf dans certains commentaires pour questionner certains choix, rien de méchant c'est juste pour se poser les bonnes questions et pour éviter qu'on fixe des conventions implicites dans Gladys qu'on ne veut pas forcément.

@@ -193,6 +193,7 @@ const INTENTS = {
},
WEATHER: {
GET: 'intent.weather.get',
GET_PREVISIONS: 'intent.weather.getPrevisions',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attention à la convention, ici pas de camelCase dans les clés d'intents, on met des tirets!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

server/package.json Show resolved Hide resolved
const { latitude, longitude, language, units, offset, datetime } = optionsMerged;
let timestamp = '';
if (offset !== 0) {
timestamp = `, ${moment()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Petite question, as tu essayé d'utiliser daysjs au lieu de moment ici?

Une des philosophies de la v4 est de rester le plus léger possible au niveau des librairies utilisées (quand c'est possible!) Moment est connu pour être un monstre assez lourd, et j'ai l'impression qu'ici dayjs pourrait faire le boulot ici. Qu'en pense tu?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oui, je me suis fait la même remarque sur moment, mais dayjs ne semble pas gérer les timezones, et me sortait des timestamp erronés.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah? daysjs ne gère pas les timezones? Même avec quelque chose comme ça :

iamkun/dayjs#323 (comment)

?

return { icon: 'cloud', summary: 'cloudy' };
}
if (weather.search('night') !== -1 && (datetime && sunrise && sunset && (datetime < sunrise || datetime > sunset))) {
return { icon: 'night', summary: 'clear night' };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Est-ce qu'on pourrait avoir des clés standardisée et sans espace ni majuscule? ("clear-night" ici)

J'ai vu dans les fichiers de tests que les summary n'était pas les mêmes qu'ici, passer par un fichier de consts dans le service darsky permettrait d'éviter ce genre d'erreur d'inattention :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le summary ici n'est pas le même que celui ci retourne par l'API Darksky.
Celui de Darksky est inutilisable car toujours en anglais et non documenté.

Celui ci, basé sur l’icône renvoyée par Darksky est utilisé dans les réponses textuelles du chat (c'est donc une valeur affichée a l'utilisateur).

J'ai supprime le summary de darksky que je renvoyais dans les prévisions horaires pour éviter la confusion (d'autant plus, qu'au final, je ne l'utilisais pas)

Par contre, tu as raison, il faudrait mettre ce texte dans le dossier config i18n, pour afficher dans la langue de l'utilisateur une fois que le français sera pris en charge par Gladys.

Mais je n'arrive pas a récupérer la langue de l'utilisateur dans ma fonction, comment faire ?
La commande gladys.stateManager.get('system', 'SYSTEM_LANGUAGE') utilisée dans le service Hue ne me renvoie rien car l'object gladys.stateManager.system est vide

server/services/darksky/lib/formatResults.js Show resolved Hide resolved
server/services/darksky/lib/formatResults.js Show resolved Hide resolved
switch (classification.intent) {
case 'weather.get':
weather = await this.get(house);
context.temperature = weather.temperature;
context.units = weather.units === 'si' ? '°C' : '°F';
await this.messageManager.replyByIntent(message, `weather.get.success.${weather.weather}`, context);
break;
case 'weather.getPrevisions':
if (context.time) {
house.target = 'hourly';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pourquoi faire une mutation sur un paramètre de cette fonction? Cela n'a pas de sens que la maison est un attribut "target"?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parce que selon la requete, cette variable peut avoir 3 valeurs : currently, hourly and daily.
Et le retour de l'API est différent selon ces 3 uses cases.

Le nom target est peut être pas le plus pertinent, mais j'avoue manquer d'inspiration ^^

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En fait mon problème c'est surtout que tu fais une mutation sur l'objet "house", ça n'a pas de sens! Pourquoi une maison aurait un attribut target?

server/lib/weather/weather.command.js Show resolved Hide resolved
dataToReturn.datetime = new Date(dataPoint.time * 1000);
dataToReturn.units = options.units;
dataToReturn.wind_speed = dataPoint.windSpeed;
dataToReturn.time_sunrise = new Date(result.daily.data[0].sunriseTime * 1000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attention, en anglais:

  • "heure du lever de soleil" = "sunrise time"
  • "heure du coucher de soleil" = "sunset time"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je sais, c'est un choix.

Je préférè regrouper les valeurs comme object_attribut plutôt que attribut_object
Ça permet de mutualiser les valeurs de même famille (mais pas forcément du même object) avec le même préfixe.

ça me parait plus logique, plus instinctif, mais surtout, ça aide pas mal pour l’autocomplétion des IDE

Par exemple, si je me souviens plus de tous les attributs wind que j'ai, juste en tapant "wind_", l'IDE affiche la liste des variables existantes. Sans ça, il faut scroller pour rechercher le nom des variables dans le code.

return { icon: 'night', summary: 'clear night' };
}
if (weather.search('rain') !== -1) {
return { icon: 'rain', summary: 'raining' };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autre remarque, j'ai du mal à comprendre la différence entre "icon" et "summary", j'ai l'impression que les deux renvoient toujours la même chose avec juste une différence grammaticale.. Si les deux sont liés, pourquoi en avoir deux?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Icon est utilise pour l'affichage graphique et summary pour une réponse textuelle

Copy link
Contributor

@Pierre-Gilles Pierre-Gilles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! Je t'ai répondu sur plusieurs messages. Je ne sais pas si c'est une erreur ou du code pas encore pushé, mais tu as mis certains feedbacks en "résolu" alors que c'est encore le code d'avant, c'est normal?

switch (classification.intent) {
case 'weather.get':
weather = await this.get(house);
context.temperature = weather.temperature;
context.units = weather.units === 'si' ? '°C' : '°F';
await this.messageManager.replyByIntent(message, `weather.get.success.${weather.weather}`, context);
break;
case 'weather.getPrevisions':
if (context.time) {
house.target = 'hourly';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

En fait mon problème c'est surtout que tu fais une mutation sur l'objet "house", ça n'a pas de sens! Pourquoi une maison aurait un attribut target?

const { latitude, longitude, language, units, offset, datetime } = optionsMerged;
let timestamp = '';
if (offset !== 0) {
timestamp = `, ${moment()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah? daysjs ne gère pas les timezones? Même avec quelque chose comme ça :

iamkun/dayjs#323 (comment)

?

@Pierre-Gilles
Copy link
Contributor

Hello :) De retour de congés, je reprend les PR.

Du nouveau sur celle-ci ?

@Pierre-Gilles Pierre-Gilles changed the title Weather module [WIP] Weather module Feb 27, 2020
@callemand callemand mentioned this pull request Nov 17, 2020
6 tasks
@atrovato
Copy link
Contributor

Je crois que le service darksky n'est plus d'actualité, non ?
Et comme le dit @callemand il a refait le système pour openweather : #961

@Pierre-Gilles
Copy link
Contributor

Je ferme cette PR en faveur de #961

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants