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

Calcul et sauvegarde de métadonnées pour les ressources GBFS #1905

Merged
merged 24 commits into from
Nov 16, 2021

Conversation

AntoineAugusti
Copy link
Member

@AntoineAugusti AntoineAugusti commented Nov 5, 2021

Implémentation de #1852, utilise #1902

Cette PR ajoute une tâche quotidienne en charge de calculer et de stocker des métadonnées pour les ressources GBFS. Elle identifie les ressources GBFS, calcule des métadonnées (dont la validité du flux) et enregisre ceci dans les métadonnées de chaque ressource.

Un exemple de métadonnées

%{
  languages: ["fr"],
  system_details: %{name: "velhop", timezone: "Europe/Paris"},
  ttl: 3600,
  types: ["stations"],
  versions: ["1.1"],
  feeds: ["system_information", "station_information", "station_status"],
  validation: %GBFSValidationSummary{
    errors_count: 0,
    has_errors: false,
    version_detected: "1.1",
    version_validated: "1.1"
  },
  has_cors: true,
  is_cors_allowed: true
}

Comment on lines 10 to 16
setup do
:verify_on_exit!
# Do not use a mock for the GBFS Validator as we'll mock HTTP calls
old_value = Application.fetch_env!(:transport, :gbfs_validator_impl)
on_exit(fn -> Application.put_env(:transport, :gbfs_validator_impl, old_value) end)
Application.put_env(:transport, :gbfs_validator_impl, Shared.Validation.GBFSValidator.HTTPClient)
end
Copy link
Member Author

Choose a reason for hiding this comment

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

Similaire aux tests utilisant le "page cache" dans l'app GBFS

@AntoineAugusti AntoineAugusti marked this pull request as ready for review November 8, 2021 13:38
@AntoineAugusti AntoineAugusti changed the title Gbfs metadata Calcul et sauvegarde de métadonnées pour les ressources GBFS Nov 8, 2021
apps/transport/lib/transport/gbfs_metadata.ex Show resolved Hide resolved
end

@spec compute_feed_metadata(Resource.t()) :: map()
def compute_feed_metadata(resource) do
Copy link
Member Author

Choose a reason for hiding this comment

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

C'est cette fonction qui est testée avec attention

config/prod.exs Outdated
@@ -20,6 +20,8 @@ config :transport, Transport.Scheduler,
{"@daily", {Transport.DataChecker, :outdated_data, []}},
# Set inactive data
{"@daily", {Transport.DataChecker, :inactive_data, []}},
# Compute metadata for GBFS resources
{"@daily", {Transport.GBFSMetadata, :set_gbfs_feeds_metadata, []}},
Copy link
Member Author

Choose a reason for hiding this comment

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

Un avis sur la fréquence et l'enchainement par rapport aux autres jobs ?

Actuellement ça finit en moins de 30 secondes car ça analyse 30 feeds

Copy link
Contributor

Choose a reason for hiding this comment

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

Pour commencer une fois par nuit ça me paraît bien comme tu as mis là !

Après une fois qu'on sera sur du job récurrent, on pourra peut-être accélérer et voir comment ça gère.

Comment on lines 13 to 18
Resource
|> join(:inner, [r], d in Dataset, on: r.dataset_id == d.id)
|> where([_r, d], d.type == "bike-scooter-sharing" and d.is_active)
|> where([r, _d], like(r.url, "%gbfs.json") or r.format == "gbfs")
|> where([r, _d], not fragment("? ~ ?", r.url, "station|free_bike"))
|> Repo.all()
Copy link
Member Author

Choose a reason for hiding this comment

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

Reprend la requête SQL ici #1891 (comment)

Copy link
Contributor

@thbar thbar left a comment

Choose a reason for hiding this comment

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

Je vais proposer de petits refactorings sur les tests, et ajouter un peu de doc, mais ça semble good !

@@ -8,6 +8,7 @@ defmodule Shared.Validation.GBFSValidator do
A structure holding validation results for a GBFS feed
"""
@enforce_keys [:has_errors, :errors_count, :version_detected, :version_validated]
@derive Jason.Encoder
Copy link
Contributor

Choose a reason for hiding this comment

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

apps/shared/test/validation/gbfs_validator_test.exs Outdated Show resolved Hide resolved
apps/transport/lib/transport/gbfs_metadata.ex Show resolved Hide resolved
ttl: ttl(json)
}
else
e ->
Copy link
Contributor

Choose a reason for hiding this comment

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

Dans le futur, peut-être qu'on gagnera à mettre de la télémétrie ici (et/ou à retourner une erreur à l'appelant)

apps/transport/lib/transport/gbfs_metadata.ex Show resolved Hide resolved
iex> Transport.GBFSMetadata.has_cors?(%HTTPoison.Response{headers: [{"Access-Control-Allow-Origin", "*"}]})
true
"""
def has_cors?(%HTTPoison.Response{} = response) do
Copy link
Contributor

