Skip to content
This repository has been archived by the owner on Sep 5, 2023. It is now read-only.

Taxonomy model for Label is not correct #720

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions Sources/Helpers/OFFUrlsHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ class OFFUrlsHelper: NSObject {
return URL(string: "\(baseUrl())/nucleotide/\(nucleotide.code)")!
}

static func url(forLabel label: Label) -> URL {
return URL(string: "\(baseUrl())/label/\(label.code)")!
}

// Not sure if there is a taxonomy for this
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

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

static func url(forOther other: OtherNutritionalSubstance) -> URL {
Expand Down
4 changes: 3 additions & 1 deletion Sources/Models/API/OFFReadAPIkeysJSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ struct OFFJson {
//static let IngredientsIdsDebugKey = "ingredients_ids_debug"
//static let IngredientsThatMayBeFromPalmOilNKey = "ingredients_that_may_be_from_palm_oil_n"
static let LabelsKey = "labels"
static let LabelsTagsKey = "labels_tags"
//static let LabelsPrevHierarchyKey = "labels_prev_hierarchy"
//static let LcKey = "lc"
static let MineralsTagsKey = "minerals_tags"
Expand Down Expand Up @@ -375,7 +376,8 @@ struct OFFJson {
OFFJson.IngredientsTextKey,
OFFJson.IngredientsThatMayBeFromPalmOilTagsKey,
// OFFJson.LabelsHierarchyKey,
// OFFJson.LabelsTagsKey,
OFFJson.LabelsKey,
OFFJson.LabelsTagsKey,
OFFJson.LangKey,
OFFJson.ProductNameLanguagesKey,
OFFJson.GenericNameLanguagesKey,
Expand Down
3 changes: 2 additions & 1 deletion Sources/Models/API/Product.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ struct Product: Mappable {
var manufacturingPlaces: String?
var origins: String?
var labels: [String]?
var labelsTags: [String]?
var citiesTags: [String]?
var embCodesTags: [String]?
var stores: [String]?
Expand Down Expand Up @@ -303,6 +304,7 @@ struct Product: Mappable {
manufacturingPlaces <- map[OFFJson.ManufacturingPlacesKey]
origins <- map[OFFJson.OriginsKey]
labels <- (map[OFFJson.LabelsKey], ArrayTransform())
labelsTags <- map[OFFJson.LabelsTagsKey]
citiesTags <- map[OFFJson.CitiesTagsKey]
// countries <- (map[OFFJson.CountriesKey], ArrayTransform())
countriesTags <- map[OFFJson.CountriesTagsKey]
Expand All @@ -316,7 +318,6 @@ struct Product: Mappable {
imageUrl <- map[OFFJson.ImageUrlKey]
ingredientsImageUrlDecoded <- map[OFFJson.ImageIngredientsUrlKey]
ingredientsListDecoded <- map[OFFJson.IngredientsKey]
labels <- (map[OFFJson.LabelsKey], ArrayTransform())
lang <- map[OFFJson.LangKey]
languageCodes <- map[OFFJson.LanguageCodesKey]
manufacturingPlaces <- map[OFFJson.ManufacturingPlacesKey]
Expand Down
49 changes: 49 additions & 0 deletions Sources/Models/API/Taxonomies/Label.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Label.swift
// OpenFoodFacts
//
// Created by Alexander Scott Beaty on 8/23/20.
//

import Foundation
import RealmSwift
import ObjectMapper

class Label: Object {

@objc dynamic var code = ""

let parents = List<String>()
let children = List<String>()
let names = List<Tag>()
jncosideout marked this conversation as resolved.
Show resolved Hide resolved

@objc dynamic var mainName = "" // name in the language of the app, for sorting
@objc dynamic var indexedNames = "" // all names concatenated, for search

convenience init(code: String, parents: [String], children: [String], names: [Tag]) {
self.init()
self.code = code

self.parents.removeAll()
self.parents.append(objectsIn: parents)

self.children.removeAll()
self.children.append(objectsIn: children)

self.names.removeAll()
self.names.append(objectsIn: names)

self.mainName = names.chooseForCurrentLanguage()?.value ?? ""
self.indexedNames = names.map({ (tag) -> String in
return tag.languageCode.appending(":").appending(tag.value)
}).joined(separator: " ||| ") // group all names to be able to query on only one field, independently of language
}

override static func primaryKey() -> String? {
return "code"
}

override static func indexedProperties() -> [String] {
return ["mainName", "indexedNames"]
}
}
6 changes: 6 additions & 0 deletions Sources/Models/DataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ protocol DataManagerProtocol {
func ingredientsAnalysis(forProduct product: Product) -> [IngredientsAnalysisDetail]
func ingredientsAnalysis(forTag tag: String) -> IngredientsAnalysis?
func ingredientsAnalysisConfig(forTag tag: String) -> IngredientsAnalysisConfig?
func label(forTag: String) -> Label?

func getTagline(_ callback: @escaping (_: Tagline?) -> Void)

Expand Down Expand Up @@ -257,6 +258,11 @@ class DataManager: DataManagerProtocol {
taxonomiesApi.getTagline(callback)
}

func label(forTag tag: String) -> Label? {
let myLabel = persistenceManager.label(forCode: tag)
return myLabel
}

// MARK: - Settings
func addAllergy(toAllergen: Allergen) {
persistenceManager.addAllergy(toAllergen: toAllergen)
Expand Down
18 changes: 18 additions & 0 deletions Sources/Models/PersistenceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ protocol PersistenceManagerProtocol {
func tagLine() -> Tagline?
var additivesIsEmpty: Bool { get }

func save(labels: [Label])
func label(forCode: String) -> Label?
var labelsIsEmpty: Bool { get }

// Offline
func save(offlineProducts: [RealmOfflineProduct])
func getOfflineProduct(forCode: String) -> RealmOfflineProduct?
Expand Down Expand Up @@ -365,6 +369,20 @@ class PersistenceManager: PersistenceManagerProtocol {
return getRealm().object(ofType: IngredientsAnalysisConfig.self, forPrimaryKey: code)
}

func save(labels: [Label]) {
saveOrUpdate(objects: labels)
log.info("Saved \(labels.count) labels in taxonomy database")
}

func label(forCode code: String) -> Label? {
return getRealm().object(ofType: Label.self, forPrimaryKey: code)
}

var labelsIsEmpty: Bool {
getRealm().objects(Label.self).isEmpty
}

// Offline Products
func save(offlineProducts: [RealmOfflineProduct]) {
saveOrUpdate(objects: offlineProducts)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Models/Transforms/TagTransform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class Tag: Object {

/// choose the most appropriate tags based on the language passed in parameters, default to english if not found
static func choose(inTags tags: [Tag], forLanguageCode languageCode: String? = nil, defaultToFirst: Bool = false) -> Tag? {
let lang = languageCode ?? Bundle.main.preferredLocalizations.first ?? "en"
let lang = languageCode ?? Bundle.main.currentLocalization

if let tag = tags.first(where: { (tag: Tag) -> Bool in
return tag.languageCode == lang
Expand Down
13 changes: 13 additions & 0 deletions Sources/Network/TaxonomiesParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ struct TaxonomiesParser: TaxonomiesParserProtocol {
return ingredientsAnalysisConfig
}

func parseLabels(data: [String: Any]) -> [Label] {
let labels = data.compactMap({ (labelCode: String, value: Any) -> Label? in
let tags = parseTags(value: value)
let parents = parseParents(value: value)
let children = parseChildren(value: value)
return Label(code: labelCode,
parents: parents,
children: children,
names: tags)
})
return labels
}

// MARK: - Private Helper Methods

private func parseTags(value: Any) -> [Tag] {
Expand Down
2 changes: 2 additions & 0 deletions Sources/Network/TaxonomiesRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct TaxonomiesRequest: URLRequestConvertible {
return route.rawValue + "/data/" + Endpoint.get
case (.post, _):
return Endpoint.post + route.rawValue
case (.get, _):
return Endpoint.get + "/data/" + route.rawValue
jncosideout marked this conversation as resolved.
Show resolved Hide resolved
default:
return ""
}
Expand Down
32 changes: 30 additions & 2 deletions Sources/Network/TaxonomiesService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum TaxonomiesRoute: String {
case getMinerals = "taxonomies/minerals.json"
case getNucleotides = "taxonomies/nucleotides.json"
case getInvalidBarcodes = "invalid-barcodes.json"
case getLabels = "taxonomies/labels.json"
}

enum FilesRouter: URLRequestConvertible {
Expand Down Expand Up @@ -357,6 +358,27 @@ class TaxonomiesService: TaxonomiesApi {
}
}

fileprivate func refreshLabels(_ callback: @escaping (_: Bool) -> Void) {
do {
let request = try TaxonomiesRequest(route: .getLabels, requestType: .get).asURLRequest()
jncosideout marked this conversation as resolved.
Show resolved Hide resolved
Alamofire.request(request)
.responseJSON { (response) in
switch response.result {
case .success(let responseBody):
if let json = responseBody as? [String: Any] {
let labels = self.taxonomiesParser.parseLabels(data: json)
self.persistenceManager.save(labels: labels)
callback(true)
}
case .failure(let error):
AnalyticsManager.record(error: error)
callback(false)
}
}
} catch {
callback(false)
}
}
// swiftlint:disable identifier_name

/// increment last number each time you want to force a refresh. Useful if you add a new refresh method or a new field
Expand All @@ -380,7 +402,8 @@ class TaxonomiesService: TaxonomiesApi {
persistenceManager.nucleotidesIsEmpty ||
// persistenceManager.otherNutritionalSubstancesIsEmpty ||
persistenceManager.ingredientsAnalysisIsEmpty ||
persistenceManager.ingredientsAnalysisConfigIsEmpty
persistenceManager.ingredientsAnalysisConfigIsEmpty ||
persistenceManager.labelsIsEmpty
if shouldDownload {
downloadTaxonomies()
} else {
Expand Down Expand Up @@ -454,12 +477,17 @@ class TaxonomiesService: TaxonomiesApi {
})

group.enter()

self.refreshInvalidBarcodes { (success) in
allSuccess = allSuccess && success
group.leave()
}

group.enter()
self.refreshLabels({ (success) in
allSuccess = allSuccess && success
group.leave()
})

group.wait()

if allSuccess {
Expand Down
1 change: 1 addition & 0 deletions Sources/Protocols/TaxonomiesParserProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ protocol TaxonomiesParserProtocol {
func parseAdditives(data: [String: Any]) -> [Additive]
func parseIngredientsAnalysis(data: [String: Any]) -> [IngredientsAnalysis]
func parseIngredientsAnalysisConfig(data: [String: Any]) -> [IngredientsAnalysisConfig]
func parseLabels(data: [String: Any]) -> [Label]
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,15 @@ class ProductDetailViewController: ButtonBarPagerTabStripViewController, DataMan
return NSAttributedString(string: categoryTag)
}), label: InfoRowKey.categories.localizedString)

createFormRow(with: &rows, item: product.labels, label: InfoRowKey.labels.localizedString)
createFormRow(with: &rows, item: product.labelsTags?.map({ (labelTag: String) -> NSAttributedString in
if let label = dataManager.label(forTag: labelTag) {
if let name = Tag.choose(inTags: Array(label.names)) {
return NSAttributedString(string: name.value, attributes: [NSAttributedString.Key.link : OFFUrlsHelper.url(forLabel: label)])
}
}
return NSAttributedString(string: labelTag)
}), label: InfoRowKey.labels.localizedString)

createFormRow(with: &rows, item: product.citiesTags, label: InfoRowKey.citiesTags.localizedString)

createFormRow(with: &rows, item: product.embCodesTags?.map({ (tag: String) -> NSAttributedString in
Expand Down
7 changes: 7 additions & 0 deletions Tests/Models/PersistenceManagerMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,13 @@ class PersistenceManagerMock: PersistenceManagerProtocol {
return nil
}

func save(labels: [Label]) {
}

func label(forCode: String) -> Label? {
return nil
}

func save(offlineProducts: [RealmOfflineProduct]) {
}

Expand Down