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

feat: ingredients knowledge panels #10904

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion lib/ProductOpener/KnowledgePanels.pm
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ BEGIN {
&initialize_knowledge_panels_options
&create_knowledge_panels
&create_panel_from_json_template
&add_taxonomy_properties_in_target_languages_to_object

); # symbols to export on request
%EXPORT_TAGS = (all => [@EXPORT_OK]);
Expand All @@ -66,6 +67,7 @@ use ProductOpener::Lang qw/f_lang f_lang_in_lc lang lang_in_other_lc/;
use ProductOpener::Display qw/:all/;
use ProductOpener::Ecoscore qw/is_ecoscore_extended_data_more_precise_than_agribalyse/;
use ProductOpener::PackagerCodes qw/%packager_codes/;
use ProductOpener::KnowledgePanelsIngredients qw/create_ingredients_list_panel/;
use ProductOpener::KnowledgePanelsContribution qw/create_contribution_card_panel/;
use ProductOpener::KnowledgePanelsReportProblem qw/create_report_problem_card_panel/;
use ProductOpener::ProductsFeatures qw/feature_enabled/;
Expand Down Expand Up @@ -271,6 +273,8 @@ sub create_knowledge_panels ($product_ref, $target_lc, $target_cc, $options_ref,
Helper function to allow to enter multiline strings in JSON templates.
The function converts the multiline string into a single line string.

New lines are converted to \n, and quotes " and \ are escaped if not escaped already.

=cut

sub convert_multiline_string_to_singleline ($line) {
Expand All @@ -285,6 +289,30 @@ sub convert_multiline_string_to_singleline ($line) {
return '"' . $line . '"';
}

=head2 convert_multiline_string_to_singleline_without_line_breaks_and_extra_spaces($line)

Helper function to allow to enter multiline strings in JSON templates.
The function converts the multiline string into a single line string.

Line breaks are converted to spaces, and multiple spaces are converted to a single space.

This function is useful in templates where we use IF statements etc. to generate a single value like a title.

=cut

sub convert_multiline_string_to_singleline_without_line_breaks_and_extra_spaces ($line) {
Comment on lines +292 to +303
Copy link
Member

Choose a reason for hiding this comment

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

Good idea :-)


# Escape " and \ unless they have been escaped already
# negative look behind to not convert \n to \\n or \" to \\" or \\ to \\\\
$line =~ s/(?<!\\)("|\\)/\\$1/g;

$line =~ s/\s+/ /g;
$line =~ s/^\s+//;
$line =~ s/\s+$//;

return '"' . $line . '"';
}

=head2 create_panel_from_json_template ( $panel_id, $panel_template, $panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref )

Creates a knowledge panel from a JSON template.
Expand All @@ -299,6 +327,8 @@ Some special features that are not included in the JSON format are supported:
- The multiline strings will be converted to a single string.
- Quotes " are automatically escaped unless they are already escaped

Using two backticks at the start and end of the string removes line breaks and extra spaces.

3. Comments can be included by starting a line with //
- Comments will be removed in the resulting JSON, they are only intended to make the source template easier to understand.

Expand Down Expand Up @@ -384,6 +414,8 @@ sub create_panel_from_json_template ($panel_id, $panel_template, $panel_data_ref

# Also escape quotes " to \"

$panel_json
=~ s/\`\`([^\`]*)\`\`/convert_multiline_string_to_singleline_without_line_breaks_and_extra_spaces($1)/seg;
$panel_json =~ s/\`([^\`]*)\`/convert_multiline_string_to_singleline($1)/seg;

# Remove trailing commas at the end of a string delimited by quotes
Expand Down Expand Up @@ -982,7 +1014,10 @@ sub create_health_card_panel ($product_ref, $target_lc, $target_cc, $options_ref
$log->debug("create health card panel", {code => $product_ref->{code}}) if $log->is_debug();

# All food, pet food and beauty products have ingredients
create_ingredients_panel($product_ref, $target_lc, $target_cc, $options_ref);
if (feature_enabled("ingredients")) {
create_ingredients_panel($product_ref, $target_lc, $target_cc, $options_ref);
create_ingredients_list_panel($product_ref, $target_lc, $target_cc, $options_ref);
}

# Show additives only for food and pet food
if (feature_enabled("additives")) {
Expand Down
156 changes: 156 additions & 0 deletions lib/ProductOpener/KnowledgePanelsIngredients.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# This file is part of Product Opener.
#
# Product Opener
# Copyright (C) 2011-2023 Association Open Food Facts
# Contact: [email protected]
# 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 <http://www.gnu.org/licenses/>.

=head1 NAME

ProductOpener::KnowledgePanelsIngredients - Generate knowledge panels to report a problem with the data or the product

=head1 SYNOPSIS

Knowledge panels to indicate how to report a problem with the product data,
or with the product (e.g. link to report to authorities like SignalConso in France)

=cut

package ProductOpener::KnowledgePanelsIngredients;

use ProductOpener::PerlStandards;
use Exporter qw< import >;

use Log::Any qw($log);

BEGIN {
use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS);
@EXPORT_OK = qw(
&create_ingredients_list_panel
&create_data_quality_panel
); # symbols to export on request
%EXPORT_TAGS = (all => [@EXPORT_OK]);
}

use vars @EXPORT_OK;

use ProductOpener::KnowledgePanels
qw(create_panel_from_json_template add_taxonomy_properties_in_target_languages_to_object);
use ProductOpener::Tags qw(:all);

use Encode;
use Data::DeepAccess qw(deep_get);

=head2 create_ingredients_list_panel ( $product_ref, $target_lc, $target_cc, $options_ref )

Creates a panel with a list of ingredients as individual panels.

=head3 Arguments

=head4 product reference $product_ref

=head4 language code $target_lc

=head4 country code $target_cc

=cut

sub create_ingredients_list_panel ($product_ref, $target_lc, $target_cc, $options_ref) {

$log->debug("create ingredients list panel", {code => $product_ref->{code}}) if $log->is_debug();

# Create a panel only if the product has ingredients

if ((defined $product_ref->{ingredients_tags}) and (scalar @{$product_ref->{ingredients_tags}} > 0)) {

my $ingredient_i = 0; # sequence number for ingredients
# creates each individual panels for each ingredient
my @ingredients_panels_ids
stephanegigandet marked this conversation as resolved.
Show resolved Hide resolved
= create_ingredients_panels_recursive($product_ref, \$ingredient_i, 0, $product_ref->{ingredients},
$target_lc, $target_cc, $options_ref);
my $ingredients_list_panel_data_ref = {ingredients_panels_ids => \@ingredients_panels_ids};

# create the panel that reference ingredients panels
create_panel_from_json_template(
stephanegigandet marked this conversation as resolved.
Show resolved Hide resolved
"ingredients_list",
"api/knowledge-panels/health/ingredients/ingredients_list.tt.json",
$ingredients_list_panel_data_ref,
$product_ref, $target_lc, $target_cc, $options_ref
);

}
return;
}

sub create_ingredients_panels_recursive ($product_ref, $ingredient_i_ref, $level, $ingredients_ref, $target_lc,
$target_cc, $options_ref)
{

my @ingredients_panels_ids = ();

foreach my $ingredient_ref (@$ingredients_ref) {

push @ingredients_panels_ids,
create_ingredient_panel($product_ref, $ingredient_i_ref, $level, $ingredient_ref, $target_lc, $target_cc,
$options_ref);
if (defined $ingredient_ref->{ingredients}) {
push @ingredients_panels_ids,
create_ingredients_panels_recursive($product_ref, $ingredient_i_ref, $level + 1,
$ingredient_ref->{ingredients},
$target_lc, $target_cc, $options_ref);
}

}

return @ingredients_panels_ids;
}

sub create_ingredient_panel ($product_ref, $ingredient_i_ref, $level, $ingredient_ref, $target_lc, $target_cc,
$options_ref)
{

$$ingredient_i_ref++;
my $ingredient_panel_id = "ingredient_" . $$ingredient_i_ref;

my $ingredient_panel_data_ref
= {ingredient_id => $ingredient_ref->{id}, level => $level, ingredient => $ingredient_ref};

# Wikipedia abstracts, in target language or English

my $target_lcs_ref = [$target_lc];
if ($target_lc ne "en") {
push @$target_lcs_ref, "en";
}

add_taxonomy_properties_in_target_languages_to_object($ingredient_panel_data_ref, "ingredients",
$ingredient_ref->{id}, ["wikipedia_url", "wikipedia_title", "wikipedia_abstract"],
$target_lcs_ref);

# We check if the knowledge content for this ingredient (and language/country) is available.
# If it is it will be displayed instead of the wikipedia extract
my $ingredient_description = get_knowledge_content("ingredients", $ingredient_ref->{id}, $target_lc, $target_cc);

if (defined $ingredient_description) {
$ingredient_panel_data_ref->{ingredient_description} = $ingredient_description;
}

create_panel_from_json_template($ingredient_panel_id, "api/knowledge-panels/health/ingredients/ingredient.tt.json",
$ingredient_panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);

return $ingredient_panel_id;
}

1;
9 changes: 9 additions & 0 deletions po/common/common.pot
Original file line number Diff line number Diff line change
Expand Up @@ -7203,3 +7203,12 @@ msgstr "Poor repairability"
msgctxt "attribute_repairability_index_france_bad_description_short"
msgid "Bad repairability"
msgstr "Bad repairability"

msgctxt "estimate"
msgid "estimate"
msgstr "estimate"

# information on several ingredients, in plural in languages like French
msgctxt "ingredient_information"
msgid "Ingredient information"
msgstr "Ingredient information"
9 changes: 9 additions & 0 deletions po/common/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -7196,3 +7196,12 @@ msgstr "Bad repairability"
msgctxt "concerned_categories"
msgid "Bad repairability"
msgstr "Bad repairability"

msgctxt "estimate"
msgid "estimate"
msgstr "estimate"

# information on several ingredients, in plural in languages like French
msgctxt "ingredient_information"
msgid "Ingredient information"
msgstr "Ingredient information"
8 changes: 8 additions & 0 deletions po/common/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -7051,3 +7051,11 @@ msgctxt "attribute_repairability_index_france_bad_description_short"
msgid "Bad repairability"
msgstr "Très mauvaise réparabilité"

msgctxt "estimate"
msgid "estimate"
msgstr "estimation"

# information on several ingredients, in plural in languages like French
msgctxt "ingredient_information"
msgid "Ingredient information"
msgstr "Information sur les ingrédients"
4 changes: 4 additions & 0 deletions scss/_product-page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ $decalTop:48px;
}
}

li.accordion-navigation.accordion-navigation-inactive:after {
display: none;
}

// align dropdown only on manages images accordion
#manage_images_accordion .accordion-navigation {
&:after {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"level": "info",
"topics": [
"health"
],
"size": "small",
"title_element": {
// double backticks: convert to a single line without newlines and extra spaces
Copy link
Member

Choose a reason for hiding this comment

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

😲 cool !

"title": ``
[% dash = "—" %]
[% dash.repeat(panel.level) %]

[% display_taxonomy_tag_name("ingredients", panel.ingredient_id) %][% sep %]:
[% IF panel.ingredient.percent.defined %]
[% sprintf("%.1f", panel.ingredient.percent) %]%
[% ELSIF panel.ingredient.percent_estimate.defined %]
[% IF panel.ingredient.percent_estimate < 2 %]
< 2% ([% lang("estimate") %])
[% ELSE %]
[% sprintf("%.1f", panel.ingredient.percent_estimate) %]% ([% lang("estimate") %])
[% END %]
[% END %]
``,
},
"elements": [
[% IF panel.ingredient_description %]
{
"element_type": "text",
"text_element": {
"html": `[% panel.ingredient_description %]`
alexgarel marked this conversation as resolved.
Show resolved Hide resolved
}
}
[% ELSIF panel.wikipedia_abstract %]
{
"element_type": "text",
"text_element": {
"html": `<strong>[% edq(panel.wikipedia_title) %][% sep %]:</strong> [% edq(panel.wikipedia_abstract) %]`,
"source_text": `Wikipedia`,
"source_url": `[% panel.wikipedia_url %]`,
"source_lc": "[% panel.wikipedia_url_lc %]",
"source_language": "[% panel.wikipedia_url_language %]"
}
}
[% END %]
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
`,
},
},
[% END %]
[% END %]
[% IF product.ingredients_n.defined && product.ingredients_n > 0 %]
{
"element_type": "panel",
"panel_element": {
"panel_id":
"ingredients_list",
},
},
[% END %]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"level": "info",
"topics": [
"health"
],
"expanded": true,
"title_element": {
"title":"[% lang("ingredient_information") %]",
},
"elements": [
[% FOREACH ingredient_panel_id IN panel.ingredients_panels_ids %]
{
"element_type": "panel",
"panel_element": {
"panel_id": "[% ingredient_panel_id %]",
}
},
[% END %]
]
}
Loading
Loading