Choose a reason for hiding this comment

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

Toute cette partie pourra peut-être être bougée dans le futur vers un module non spécifique au GBFS, si on retrouve l'usage ailleurs.

apps/shared/test/validation/gbfs_validator_test.exs Outdated Show resolved Hide resolved
end
end

defp types(%{"data" => _data} = payload) do
Copy link
Contributor

Choose a reason for hiding this comment

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

Dans un style un peu plus fonctionnel, je suggère :

Suggested change
defp types(%{"data" => _data} = payload) do
defp types(%{"data" => _data} = payload) do
result =
[]
|> maybe_add_type("free_floating", has_feed?(payload, "free_bike_status"))
|> maybe_add_type("stations", has_feed?(payload, "station_information"))
if Enum.empty?(result) do
Logger.error("Cannot detect GBFS types for feed #{inspect(payload)}")
nil
else
result
end
end
def maybe_add_type(result, tag, condition) do
if condition, do: result ++ [tag], else: result
end

Copy link
Contributor

Choose a reason for hiding this comment

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

(à vérifier!)

Copy link
Contributor

@thbar thbar left a comment

Choose a reason for hiding this comment

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

Beau boulot !

J'ai fait des modifications liées à des éléments simples (simplification d'un test, correction d'un bug sur l'usage de Mox, etc), et fait une proposition de refactoring.

Par ailleurs plusieurs remarques de "haut niveau" (qui n'appellent pas forcément à modification, je te laisse juge, ou à dispo pour en discuter comme tu veux) :

Sérialisation / désérialisation

Il faudra faire attention au fait qu'une séquence sérialisation + désérialisation comme suit (dont je me suis servi pour regarder un peu le résultat dans IEx sans aller dans la base de données) ne redonne pas les mêmes éléments:

Transport.GBFSMetadata.gbfs_feeds_query()
|> DB.Repo.all()
|> Enum.take(1)
|> Enum.each(fn(resource) ->
  Transport.GBFSMetadata.compute_feed_metadata(resource)
  |> IO.inspect(IEx.inspect_opts())
  |> Jason.encode!()
  |> Jason.decode!()
  |> IO.inspect(IEx.inspect_opts())
end)
%{
  feeds: ["system_information", "station_information", "station_status"],
  has_cors: false,
  is_cors_allowed: false,
  languages: ["en"],
  system_details: %{name: "Vélocéo Vannes", timezone: "Europe/Paris"},
  ttl: 60,
  types: ["stations"],
  validation: %Shared.Validation.GBFSValidator.Summary{
    errors_count: 0,
    has_errors: false,
    version_detected: "1.0",
    version_validated: "1.0"
  },
  versions: ["1.0"]
}
%{
  "feeds" => ["system_information", "station_information", "station_status"],
  "has_cors" => false,
  "is_cors_allowed" => false,
  "languages" => ["en"],
  "system_details" => %{
    "name" => "Vélocéo Vannes",
    "timezone" => "Europe/Paris"
  },
  "ttl" => 60,
  "types" => ["stations"],
  "validation" => %{
    "errors_count" => 0,
    "has_errors" => false,
    "version_detected" => "1.0",
    "version_validated" => "1.0"
  },
  "versions" => ["1.0"]
}

En particulier le typage sur %Shared.Validation.GBFSValidator.Summary disparaît.

C'est sans impact a priori là, mais à garder en tête si on utilise @derive Jason.Encoder davantage.

Impact des méthodes privées defp sur la complexité du test

Je pense qu'on pourrait avoir un test un peu "intégration" avec les stubs, et pour le reste tester plus unitairement chaque méthode (en la passant de defp à def), et que ça pourrait donner (à vérifier, c'est juste une intuition) des tests plus simples à appréhender.

@thbar
Copy link
Contributor

thbar commented Nov 15, 2021

(J'ai aussi adapté le code en provenance de master, il y avait un peu de travail après le merge de #1875).

@AntoineAugusti
Copy link
Member Author

@thbar Merci de ta relecture attentive et des tests localement ! Et des améliorations effectuées :)

J'avais remarqué la différence de désérialisation/sérialisation où l'on perd la structure quand on récupère un objet depuis la BD. C'est facheux, mais je pense que c'est partout comme ça dans le projet étant donné qu'on indique juste que metadata est du JSON, mais pas moyen de faire un chargement plus intelligent. Les tests mettent en évidence ce comportement, il sera toujours temps d'adapter ça, sûrement en faisant du cast quand on hydrate l'objet.

C'est vrai que je trouve que j'ai plus souvent à des defp que le reste du code, je vais faire attention à l'avenir, et ne pas avoir peur de tester ces différentes fonctions, avec des doctests ou des tests.

@AntoineAugusti AntoineAugusti merged commit 069d93b into master Nov 16, 2021
@AntoineAugusti AntoineAugusti deleted the gbfs-metadata branch November 16, 2021 10:09
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.

2 participants