From 8ef9eea2c836605ce97e424520506ed006fa3233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Fri, 9 Sep 2022 15:08:23 +0200 Subject: [PATCH] feat: send events when editing products (#7303) A firs step towards gamification: record product edit event, see #6259 --- .env | 1 + cgi/product_multilingual.pl | 7 +- conf/apache.conf | 3 + docker-compose.yml | 3 + lib/ProductOpener/Config2_docker.pm | 9 ++ lib/ProductOpener/Config2_sample.pm | 10 +- lib/ProductOpener/Config_obf.pm | 11 +++ lib/ProductOpener/Config_off.pm | 11 +++ lib/ProductOpener/Config_opf.pm | 11 +++ lib/ProductOpener/Config_opff.pm | 11 +++ lib/ProductOpener/Events.pm | 143 ++++++++++++++++++++++++++++ 11 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 lib/ProductOpener/Events.pm diff --git a/.env b/.env index b9ec3dc8e6865..5ca536dd8fecb 100644 --- a/.env +++ b/.env @@ -37,6 +37,7 @@ MONGODB_CACHE_SIZE=8 # GB MONGO_INITDB_ROOT_USERNAME=root MONGO_INITDB_ROOT_PASSWORD=test ROBOTOFF_URL=http://api:5500 # connect to Robotoff running in separate docker-compose deployment +EVENTS_URL= GOOGLE_CLOUD_VISION_API_KEY= CROWDIN_PROJECT_IDENTIFIER= CROWDIN_PROJECT_KEY= diff --git a/cgi/product_multilingual.pl b/cgi/product_multilingual.pl index 374b4901d7a3f..ac5a84d7d3ab8 100755 --- a/cgi/product_multilingual.pl +++ b/cgi/product_multilingual.pl @@ -44,6 +44,7 @@ use ProductOpener::ForestFootprint qw/:all/; use ProductOpener::Web qw(get_languages_options_list); use ProductOpener::Text qw/:all/; +use ProductOpener::Events qw/:all/; use Apache2::RequestRec (); use Apache2::Const (); @@ -1500,6 +1501,9 @@ ($product_ref, $field, $language) # Notify robotoff send_notification_for_product_change($product_ref, "updated"); + # Create an event + send_event( { user_id => $User_id, event_type => "product_edited", barcode => $code, points => 5}); + $template_data_ref_process->{display_random_sample_of_products_after_edits_options} = $options{display_random_sample_of_products_after_edits}; @@ -1518,10 +1522,11 @@ ($product_ref, $field, $language) ); display_product(\%request); - } } + $log->debug("product edited", { code => $code }) if $log->is_debug(); + $template_data_ref_process->{edited_product_url} = $edited_product_url; process_template('web/pages/product_edit/product_edit_form_process.tt.html', $template_data_ref_process, \$html) or $html = "

" . $tt->error() . "

"; diff --git a/conf/apache.conf b/conf/apache.conf index 4146175056de9..cc27fee09a049 100644 --- a/conf/apache.conf +++ b/conf/apache.conf @@ -14,6 +14,9 @@ PerlPassEnv PRODUCT_OPENER_DOMAIN PerlPassEnv PRODUCT_OPENER_PORT PerlPassEnv PRODUCERS_PLATFORM PerlPassEnv ROBOTOFF_URL +PerlPassEnv EVENTS_URL +PerlPassEnv EVENTS_USERNAME +PerlPassEnv EVENTS_PASSWORD PerlPassEnv MONGODB_HOST PerlPassEnv GOOGLE_CLOUD_VISION_API_KEY PerlPassEnv CROWDIN_PROJECT_IDENTIFIER diff --git a/docker-compose.yml b/docker-compose.yml index 2c2b47c154b34..cabcf6e59724d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,9 @@ x-backend-conf: &backend-conf - POSTGRES_USER - POSTGRES_PASSWORD - ROBOTOFF_URL + - EVENTS_URL + - EVENTS_USERNAME + - EVENTS_PASSWORD - GOOGLE_CLOUD_VISION_API_KEY - CROWDIN_PROJECT_IDENTIFIER - CROWDIN_PROJECT_KEY diff --git a/lib/ProductOpener/Config2_docker.pm b/lib/ProductOpener/Config2_docker.pm index 61b8d8c3fa287..5b3184923d3f6 100755 --- a/lib/ProductOpener/Config2_docker.pm +++ b/lib/ProductOpener/Config2_docker.pm @@ -46,6 +46,9 @@ BEGIN $crowdin_project_identifier $crowdin_project_key $robotoff_url + $events_url + $events_username + $events_password %server_options ); %EXPORT_TAGS = (all => [@EXPORT_OK]); @@ -93,6 +96,12 @@ my $postgres_url = "postgresql://${postgres_user}:${postgres_password}\@${postgr # enable an in-site robotoff-asker in the product page $robotoff_url = $ENV{ROBOTOFF_URL}; +# Set this to your instance of https://github.com/openfoodfacts/openfoodfacts-events +# enable creating events for some actions (e.g. when a product is edited) +$events_url = $ENV{EVENTS_URL}; +$events_username = $ENV{EVENTS_USERNAME}; +$events_password = $ENV{EVENTS_PASSWORD}; + %server_options = ( private_products => $producers_platform, # 1 to make products visible only to the owner (producer platform) producers_platform => $producers_platform, diff --git a/lib/ProductOpener/Config2_sample.pm b/lib/ProductOpener/Config2_sample.pm index cac0732518598..abd7fbdc0a0a0 100644 --- a/lib/ProductOpener/Config2_sample.pm +++ b/lib/ProductOpener/Config2_sample.pm @@ -40,7 +40,9 @@ BEGIN $crowdin_project_identifier $crowdin_project_key $robotoff_url - + $events_url + $events_username + $events_password %server_options ); @@ -74,6 +76,12 @@ $crowdin_project_key = ''; # enable an in-site robotoff-asker in the product page $robotoff_url = ''; +# Set this to your instance of https://github.com/openfoodfacts/openfoodfacts-events +# enable creating events for some actions (e.g. when a product is edited) +$events_url = ''; +$events_username = ''; +$events_password = ''; + %server_options = ( cookie_domain => "openfoodfacts.dev", # if not set, default to $server _domain diff --git a/lib/ProductOpener/Config_obf.pm b/lib/ProductOpener/Config_obf.pm index a6df14edbd738..ea310bcffab0e 100644 --- a/lib/ProductOpener/Config_obf.pm +++ b/lib/ProductOpener/Config_obf.pm @@ -49,6 +49,9 @@ BEGIN $crowdin_project_key $robotoff_url + $events_url + $events_username + $events_password $mongodb $mongodb_host @@ -199,8 +202,16 @@ $google_cloud_vision_api_key = $ProductOpener::Config2::google_cloud_vision_api_ $crowdin_project_identifier = $ProductOpener::Config2::crowdin_project_identifier; $crowdin_project_key = $ProductOpener::Config2::crowdin_project_key; +# Set this to your instance of https://github.com/openfoodfacts/robotoff/ to +# enable an in-site robotoff-asker in the product page $robotoff_url = $ProductOpener::Config2::robotoff_url; +# Set this to your instance of https://github.com/openfoodfacts/openfoodfacts-events +# enable creating events for some actions (e.g. when a product is edited) +$events_url = $ProductOpener::Config2::events_url; +$events_username = $ProductOpener::Config2::events_username; +$events_password = $ProductOpener::Config2::events_password; + # server options %server_options = %ProductOpener::Config2::server_options; diff --git a/lib/ProductOpener/Config_off.pm b/lib/ProductOpener/Config_off.pm index 1dfb5f4d2cbbf..1aead10895ad3 100644 --- a/lib/ProductOpener/Config_off.pm +++ b/lib/ProductOpener/Config_off.pm @@ -48,6 +48,9 @@ BEGIN $crowdin_project_key $robotoff_url + $events_url + $events_username + $events_password $mongodb $mongodb_host @@ -345,8 +348,16 @@ $google_cloud_vision_api_key = $ProductOpener::Config2::google_cloud_vision_api_ $crowdin_project_identifier = $ProductOpener::Config2::crowdin_project_identifier; $crowdin_project_key = $ProductOpener::Config2::crowdin_project_key; +# Set this to your instance of https://github.com/openfoodfacts/robotoff/ to +# enable an in-site robotoff-asker in the product page $robotoff_url = $ProductOpener::Config2::robotoff_url; +# Set this to your instance of https://github.com/openfoodfacts/openfoodfacts-events +# enable creating events for some actions (e.g. when a product is edited) +$events_url = $ProductOpener::Config2::events_url; +$events_username = $ProductOpener::Config2::events_username; +$events_password = $ProductOpener::Config2::events_password; + # server options %server_options = %ProductOpener::Config2::server_options; diff --git a/lib/ProductOpener/Config_opf.pm b/lib/ProductOpener/Config_opf.pm index 96a4f53e74dea..1d35503761c31 100644 --- a/lib/ProductOpener/Config_opf.pm +++ b/lib/ProductOpener/Config_opf.pm @@ -49,6 +49,9 @@ BEGIN $crowdin_project_key $robotoff_url + $events_url + $events_username + $events_password $mongodb $mongodb_host @@ -197,8 +200,16 @@ $google_cloud_vision_api_key = $ProductOpener::Config2::google_cloud_vision_api_ $crowdin_project_identifier = $ProductOpener::Config2::crowdin_project_identifier; $crowdin_project_key = $ProductOpener::Config2::crowdin_project_key; +# Set this to your instance of https://github.com/openfoodfacts/robotoff/ to +# enable an in-site robotoff-asker in the product page $robotoff_url = $ProductOpener::Config2::robotoff_url; +# Set this to your instance of https://github.com/openfoodfacts/openfoodfacts-events +# enable creating events for some actions (e.g. when a product is edited) +$events_url = $ProductOpener::Config2::events_url; +$events_username = $ProductOpener::Config2::events_username; +$events_password = $ProductOpener::Config2::events_password; + # server options %server_options = %ProductOpener::Config2::server_options; diff --git a/lib/ProductOpener/Config_opff.pm b/lib/ProductOpener/Config_opff.pm index 2ba73bd00c669..2e25e5e22356a 100644 --- a/lib/ProductOpener/Config_opff.pm +++ b/lib/ProductOpener/Config_opff.pm @@ -49,6 +49,9 @@ BEGIN $crowdin_project_key $robotoff_url + $events_url + $events_username + $events_password $mongodb $mongodb_host @@ -195,8 +198,16 @@ $google_cloud_vision_api_key = $ProductOpener::Config2::google_cloud_vision_api_ $crowdin_project_identifier = $ProductOpener::Config2::crowdin_project_identifier; $crowdin_project_key = $ProductOpener::Config2::crowdin_project_key; +# Set this to your instance of https://github.com/openfoodfacts/robotoff/ to +# enable an in-site robotoff-asker in the product page $robotoff_url = $ProductOpener::Config2::robotoff_url; +# Set this to your instance of https://github.com/openfoodfacts/openfoodfacts-events +# enable creating events for some actions (e.g. when a product is edited) +$events_url = $ProductOpener::Config2::events_url; +$events_username = $ProductOpener::Config2::events_username; +$events_password = $ProductOpener::Config2::events_password; + # server options %server_options = %ProductOpener::Config2::server_options; diff --git a/lib/ProductOpener/Events.pm b/lib/ProductOpener/Events.pm new file mode 100644 index 0000000000000..d4c583da1d7a0 --- /dev/null +++ b/lib/ProductOpener/Events.pm @@ -0,0 +1,143 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2022 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +=head1 NAME + +ProductOpener::Events - Send events to https://events.openfoodfacts.org + +=head1 SYNOPSIS + +C is used to create events to https://events.openfoodfacts.org + + use ProductOpener::Events qw/:all/; + + # TODO + +=head1 DESCRIPTION + +See https://github.com/openfoodfacts/openfoodfacts-events + +=cut + +package ProductOpener::Events; + +use ProductOpener::PerlStandards; +use Exporter qw< import >; + +BEGIN { + use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); + @EXPORT_OK = qw( + &send_event + ); # symbols to export on request + %EXPORT_TAGS = (all => [@EXPORT_OK]); +} + +use vars @EXPORT_OK; + +use Log::Any qw($log); + +use Encode; +use JSON::PP; +use LWP::UserAgent; +use HTTP::Request::Common; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Display qw/display_date_iso/; + +=head1 FUNCTIONS + +=head2 send_event ( $event_ref ) + +=head3 Arguments + +Arguments are passed through a single hash reference with the following keys: + +=head4 event_type - required - string + +Type of the event (e.g. "product_edited") + +=head4 barcode - required - string + +Barcode of the product. + +=head4 user id - required + +=cut + +sub send_event ($event_ref) { + + if ((defined $events_url) and ($events_url ne "")) { + + # Add timestamp if we event does not contain one already + if (not defined $event_ref->{timestamp}) { + $event_ref->{timestamp} = display_date_iso(time()); + } + + my $ua = LWP::UserAgent->new(); + my $endpoint = "$events_url/events"; + $ua->timeout(2); + + my $request = POST $endpoint, $event_ref; + $request->header('content-type' => 'application/json'); + $request->content(decode_utf8(encode_json($event_ref))); + + # Add basic HTTP authentification credentials if we have some + # (as of August 2022, they are required to post to /events) + if ((defined $events_username) and ($events_username ne "")) { + $request->authorization_basic($events_username, $events_password); + } + + $log->debug("send_event request", {endpoint => $endpoint, event => $event_ref}) if $log->is_debug(); + my $response = $ua->request($request); + + if ($response->is_success) { + $log->debug( + "send_event response ok", + { + endpoint => $endpoint, + event => $event_ref, + is_success => $response->is_success, + code => $response->code, + status_line => $response->status_line + } + ) if $log->is_debug(); + } + else { + $log->warn( + "send_event response not ok", + { + endpoint => $endpoint, + event => $event_ref, + is_success => $response->is_success, + code => $response->code, + status_line => $response->status_line, + response => $response + } + ) if $log->is_warn(); + } + } + else { + $log->debug("send_event EVENTS_URL not defined", {events_url => $events_url}) if $log->is_debug(); + } + + return; +} + +1;