From 1f30bb2db5d16c1cff95437006db64e6060ae3d2 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 28 Feb 2024 13:01:20 +0100 Subject: [PATCH 01/88] fix(translations): sync translations from transifex (master) Automatically merged. --- i18n/cs.po | 11 +++++++--- i18n/es.po | 38 +++++++++++++++++++--------------- i18n/es_419.po | 41 ++++++++++++++++++++++++++---------- i18n/fr.po | 28 ++++++++++++++----------- i18n/id.po | 18 +++++++++------- i18n/nl.po | 13 ++++++++---- i18n/pt.po | 28 ++++++++++++++----------- i18n/pt_BR.po | 56 ++++++++++++++++++++++++++++++++++---------------- i18n/vi.po | 14 ++++++++----- i18n/zh.po | 18 ++++++++-------- 10 files changed, 168 insertions(+), 97 deletions(-) diff --git a/i18n/cs.po b/i18n/cs.po index 5d74aade2..44dc10d9f 100644 --- a/i18n/cs.po +++ b/i18n/cs.po @@ -1,13 +1,14 @@ # # Translators: # Jiří Podhorecký, 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-07-06T08:30:33.216Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Jiří Podhorecký, 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Czech (https://app.transifex.com/hisp-uio/teams/100509/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -385,6 +386,10 @@ msgstr "Skrýt interpretaci" msgid "Write an interpretation" msgstr "Napsat interpretaci" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "Vložit interpretaci" @@ -638,7 +643,7 @@ msgstr[2] "{{count}} skupin" msgstr[3] "{{count}} skupin" msgid "Selected: {{commaSeparatedListOfOrganisationUnits}}" -msgstr "Vybráno: {{commaSeparatedListOfOfOrganisationUnits}}" +msgstr "Vybráno: {{commaSeparatedListOfOrganisationUnits}}" msgid "Nothing selected" msgstr "Není vybráno nic" diff --git a/i18n/es.po b/i18n/es.po index 2e622029d..369f05475 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -6,17 +6,17 @@ # Gabriela Rodriguez , 2022 # Viktor Varland , 2022 # Marta Vila , 2023 -# Philip Larsen Donnelly, 2023 # Janeth Cruz, 2023 # Sergio Valenzuela , 2023 -# Enzo Nicolas Rossi , 2023 +# Enzo Nicolas Rossi , 2024 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-07-06T08:30:33.216Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Enzo Nicolas Rossi , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Spanish (https://app.transifex.com/hisp-uio/teams/100509/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -227,10 +227,10 @@ msgid "All metrics" msgstr "Todas las métricas" msgid "Move to {{axisName}}" -msgstr "Mover a {{Nombredeleje}}" +msgstr "Mover a {{axisName}}" msgid "Add to {{axisName}}" -msgstr "Añadir a {{Nombredeleje}}" +msgstr "Añadir a {{axisName}}" msgid "Not available for {{visualizationType}}" msgstr "No disponible para {{visualizationType}}" @@ -399,6 +399,12 @@ msgstr "Ocultar interpretación" msgid "Write an interpretation" msgstr "Escribir una interpretación" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" +"Otras personas que vean esta interpretación en el futuro podrían ver más " +"datos." + msgid "Post interpretation" msgstr "Interpretación posterior" @@ -636,15 +642,15 @@ msgstr[2] "{{count}} unidades organizativas" msgid "{{count}} levels" msgid_plural "{{count}} levels" -msgstr[0] "{{contar}} nivel" -msgstr[1] "{{contar}} niveles" -msgstr[2] "{{contar}} niveles" +msgstr[0] "{{count}} nivel" +msgstr[1] "{{count}} niveles" +msgstr[2] "{{count}} niveles" msgid "{{count}} groups" msgid_plural "{{count}} groups" -msgstr[0] "{{contar}} grupo" -msgstr[1] "{{contar}} grupos" -msgstr[2] "{{contar}} grupos" +msgstr[0] "{{count}} grupo" +msgstr[1] "{{count}} grupos" +msgstr[2] "{{count}} grupos" msgid "Selected: {{commaSeparatedListOfOrganisationUnits}}" msgstr "Seleccionado: {{commaSeparatedListOfOrganisationUnits}}" @@ -653,7 +659,7 @@ msgid "Nothing selected" msgstr "Nada seleccionado" msgid "User organisation unit" -msgstr "Usuario de unidad organizativa" +msgstr "Unidad organizativa del usuario" msgid "User sub-units" msgstr "Subunidades de usuario" @@ -1223,9 +1229,9 @@ msgstr "Eje {{axisId}}" msgid "{{count}} items" msgid_plural "{{count}} items" -msgstr[0] "{{contar}} elemento" -msgstr[1] "{{contar}} artículos" -msgstr[2] "{{contar}} artículos" +msgstr[0] "{{count}} elemento" +msgstr[1] "{{count}} artículos" +msgstr[2] "{{count}} artículos" msgid "Reset zoom" msgstr "Restablecer zoom" diff --git a/i18n/es_419.po b/i18n/es_419.po index 397bfb887..e3a15ca3c 100644 --- a/i18n/es_419.po +++ b/i18n/es_419.po @@ -1,14 +1,14 @@ # # Translators: # Jaime Bosque , 2022 -# Enzo Nicolas Rossi , 2023 +# Enzo Nicolas Rossi , 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Enzo Nicolas Rossi , 2023\n" +"Last-Translator: Enzo Nicolas Rossi , 2024\n" "Language-Team: Spanish (Latin America) (https://app.transifex.com/hisp-uio/teams/100509/es_419/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -168,7 +168,7 @@ msgid "Search by data item name" msgstr "" msgid "No items selected" -msgstr "" +msgstr "No hay elementos seleccionados" msgid "Selected Items" msgstr "" @@ -213,7 +213,7 @@ msgid "Move to {{axisName}}" msgstr "" msgid "Add to {{axisName}}" -msgstr "" +msgstr "Añadir a {{axisName}}" msgid "Not available for {{visualizationType}}" msgstr "" @@ -225,7 +225,7 @@ msgid "Add Assigned Categories" msgstr "" msgid "Remove" -msgstr "" +msgstr "Eliminar" msgid "Filter dimensions" msgstr "" @@ -347,6 +347,9 @@ msgstr "" msgid "Post reply" msgstr "" +msgid "Delete failed" +msgstr "" + msgid "Could not update comment" msgstr "" @@ -373,19 +376,32 @@ msgstr "" msgid "Write an interpretation" msgstr "" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" msgid "Interpretations" msgstr "" -msgid "Unlike" +msgid "Reply" msgstr "" -msgid "Like" +msgid "{{count}} replies" +msgid_plural "{{count}} replies" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "View replies" msgstr "" -msgid "Reply" +msgid "Unlike" +msgstr "" + +msgid "Like" msgstr "" msgid "Share" @@ -434,7 +450,7 @@ msgid "Searching for \"{{- searchText}}\"" msgstr "" msgid "No results found" -msgstr "" +msgstr "No se han encontrado resultados" msgid "Not available offline" msgstr "" @@ -845,6 +861,9 @@ msgstr "" msgid "Years" msgstr "Años" +msgid "Interpretations and details" +msgstr "" + msgid "Translating to" msgstr "" @@ -906,7 +925,7 @@ msgid "Expected reports" msgstr "" msgid "Program" -msgstr "" +msgstr "Programa" msgid "Select a program" msgstr "" diff --git a/i18n/fr.po b/i18n/fr.po index 7b8e71382..96d50b43d 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -6,16 +6,16 @@ # tx_e2f_fr r25 , 2021 # Karoline Tufte Lien , 2022 # Edem Kossi , 2022 -# phil_dhis2, 2023 # Yao Selom SAKA (HISP WCA) , 2023 # Yayra Gomado , 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-07-06T08:30:33.216Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Yayra Gomado , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: French (https://app.transifex.com/hisp-uio/teams/100509/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -217,13 +217,13 @@ msgid "All metrics" msgstr "Tous les éléments" msgid "Move to {{axisName}}" -msgstr "Déménager à {{nomdel'axe}}" +msgstr "Déménager à {{axisName}}" msgid "Add to {{axisName}}" -msgstr "Ajouter à {{nomdel'axe}} " +msgstr "Ajouter à {{axisName}} " msgid "Not available for {{visualizationType}}" -msgstr "Non disponible pour {{Typedevisualisation}}" +msgstr "Non disponible pour {{visualizationType}}" msgid "Remove Assigned Categories" msgstr "Supprimer les Catégories Attribuées" @@ -383,6 +383,10 @@ msgstr "" msgid "Write an interpretation" msgstr "Ecrire une interprétation" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" @@ -1063,19 +1067,19 @@ msgid "Bottom" msgstr "Bas" msgid "{{dynamicOuNames}} and {{lastOuName}}" -msgstr "{{NomsOudynamiques}} et {{dernièreOuNom}}" +msgstr "{{dynamicOuNames}} et {{lastOuName}}" msgid "{{allDynamicOuNames}} levels" -msgstr "{{touslesnomsOudynamiques}} niveaux" +msgstr "{{allDynamicOuNames}} niveaux" msgid "{{allDynamicOuNames}} groups" -msgstr "{{touslesnomsOudynamiques}} groupes" +msgstr "{{allDynamicOuNames}} groupes" msgid "{{allDynamicOuNames}} levels in {{staticOuNames}}" -msgstr "{{touslesnomsOudynamiques}} niveaux dans {{NomsOustatiques}} " +msgstr "{{allDynamicOuNames}} niveaux dans {{staticOuNames}} " msgid "{{allDynamicOuNames}} groups in {{staticOuNames}}" -msgstr "{{touslesnomsOudynamiques}} groupes dans {{NomsOustatiques}}" +msgstr "{{allDynamicOuNames}} groupes dans {{staticOuNames}}" msgid "{{percentage}}% of total x values" msgstr "{{percentage}}% de nombre total de X valeurs" @@ -1183,7 +1187,7 @@ msgid "Base" msgstr "Base" msgid "Axis {{axisId}}" -msgstr "Axe {{{axisId}}" +msgstr "Axe {{axisId}}" msgid "{{count}} items" msgid_plural "{{count}} items" diff --git a/i18n/id.po b/i18n/id.po index 592793bf1..fb1292fe5 100644 --- a/i18n/id.po +++ b/i18n/id.po @@ -1,19 +1,19 @@ # # Translators: -# phil_dhis2, 2021 # Carwoto Sa'an , 2022 # Guardian Sanjaya , 2022 # Aprisa Chrysantina , 2022 # Viktor Varland , 2023 # Yusuf Setiawan , 2023 # Untoro Dwi Raharjo , 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-07-06T08:30:33.216Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Untoro Dwi Raharjo , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Indonesian (https://app.transifex.com/hisp-uio/teams/100509/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -379,6 +379,10 @@ msgstr "" msgid "Write an interpretation" msgstr "" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" @@ -1085,10 +1089,10 @@ msgid "{{percentage}}% of total y values" msgstr "{{percentage}}% dari total nilai y" msgid "{{thresholdFactor}} × IQR Q1" -msgstr "{{faktor ambang}} × IQR Q1" +msgstr "{{thresholdFactor}} × IQR Q1" msgid "{{thresholdFactor}} × IQR Q3" -msgstr "{{Faktor ambang}} × IQR Q3" +msgstr "{{thresholdFactor}} × IQR Q3" msgid "{{thresholdFactor}} × Modified Z-score low" msgstr "{{thresholdFactor}} × Modifikasi Z-score rendah" @@ -1100,7 +1104,7 @@ msgid "{{thresholdFactor}} × Z-score low" msgstr "{{thresholdFactor}} × Z-skor rendah" msgid "{{thresholdFactor}} × Z-score high" -msgstr "{{ThresholdFactor}} × Z-skor tinggi" +msgstr "{{thresholdFactor}} × Z-skor tinggi" msgid "Data" msgstr "Data" @@ -1172,7 +1176,7 @@ msgid "No legend for this series" msgstr "Tidak ada legenda untuk seri ini" msgid "and {{amount}} more..." -msgstr "dan {{jumlah}} lainnya..." +msgstr "dan {{amount}} lainnya..." msgid "Linear Regression" msgstr "Regresi linier" diff --git a/i18n/nl.po b/i18n/nl.po index 1c9c354f1..52e67a6f6 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -5,13 +5,14 @@ # Rica Zamora Duchateau, 2022 # Charel van den Elsen, 2023 # Enzo Nicolas Rossi , 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-07-06T08:30:33.216Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Enzo Nicolas Rossi , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Dutch (https://app.transifex.com/hisp-uio/teams/100509/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -204,7 +205,7 @@ msgid "No program indicators found for \"{{- searchTerm}}\"" msgstr "Geen programma-indicatoren gevonden voor \"{{- searchTerm}}\"" msgid "Nothing found for \"{{- searchTerm}}\"" -msgstr "Niets gevonden voor \"{{- zoekTerm}}\"" +msgstr "Niets gevonden voor \"{{- searchTerm}}\"" msgid "Calculation" msgstr "Berekening" @@ -216,7 +217,7 @@ msgid "All metrics" msgstr "Alle statistieken" msgid "Move to {{axisName}}" -msgstr "Ga naar {{axisNaam}}" +msgstr "Ga naar {{axisName}}" msgid "Add to {{axisName}}" msgstr "Voeg toe aan {{axisName}}" @@ -388,6 +389,10 @@ msgstr "Interpretatie verbergen" msgid "Write an interpretation" msgstr "Schrijf een interpretatie" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "Interpretatie posten" diff --git a/i18n/pt.po b/i18n/pt.po index aee7e4b4d..05e81fb21 100644 --- a/i18n/pt.po +++ b/i18n/pt.po @@ -4,16 +4,16 @@ # Gabriela Rodriguez , 2022 # Viktor Varland , 2022 # Fernando Jorge Bade, 2022 -# phil_dhis2, 2023 # David Júnior , 2023 # Ge Joao , 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-07-06T08:30:33.216Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Ge Joao , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Portuguese (https://app.transifex.com/hisp-uio/teams/100509/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -215,13 +215,13 @@ msgid "All metrics" msgstr "Todas métricas" msgid "Move to {{axisName}}" -msgstr "Mover para {{Nomedoeixo}}" +msgstr "Mover para {{axisName}}" msgid "Add to {{axisName}}" -msgstr "Adicionar à {{Nomedoeixo}}" +msgstr "Adicionar à {{axisName}}" msgid "Not available for {{visualizationType}}" -msgstr "Não disponível para {{Tipodevisualização}}" +msgstr "Não disponível para {{visualizationType}}" msgid "Remove Assigned Categories" msgstr "Remover categorias atribuídas" @@ -381,6 +381,10 @@ msgstr "" msgid "Write an interpretation" msgstr "Escreva uma interpretação" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" @@ -1061,19 +1065,19 @@ msgid "Bottom" msgstr "Inferior" msgid "{{dynamicOuNames}} and {{lastOuName}}" -msgstr "{{nomesdinâmicosOu}} e {{últimoNomeOu}}" +msgstr "{{dynamicOuNames}} e {{lastOuName}}" msgid "{{allDynamicOuNames}} levels" -msgstr "{{tudoDynamicOuNomes}} níveis" +msgstr "{{allDynamicOuNames}} níveis" msgid "{{allDynamicOuNames}} groups" -msgstr "{{tudoDynamicOuNomes}} grupos" +msgstr "{{allDynamicOuNames}} grupos" msgid "{{allDynamicOuNames}} levels in {{staticOuNames}}" -msgstr "{{tudoDynamicOuNomes}} níveis em {{NomesestáticosOu}}" +msgstr "{{allDynamicOuNames}} níveis em {{staticOuNames}}" msgid "{{allDynamicOuNames}} groups in {{staticOuNames}}" -msgstr "{{tudoDynamicOuNomes}} grupos em {{NomesestáticosOu}}" +msgstr "{{allDynamicOuNames}} grupos em {{staticOuNames}}" msgid "{{percentage}}% of total x values" msgstr "" @@ -1181,7 +1185,7 @@ msgid "Base" msgstr "Base" msgid "Axis {{axisId}}" -msgstr "Eixo {{eixoId}}" +msgstr "Eixo {{axisId}}" msgid "{{count}} items" msgid_plural "{{count}} items" diff --git a/i18n/pt_BR.po b/i18n/pt_BR.po index 5d67e5a44..f61f34035 100644 --- a/i18n/pt_BR.po +++ b/i18n/pt_BR.po @@ -1,14 +1,15 @@ # # Translators: # Viktor Varland , 2021 -# phil_dhis2, 2023 +# Philip Larsen Donnelly, 2023 +# Thiago Rocha, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-04-18T08:41:27.838Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: phil_dhis2, 2023\n" +"Last-Translator: Thiago Rocha, 2024\n" "Language-Team: Portuguese (Brazil) (https://app.transifex.com/hisp-uio/teams/100509/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -80,7 +81,7 @@ msgid "This app could not retrieve required data." msgstr "" msgid "Network error" -msgstr "" +msgstr "Erro de rede" msgid "Data / Edit calculation" msgstr "" @@ -121,7 +122,7 @@ msgid "" msgstr "" msgid "Yes, delete" -msgstr "" +msgstr "Sim, excluir" msgid "Totals only" msgstr "" @@ -159,7 +160,7 @@ msgid "All types" msgstr "" msgid "Disaggregation" -msgstr "" +msgstr "Desagregação" msgid "No data" msgstr "Não ha dados" @@ -347,6 +348,9 @@ msgstr "" msgid "Post reply" msgstr "" +msgid "Delete failed" +msgstr "Falha na exclusão" + msgid "Could not update comment" msgstr "" @@ -373,20 +377,33 @@ msgstr "" msgid "Write an interpretation" msgstr "" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" msgid "Interpretations" msgstr "Interpretações" -msgid "Unlike" +msgid "Reply" +msgstr "Responder" + +msgid "{{count}} replies" +msgid_plural "{{count}} replies" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "View replies" msgstr "" -msgid "Like" +msgid "Unlike" msgstr "" -msgid "Reply" -msgstr "Responder" +msgid "Like" +msgstr "como" msgid "Share" msgstr "Partilhar" @@ -419,7 +436,7 @@ msgid "Add emoji" msgstr "" msgid "Preview" -msgstr "" +msgstr "Pré-visualização" msgid "Back to write mode" msgstr "" @@ -434,13 +451,13 @@ msgid "Searching for \"{{- searchText}}\"" msgstr "" msgid "No results found" -msgstr "" +msgstr "Nenhum resultado encontrado" msgid "Not available offline" msgstr "" msgid "Created by" -msgstr "" +msgstr "Criado por" msgid "Anyone" msgstr "" @@ -458,7 +475,7 @@ msgid "Filter by name" msgstr "Filtrar por nome" msgid "Created" -msgstr "" +msgstr "Criado" msgid "Last updated" msgstr "Última actualização" @@ -467,13 +484,13 @@ msgid "Type" msgstr "Tipo" msgid "Clear filters" -msgstr "" +msgstr "Limpar filtros" msgid "{{firstItemIndex}}-{{lastItemIndex}} of {{totalNumberOfItems}}" msgstr "" msgid "Open" -msgstr "" +msgstr "Abrir" msgid "Couldn't load items" msgstr "" @@ -615,7 +632,7 @@ msgid "Select a group" msgstr "" msgid "Deselect all" -msgstr "" +msgstr "Desmarcar todos" msgid "Period type" msgstr "Tipo de período" @@ -819,7 +836,7 @@ msgid "Last 10 years" msgstr "" msgid "Days" -msgstr "" +msgstr "Dias" msgid "Weeks" msgstr "" @@ -845,6 +862,9 @@ msgstr "" msgid "Years" msgstr "Anos" +msgid "Interpretations and details" +msgstr "" + msgid "Translating to" msgstr "" diff --git a/i18n/vi.po b/i18n/vi.po index f8457df27..ae58444ca 100644 --- a/i18n/vi.po +++ b/i18n/vi.po @@ -1,15 +1,15 @@ # # Translators: -# phil_dhis2, 2023 +# Mai Nguyen , 2022 # Viktor Varland , 2023 -# Mai Nguyen , 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-07-06T08:30:33.216Z\n" +"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: Mai Nguyen , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Vietnamese (https://app.transifex.com/hisp-uio/teams/100509/vi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -375,6 +375,10 @@ msgstr "" msgid "Write an interpretation" msgstr "Viết diễn giải" +msgid "" +"Other people viewing this interpretation in the future may see more data." +msgstr "" + msgid "Post interpretation" msgstr "" @@ -386,7 +390,7 @@ msgstr "Trả lời" msgid "{{count}} replies" msgid_plural "{{count}} replies" -msgstr[0] "{{đếm}} câu trả lời" +msgstr[0] "{{count}} câu trả lời" msgid "View replies" msgstr "" diff --git a/i18n/zh.po b/i18n/zh.po index 90fabf54a..448d6eb61 100644 --- a/i18n/zh.po +++ b/i18n/zh.po @@ -1,16 +1,16 @@ # # Translators: # Viktor Varland , 2021 -# Philip Larsen Donnelly, 2022 # 晓东 林 <13981924470@126.com>, 2023 # easylin , 2023 +# Philip Larsen Donnelly, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" "PO-Revision-Date: 2020-04-28 22:05+0000\n" -"Last-Translator: easylin , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2024\n" "Language-Team: Chinese (https://app.transifex.com/hisp-uio/teams/100509/zh/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -139,7 +139,7 @@ msgid "Search by data element name" msgstr "按数据元素名称搜索" msgid "No data elements found for \"{{- searchTerm}}\"" -msgstr "找不到“ {{-searchTerm}}”的数据元素" +msgstr "找不到“ {{- searchTerm}}”的数据元素" msgid "No data elements found" msgstr "找不到数据元素" @@ -186,19 +186,19 @@ msgid "No program indicators found" msgstr "找不到项目指标" msgid "No indicators found for \"{{- searchTerm}}\"" -msgstr "找不到“ {{-searchTerm}}”的指标" +msgstr "找不到“ {{- searchTerm}}”的指标" msgid "No data sets found for \"{{- searchTerm}}\"" -msgstr "找不到“ {{-searchTerm}}”的数据集" +msgstr "找不到“ {{- searchTerm}}”的数据集" msgid "No event data items found for \"{{- searchTerm}}\"" -msgstr "找不到“ {{-searchTerm}}”的事件数据项" +msgstr "找不到“ {{- searchTerm}}”的事件数据项" msgid "No program indicators found for \"{{- searchTerm}}\"" -msgstr "找不到“ {{-searchTerm}}”的计划指标" +msgstr "找不到“ {{- searchTerm}}”的计划指标" msgid "Nothing found for \"{{- searchTerm}}\"" -msgstr "找不到“ {{-searchTerm}}”" +msgstr "找不到“ {{- searchTerm}}”" msgid "Calculation" msgstr "计算" @@ -360,7 +360,7 @@ msgid "Update" msgstr "更新" msgid "Viewing interpretation: {{- visualisationName}}" -msgstr "查看注释:{{-visualisationName}}" +msgstr "查看注释:{{- visualisationName}}" msgid "Could not load interpretation" msgstr "无法加载解释" From 89119d75a7548bf3bc56b729f30795700329d8ba Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 28 Feb 2024 12:04:55 +0000 Subject: [PATCH 02/88] chore(release): cut 26.3.1 [skip ci] ## [26.3.1](https://github.com/dhis2/analytics/compare/v26.3.0...v26.3.1) (2024-02-28) ### Bug Fixes * **translations:** sync translations from transifex (master) ([1f30bb2](https://github.com/dhis2/analytics/commit/1f30bb2db5d16c1cff95437006db64e6060ae3d2)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8356c8ff6..6b2dadd7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [26.3.1](https://github.com/dhis2/analytics/compare/v26.3.0...v26.3.1) (2024-02-28) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([1f30bb2](https://github.com/dhis2/analytics/commit/1f30bb2db5d16c1cff95437006db64e6060ae3d2)) + # [26.3.0](https://github.com/dhis2/analytics/compare/v26.2.4...v26.3.0) (2024-01-25) diff --git a/package.json b/package.json index 1062504ed..a6e953aa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.3.0", + "version": "26.3.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 163abf60326a97248daa019c65b4658c78701780 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 4 Mar 2024 14:47:55 +0100 Subject: [PATCH 03/88] feat: update interpretations component so it can be used in dashboard items (#1510) --- i18n/en.pot | 9 ++++-- .../InterpretationThread.js | 18 ++++++++++- .../InterpretationModal/index.js | 1 + .../InterpretationsUnit/InterpretationList.js | 5 +++ .../InterpretationsUnit.js | 3 ++ .../common/Interpretation/Interpretation.js | 32 ++++++++++++++++++- src/index.js | 7 ++-- 7 files changed, 68 insertions(+), 7 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index bfd246658..846edbd29 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -400,12 +400,15 @@ msgstr "Unlike" msgid "Like" msgstr "Like" -msgid "Share" -msgstr "Share" - msgid "See interpretation" msgstr "See interpretation" +msgid "Open in app" +msgstr "Open in app" + +msgid "Share" +msgstr "Share" + msgid "Manage sharing" msgstr "Manage sharing" diff --git a/src/components/Interpretations/InterpretationModal/InterpretationThread.js b/src/components/Interpretations/InterpretationModal/InterpretationThread.js index 03686cd7b..fd7b52e00 100644 --- a/src/components/Interpretations/InterpretationModal/InterpretationThread.js +++ b/src/components/Interpretations/InterpretationModal/InterpretationThread.js @@ -16,6 +16,7 @@ const InterpretationThread = ({ initialFocus, onThreadUpdated, downloadMenuComponent: DownloadMenu, + dashboardRedirectUrl, }) => { const { fromServerDate } = useTimeZoneConversion() const focusRef = useRef() @@ -34,7 +35,12 @@ const InterpretationThread = ({ ) return ( -
+
{moment(fromServerDate(interpretation.created)).format('LLL')} @@ -53,6 +59,7 @@ const InterpretationThread = ({ } onUpdated={() => onThreadUpdated(true)} onDeleted={onInterpretationDeleted} + dashboardRedirectUrl={dashboardRedirectUrl} isInThread={true} />
@@ -83,6 +90,10 @@ const InterpretationThread = ({ scroll-behavior: smooth; } + .dashboard .thread { + overflow-y: hidden; + } + .container { position: relative; overflow: auto; @@ -91,6 +102,10 @@ const InterpretationThread = ({ flex-direction: column; } + .container.dashboard { + max-height: none; + } + .container.fetching::before { content: ''; position: absolute; @@ -151,6 +166,7 @@ InterpretationThread.propTypes = { fetching: PropTypes.bool.isRequired, interpretation: PropTypes.object.isRequired, onInterpretationDeleted: PropTypes.func.isRequired, + dashboardRedirectUrl: PropTypes.string, downloadMenuComponent: PropTypes.oneOfType([ PropTypes.object, PropTypes.func, diff --git a/src/components/Interpretations/InterpretationModal/index.js b/src/components/Interpretations/InterpretationModal/index.js index 8ff513ae2..6e1b43713 100644 --- a/src/components/Interpretations/InterpretationModal/index.js +++ b/src/components/Interpretations/InterpretationModal/index.js @@ -1 +1,2 @@ export { InterpretationModal } from './InterpretationModal.js' +export { InterpretationThread } from './InterpretationThread.js' diff --git a/src/components/Interpretations/InterpretationsUnit/InterpretationList.js b/src/components/Interpretations/InterpretationsUnit/InterpretationList.js index f16d78514..7a63175e1 100644 --- a/src/components/Interpretations/InterpretationsUnit/InterpretationList.js +++ b/src/components/Interpretations/InterpretationsUnit/InterpretationList.js @@ -25,6 +25,7 @@ export const InterpretationList = ({ onReplyIconClick, refresh, disabled, + dashboardRedirectUrl, }) => { const { fromServerDate } = useTimeZoneConversion() const interpretationsByDate = interpretations.reduce( @@ -68,6 +69,9 @@ export const InterpretationList = ({ onDeleted={refresh} onUpdated={refresh} disabled={disabled} + dashboardRedirectUrl={ + dashboardRedirectUrl + } /> ))} @@ -117,5 +121,6 @@ InterpretationList.propTypes = { refresh: PropTypes.func.isRequired, onInterpretationClick: PropTypes.func.isRequired, onReplyIconClick: PropTypes.func.isRequired, + dashboardRedirectUrl: PropTypes.string, disabled: PropTypes.bool, } diff --git a/src/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js b/src/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js index 8acb12d7b..df03d7d41 100644 --- a/src/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +++ b/src/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js @@ -49,6 +49,7 @@ export const InterpretationsUnit = forwardRef( onReplyIconClick, disabled, renderId, + dashboardRedirectUrl, }, ref ) => { @@ -133,6 +134,7 @@ export const InterpretationsUnit = forwardRef( onReplyIconClick={onReplyIconClick} refresh={onCompleteAction} disabled={disabled} + dashboardRedirectUrl={dashboardRedirectUrl} /> )} @@ -194,6 +196,7 @@ InterpretationsUnit.propTypes = { currentUser: PropTypes.object.isRequired, id: PropTypes.string.isRequired, type: PropTypes.string.isRequired, + dashboardRedirectUrl: PropTypes.string, disabled: PropTypes.bool, renderId: PropTypes.number, visualizationHasTimeDimension: PropTypes.bool, diff --git a/src/components/Interpretations/common/Interpretation/Interpretation.js b/src/components/Interpretations/common/Interpretation/Interpretation.js index cd6965dd9..caf15a273 100644 --- a/src/components/Interpretations/common/Interpretation/Interpretation.js +++ b/src/components/Interpretations/common/Interpretation/Interpretation.js @@ -6,6 +6,8 @@ import { IconShare16, IconThumbUp16, IconEdit16, + IconLaunch16, + IconView16, } from '@dhis2/ui' import PropTypes from 'prop-types' import React, { useState } from 'react' @@ -27,6 +29,7 @@ export const Interpretation = ({ onDeleted, disabled, onReplyIconClick, + dashboardRedirectUrl, isInThread, }) => { const [isUpdateMode, setIsUpdateMode] = useState(false) @@ -36,7 +39,9 @@ export const Interpretation = ({ currentUser, onComplete: onUpdated, }) - const shouldShowButton = !!onClick && !disabled + const shouldShowButton = Boolean( + !!onClick && !disabled & !dashboardRedirectUrl + ) const interpretationAccess = getInterpretationAccess( interpretation, @@ -56,6 +61,12 @@ export const Interpretation = ({ } } + // Maps still uses old url style /?id= instead of hash + const getAppInterpretationUrl = () => + dashboardRedirectUrl.includes('?') + ? `${dashboardRedirectUrl}&interpretationId=${interpretation.id}` + : `${dashboardRedirectUrl}?interpretationId=${interpretation.id}` + return isUpdateMode ? ( setIsUpdateMode(false)} @@ -97,6 +108,24 @@ export const Interpretation = ({ dataTest="interpretation-reply-button" viewOnly={isInThread && !interpretationAccess.comment} /> + {dashboardRedirectUrl && !isInThread && ( + onClick(interpretation.id)} + dataTest="interpretation-view-button" + /> + )} + {dashboardRedirectUrl && ( + + window.open(getAppInterpretationUrl(), '_blank') + } + dataTest="interpretation-launch-in-app-button" + /> + )} {interpretationAccess.share && ( Date: Mon, 4 Mar 2024 13:55:08 +0000 Subject: [PATCH 04/88] chore(release): cut 26.4.0 [skip ci] # [26.4.0](https://github.com/dhis2/analytics/compare/v26.3.1...v26.4.0) (2024-03-04) ### Features * update interpretations component so it can be used in dashboard items ([#1510](https://github.com/dhis2/analytics/issues/1510)) ([163abf6](https://github.com/dhis2/analytics/commit/163abf60326a97248daa019c65b4658c78701780)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b2dadd7b..1b3c67239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [26.4.0](https://github.com/dhis2/analytics/compare/v26.3.1...v26.4.0) (2024-03-04) + + +### Features + +* update interpretations component so it can be used in dashboard items ([#1510](https://github.com/dhis2/analytics/issues/1510)) ([163abf6](https://github.com/dhis2/analytics/commit/163abf60326a97248daa019c65b4658c78701780)) + ## [26.3.1](https://github.com/dhis2/analytics/compare/v26.3.0...v26.3.1) (2024-02-28) diff --git a/package.json b/package.json index a6e953aa4..f45c63d43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.3.1", + "version": "26.4.0", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From 5a91391272022aab14081ba65e0b103693e01daa Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Tue, 5 Mar 2024 12:57:59 +0100 Subject: [PATCH 05/88] fix: use translated title and subtitle when available (DHIS2-16216) (#1597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan Henrik Øverland --- .../config/adapters/dhis_highcharts/subtitle/index.js | 7 +++++-- .../config/adapters/dhis_highcharts/title/index.js | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/visualizations/config/adapters/dhis_highcharts/subtitle/index.js b/src/visualizations/config/adapters/dhis_highcharts/subtitle/index.js index 585842325..9d2dc1bc7 100644 --- a/src/visualizations/config/adapters/dhis_highcharts/subtitle/index.js +++ b/src/visualizations/config/adapters/dhis_highcharts/subtitle/index.js @@ -50,8 +50,11 @@ export default function (series, layout, metaData, dashboard) { } // DHIS2-578: allow for optional custom subtitle - if (isString(layout.subtitle)) { - subtitle.text = layout.subtitle + const customSubtitle = + (layout.subtitle && layout.displaySubtitle) || layout.subtitle + + if (isString(customSubtitle) && customSubtitle.length) { + subtitle.text = customSubtitle } else { const filterTitle = getFilterText(layout.filters, metaData) diff --git a/src/visualizations/config/adapters/dhis_highcharts/title/index.js b/src/visualizations/config/adapters/dhis_highcharts/title/index.js index 3a42cb5f7..e4e4f1a4a 100644 --- a/src/visualizations/config/adapters/dhis_highcharts/title/index.js +++ b/src/visualizations/config/adapters/dhis_highcharts/title/index.js @@ -55,8 +55,10 @@ export default function (layout, metaData, dashboard) { return title } - if (isString(layout.title) && layout.title.length) { - title.text = layout.title + const customTitle = (layout.title && layout.displayTitle) || layout.title + + if (isString(customTitle) && customTitle.length) { + title.text = customTitle } else { switch (layout.type) { case VIS_TYPE_GAUGE: From bb32565960a5f813aaac594b61edc2f9e53d6a4d Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 5 Mar 2024 12:58:27 +0100 Subject: [PATCH 06/88] fix: interpretations panel renders with flashing when liking or unliking an interpretation (#1600) * fix: rendering a spinner based on fetching causes flash * fix: dont use loader and mask when like/unliking * fix: when liking just update the object instead of new request * fix: unneeded changes * fix: unneeded changes * fix: more cleanup * fix: set dirty so parent component refreshes interpretations list --------- Co-authored-by: Edoardo Sabadelli --- .../InterpretationModal.js | 14 ++++++++++--- .../InterpretationThread.js | 3 +++ .../InterpretationsUnit/InterpretationList.js | 3 +++ .../InterpretationsUnit.js | 20 +++++++++++++----- .../common/Interpretation/Interpretation.js | 8 ++++++- .../common/Interpretation/useLike.js | 21 +++++++++++++++++-- 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/components/Interpretations/InterpretationModal/InterpretationModal.js b/src/components/Interpretations/InterpretationModal/InterpretationModal.js index 5cc5050bd..1be7ae74f 100644 --- a/src/components/Interpretations/InterpretationModal/InterpretationModal.js +++ b/src/components/Interpretations/InterpretationModal/InterpretationModal.js @@ -83,7 +83,7 @@ const InterpretationModal = ({ }) const interpretation = data?.interpretation const shouldRenderModalContent = !error && interpretation - const shouldCssHideModal = loading || isVisualizationLoading + const loadingInProgress = loading || isVisualizationLoading const handleClose = () => { if (isDirty) { onInterpretationUpdate() @@ -97,6 +97,13 @@ const InterpretationModal = ({ } refetch({ id: interpretationId }) } + + const onLikeToggled = ({ likedBy }) => { + setIsDirty(true) + interpretation.likedBy = likedBy + interpretation.likes = likedBy.length + } + const onInterpretationDeleted = () => { setIsDirty(false) onInterpretationUpdate() @@ -111,7 +118,7 @@ const InterpretationModal = ({ return ( <> - {shouldCssHideModal && ( + {loadingInProgress && ( @@ -122,7 +129,7 @@ const InterpretationModal = ({ fluid onClose={handleClose} className={cx(modalCSS.className, { - hidden: shouldCssHideModal, + hidden: loadingInProgress, })} dataTest="interpretation-modal" > @@ -183,6 +190,7 @@ const InterpretationModal = ({ downloadMenuComponent={ downloadMenuComponent } + onLikeToggled={onLikeToggled} />
diff --git a/src/components/Interpretations/InterpretationModal/InterpretationThread.js b/src/components/Interpretations/InterpretationModal/InterpretationThread.js index fd7b52e00..eb5a3a6d3 100644 --- a/src/components/Interpretations/InterpretationModal/InterpretationThread.js +++ b/src/components/Interpretations/InterpretationModal/InterpretationThread.js @@ -13,6 +13,7 @@ const InterpretationThread = ({ fetching, interpretation, onInterpretationDeleted, + onLikeToggled, initialFocus, onThreadUpdated, downloadMenuComponent: DownloadMenu, @@ -52,6 +53,7 @@ const InterpretationThread = ({ focusRef.current?.focus() @@ -166,6 +168,7 @@ InterpretationThread.propTypes = { fetching: PropTypes.bool.isRequired, interpretation: PropTypes.object.isRequired, onInterpretationDeleted: PropTypes.func.isRequired, + onLikeToggled: PropTypes.func.isRequired, dashboardRedirectUrl: PropTypes.string, downloadMenuComponent: PropTypes.oneOfType([ PropTypes.object, diff --git a/src/components/Interpretations/InterpretationsUnit/InterpretationList.js b/src/components/Interpretations/InterpretationsUnit/InterpretationList.js index 7a63175e1..99c806aaa 100644 --- a/src/components/Interpretations/InterpretationsUnit/InterpretationList.js +++ b/src/components/Interpretations/InterpretationsUnit/InterpretationList.js @@ -22,6 +22,7 @@ export const InterpretationList = ({ currentUser, interpretations, onInterpretationClick, + onLikeToggled, onReplyIconClick, refresh, disabled, @@ -65,6 +66,7 @@ export const InterpretationList = ({ interpretation={interpretation} currentUser={currentUser} onClick={onInterpretationClick} + onLikeToggled={onLikeToggled} onReplyIconClick={onReplyIconClick} onDeleted={refresh} onUpdated={refresh} @@ -120,6 +122,7 @@ InterpretationList.propTypes = { interpretations: PropTypes.array.isRequired, refresh: PropTypes.func.isRequired, onInterpretationClick: PropTypes.func.isRequired, + onLikeToggled: PropTypes.func.isRequired, onReplyIconClick: PropTypes.func.isRequired, dashboardRedirectUrl: PropTypes.string, disabled: PropTypes.bool, diff --git a/src/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js b/src/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js index df03d7d41..33311fceb 100644 --- a/src/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +++ b/src/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js @@ -54,13 +54,16 @@ export const InterpretationsUnit = forwardRef( ref ) => { const [isExpanded, setIsExpanded] = useState(true) + const [interpretations, setInterpretations] = useState([]) const showNoTimeDimensionHelpText = type === 'eventVisualization' && !visualizationHasTimeDimension - const { data, loading, fetching, refetch } = useDataQuery( + const { loading, fetching, refetch } = useDataQuery( interpretationsQuery, { lazy: true, + onComplete: (data) => + setInterpretations(data.interpretations.interpretations), } ) @@ -82,6 +85,14 @@ export const InterpretationsUnit = forwardRef( } }, [type, id, renderId, refetch]) + const onLikeToggled = ({ id, likedBy }) => { + const interpretation = interpretations.find( + (interp) => interp.id === id + ) + interpretation.likedBy = likedBy + interpretation.likes = likedBy.length + } + return (
)} - {data && ( + {interpretations && ( <> { const [isUpdateMode, setIsUpdateMode] = useState(false) const [showSharingDialog, setShowSharingDialog] = useState(false) const { toggleLike, isLikedByCurrentUser, toggleLikeInProgress } = useLike({ interpretation, currentUser, - onComplete: onUpdated, + onComplete: (likedBy) => + onLikeToggled({ + id: interpretation.id, + likedBy, + }), }) const shouldShowButton = Boolean( !!onClick && !disabled & !dashboardRedirectUrl @@ -180,6 +185,7 @@ Interpretation.propTypes = { currentUser: PropTypes.object.isRequired, interpretation: PropTypes.object.isRequired, onDeleted: PropTypes.func.isRequired, + onLikeToggled: PropTypes.func.isRequired, onReplyIconClick: PropTypes.func.isRequired, onUpdated: PropTypes.func.isRequired, dashboardRedirectUrl: PropTypes.string, diff --git a/src/components/Interpretations/common/Interpretation/useLike.js b/src/components/Interpretations/common/Interpretation/useLike.js index 38bc5ce2f..f412b2059 100644 --- a/src/components/Interpretations/common/Interpretation/useLike.js +++ b/src/components/Interpretations/common/Interpretation/useLike.js @@ -7,11 +7,28 @@ const useLike = ({ interpretation, currentUser, onComplete }) => { const unlikeMutationRef = useRef({ resource, type: 'delete' }) const [like, { loading: likeLoading }] = useDataMutation( likeMutationRef.current, - { onComplete } + { + onComplete: () => { + const newLikedBy = interpretation.likedBy.concat({ + id: currentUser.id, + }) + setIsLikedByCurrentUser(true) + onComplete(newLikedBy) + }, + } ) const [unlike, { loading: unlikeLoading }] = useDataMutation( unlikeMutationRef.current, - { onComplete } + { + onComplete: () => { + const newLikedBy = interpretation.likedBy.filter( + (lb) => lb.id !== currentUser.id + ) + + setIsLikedByCurrentUser(false) + onComplete(newLikedBy) + }, + } ) const [isLikedByCurrentUser, setIsLikedByCurrentUser] = useState(false) const toggleLike = () => { From c463f66e06474dc9df0c7a24590660b0f6d7c1c0 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 5 Mar 2024 12:03:48 +0000 Subject: [PATCH 07/88] chore(release): cut 26.4.1 [skip ci] ## [26.4.1](https://github.com/dhis2/analytics/compare/v26.4.0...v26.4.1) (2024-03-05) ### Bug Fixes * interpretations panel renders with flashing when liking or unliking an interpretation ([#1600](https://github.com/dhis2/analytics/issues/1600)) ([bb32565](https://github.com/dhis2/analytics/commit/bb32565960a5f813aaac594b61edc2f9e53d6a4d)) * use translated title and subtitle when available (DHIS2-16216) ([#1597](https://github.com/dhis2/analytics/issues/1597)) ([5a91391](https://github.com/dhis2/analytics/commit/5a91391272022aab14081ba65e0b103693e01daa)) --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3c67239..9d2d2ab92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [26.4.1](https://github.com/dhis2/analytics/compare/v26.4.0...v26.4.1) (2024-03-05) + + +### Bug Fixes + +* interpretations panel renders with flashing when liking or unliking an interpretation ([#1600](https://github.com/dhis2/analytics/issues/1600)) ([bb32565](https://github.com/dhis2/analytics/commit/bb32565960a5f813aaac594b61edc2f9e53d6a4d)) +* use translated title and subtitle when available (DHIS2-16216) ([#1597](https://github.com/dhis2/analytics/issues/1597)) ([5a91391](https://github.com/dhis2/analytics/commit/5a91391272022aab14081ba65e0b103693e01daa)) + # [26.4.0](https://github.com/dhis2/analytics/compare/v26.3.1...v26.4.0) (2024-03-04) diff --git a/package.json b/package.json index f45c63d43..11d35d385 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/analytics", - "version": "26.4.0", + "version": "26.4.1", "main": "./build/cjs/index.js", "module": "./build/es/index.js", "exports": { From d9b1ccd7529adccb8dd1579e881721d685e7cb1d Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Wed, 13 Mar 2024 10:05:49 +0100 Subject: [PATCH 08/88] feat: add support for Outliers table vis type in DV (DHIS2-13858) (#1598) --- i18n/en.pot | 22 +- package.json | 7 +- src/__fixtures__/fixtures.js | 4 + .../json/api/analytics/outlierDetection.json | 213 +++++ src/api/analytics/AnalyticsAggregate.js | 28 +- src/api/analytics/AnalyticsBase.js | 14 +- src/api/analytics/AnalyticsRequestBase.js | 18 +- src/api/analytics/AnalyticsResponse.js | 93 +- src/api/analytics/__tests__/Analytics.spec.js | 6 + .../__tests__/AnalyticsAggregate.spec.js | 40 + .../analytics/__tests__/AnalyticsBase.spec.js | 35 +- src/components/DataDimension/DataDimension.js | 80 +- .../DataDimension/DataTypeSelector.js | 107 ++- src/components/DataDimension/GroupSelector.js | 24 +- src/components/DataDimension/ItemSelector.js | 164 ++-- .../PeriodDimension/PeriodDimension.js | 5 +- .../PeriodDimension/PeriodTransfer.js | 89 +- .../PeriodDimension.spec.js.snap | 2 +- .../__snapshots__/PeriodSelector.spec.js.snap | 13 +- src/components/VisTypeIcon.js | 17 +- src/index.js | 6 + .../getAdaptedUiLayoutByType.spec.js | 27 + src/modules/axis.js | 5 + src/modules/getAdaptedUiLayoutByType.js | 14 + src/modules/layoutTypes.js | 1 + .../layoutUiRules/__tests__/rules.spec.js | 31 +- src/modules/layoutUiRules/index.js | 2 + src/modules/layoutUiRules/rules.js | 24 + src/modules/layoutUiRules/rulesHelper.js | 4 + src/modules/layoutUiRules/rulesUtils.js | 21 +- src/modules/visTypeToLayoutType.js | 3 + src/modules/visTypes.js | 9 +- yarn.lock | 858 +++++++++--------- 33 files changed, 1313 insertions(+), 673 deletions(-) create mode 100644 src/__fixtures__/json/api/analytics/outlierDetection.json diff --git a/i18n/en.pot b/i18n/en.pot index 846edbd29..bb2f03c33 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2023-09-27T14:15:13.876Z\n" -"PO-Revision-Date: 2023-09-27T14:15:13.876Z\n" +"POT-Creation-Date: 2024-01-25T12:05:03.360Z\n" +"PO-Revision-Date: 2024-01-25T12:05:03.360Z\n" msgid "view only" msgstr "view only" @@ -150,6 +150,9 @@ msgstr "Math operators" msgid "Data Type" msgstr "Data Type" +msgid "Only {{dataType}} can be used in {{visType}}" +msgstr "Only {{dataType}} can be used in {{visType}}" + msgid "All types" msgstr "All types" @@ -180,6 +183,9 @@ msgstr "No event data items found" msgid "No program indicators found" msgstr "No program indicators found" +msgid "No calculations found" +msgstr "No calculations found" + msgid "No indicators found for \"{{- searchTerm}}\"" msgstr "No indicators found for \"{{- searchTerm}}\"" @@ -192,6 +198,9 @@ msgstr "No event data items found for \"{{- searchTerm}}\"" msgid "No program indicators found for \"{{- searchTerm}}\"" msgstr "No program indicators found for \"{{- searchTerm}}\"" +msgid "No calculations found for \"{{- searchTerm}}\"" +msgstr "No calculations found for \"{{- searchTerm}}\"" + msgid "Nothing found for \"{{- searchTerm}}\"" msgstr "Nothing found for \"{{- searchTerm}}\"" @@ -657,15 +666,15 @@ msgstr "Select year" msgid "Period" msgstr "Period" +msgid "Selected Periods" +msgstr "Selected Periods" + msgid "Relative periods" msgstr "Relative periods" msgid "Fixed periods" msgstr "Fixed periods" -msgid "Selected Periods" -msgstr "Selected Periods" - msgid "No periods selected" msgstr "No periods selected" @@ -1167,6 +1176,9 @@ msgstr "Scatter" msgid "Single value" msgstr "Single value" +msgid "Outlier table" +msgstr "Outlier table" + msgid "All charts" msgstr "All charts" diff --git a/package.json b/package.json index 11d35d385..50354d34f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@dhis2/cli-app-scripts": "^9.0.1", "@dhis2/cli-style": "^10.4.1", "@dhis2/d2-i18n": "^1.1.0", - "@dhis2/ui": "^8.4.11", + "@dhis2/ui": "^9.2.0", "@sambego/storybook-state": "^2.0.1", "@storybook/addons": "^6.5.16", "@storybook/preset-create-react-app": "^3.1.7", @@ -52,7 +52,7 @@ "peerDependencies": { "@dhis2/app-runtime": "^3", "@dhis2/d2-i18n": "^1.1", - "@dhis2/ui": "^8.2.5", + "@dhis2/ui": "^9.2.0", "prop-types": "^15", "react": "^16.3", "react-dom": "^16.3", @@ -75,6 +75,9 @@ "react-beautiful-dnd": "^10.1.1", "resize-observer-polyfill": "^1.5.1" }, + "resolutions": { + "@dhis2/ui": "^9.2.0" + }, "files": [ "build" ] diff --git a/src/__fixtures__/fixtures.js b/src/__fixtures__/fixtures.js index 4fab49688..b900ea724 100644 --- a/src/__fixtures__/fixtures.js +++ b/src/__fixtures__/fixtures.js @@ -114,6 +114,10 @@ export default (function x() { require('./json/api/analytics/enrollments.json') ) + addFixture( + '/api/analytics/outlierDetection', + require('./json/api/analytics/outlierDetection.json') + ) return { get: getFixture, add: addFixture, diff --git a/src/__fixtures__/json/api/analytics/outlierDetection.json b/src/__fixtures__/json/api/analytics/outlierDetection.json new file mode 100644 index 000000000..014ac1183 --- /dev/null +++ b/src/__fixtures__/json/api/analytics/outlierDetection.json @@ -0,0 +1,213 @@ +{ + "headers": [ + { + "name": "dxname", + "column": "Data name", + "valueType": "TEXT", + "type": "java.lang.String", + "hidden": false, + "meta": false + }, + { + "name": "ouname", + "column": "Organisation unit name", + "valueType": "TEXT", + "type": "java.lang.String", + "hidden": false, + "meta": false + }, + { + "name": "value", + "column": "Value", + "valueType": "NUMBER", + "type": "java.lang.Double", + "hidden": false, + "meta": false + }, + { + "name": "absdev", + "column": "Absolute deviation", + "valueType": "NUMBER", + "type": "java.lang.Double", + "hidden": false, + "meta": false + }, + { + "name": "modifiedzscore", + "column": "Modified zScore", + "valueType": "NUMBER", + "type": "java.lang.Double", + "hidden": false, + "meta": false + }, + { + "name": "median", + "column": "Median", + "valueType": "NUMBER", + "type": "java.lang.Double", + "hidden": false, + "meta": false + }, + { + "name": "lowerbound", + "column": "Lower boundary", + "valueType": "NUMBER", + "type": "java.lang.Double", + "hidden": false, + "meta": false + }, + { + "name": "upperbound", + "column": "Upper boundary", + "valueType": "NUMBER", + "type": "java.lang.Double", + "hidden": false, + "meta": false + } + ], + "metaData": { + "maxResults": 100, + "count": 13, + "orderBy": "mean_abs_dev", + "threshold": 3.0, + "algorithm": "MOD_Z_SCORE" + }, + "rowContext": { + + }, + "rows": [ + [ + "ANC 2nd visit", + "UMC (Urban Centre) Hospital", + "1669.0", + "920.0", + "3.902767295597484", + "749.0", + "-290.93013894085743", + "1788.9301389408574" + ], + [ + "ANC 1st visit", + "Charlotte CHP", + "49.0", + "39.0", + "7.515857142857143", + "10.0", + "-23.948490393535913", + "43.94849039353591" + ], + [ + "ANC 2nd visit", + "Charlotte CHP", + "40.0", + "33.0", + "22.258499999999998", + "7.0", + "-30.86489139031037", + "44.86489139031037" + ], + [ + "ANC 2nd visit", + "Wilberforce CHC", + "56.0", + "24.5", + "3.004590909090909", + "31.5", + "-1.3890179239210454", + "64.38901792392105" + ], + [ + "ANC 1st visit", + "Deep Eye water MCHP", + "40.0", + "16.0", + "3.5973333333333333", + "24.0", + "4.850913859925324", + "43.14908614007467" + ], + [ + "ANC 2nd visit", + "Lion for Lion Clinic", + "30.0", + "16.0", + "3.5973333333333333", + "14.0", + "-5.620896815302167", + "33.62089681530217" + ], + [ + "ANC 2nd visit", + "Deep Eye water MCHP", + "33.0", + "14.5", + "3.9121", + "18.5", + "-3.4245866551686603", + "40.42458665516866" + ], + [ + "ANC 2nd visit", + "Blessed Mokaka East Clinic", + "2.0", + "13.0", + "4.38425", + "15.0", + "-3.417043736713012", + "33.41704373671301" + ], + [ + "ANC 2nd visit", + "Malambay CHP", + "15.0", + "12.0", + "5.396", + "3.0", + "-13.770509831248425", + "19.770509831248425" + ], + [ + "ANC 2nd visit", + "Wellbody MCHP", + "20.0", + "10.0", + "3.3725", + "10.0", + "-10.999999999999996", + "30.999999999999996" + ], + [ + "ANC 1st visit", + "Blessed Mokaka East Clinic", + "26.0", + "10.0", + "4.496666666666667", + "16.0", + "1.6734512181055958", + "30.326548781894402" + ], + [ + "ANC 1st visit", + "Murray Town CHC", + "18.0", + "9.0", + "6.0705", + "9.0", + "-2.43571205496543", + "20.43571205496543" + ], + [ + "ANC 2nd visit", + "Thompson Bay MCHP", + "11.0", + "6.0", + "4.047", + "5.0", + "-2.038266127580332", + "12.038266127580332" + ] + ], + "headerWidth": 8, + "width": 8, + "height": 13 +} diff --git a/src/api/analytics/AnalyticsAggregate.js b/src/api/analytics/AnalyticsAggregate.js index 88adebc81..93de9f8b4 100644 --- a/src/api/analytics/AnalyticsAggregate.js +++ b/src/api/analytics/AnalyticsAggregate.js @@ -68,12 +68,38 @@ class AnalyticsAggregate extends AnalyticsBase { * .withStartDate('2017-10-01') * .withEndDate('2017-10-31'); * - * analytics.aggregate.getDebugSql(req); + * analytics.aggregate.getDebugSql(req) * .then(console.log); */ getDebugSql(req) { return this.fetch(req.withPath('debug/sql')) } + + /** + * @param {!AnalyticsRequest} req Request object + * + * @returns {Promise} Promise that resolves with the SQL statement used to query the database. + * + * @example + * const req = new analytics.request() + * .withParameters({ + * dx: 'fbfJHSPpUQD,cYeuwXTCPkU', + * pe: 'THIS_YEAR', + * ou: 'USER_ORGUNIT,USER_ORGUNIT_CHILDREN', + * headers: 'dxname,pename,ouname,value,absdev,modifiedzscore,median,lowerbound,upperbound', + * algorithm: 'MODIFIED_Z_SCORE', + * maxResults: 100, + * threshold: 3, + orderBy: 'value', + sortOrder: 'desc', + * }); + * + * analytics.aggregate.getOutliersData(req) + * .then(console.log); + */ + getOutliersData(req) { + return this.fetch(req.withPath('outlierDetection')) + } } export default AnalyticsAggregate diff --git a/src/api/analytics/AnalyticsBase.js b/src/api/analytics/AnalyticsBase.js index 41b2fcf62..70d2c2b5b 100644 --- a/src/api/analytics/AnalyticsBase.js +++ b/src/api/analytics/AnalyticsBase.js @@ -7,8 +7,8 @@ const analyticsQuery = { return [path, program].filter(Boolean).join('/') }, params: ({ dimensions, filters, parameters }) => ({ - dimension: dimensions, - filter: filters, + dimension: dimensions.length ? dimensions : undefined, + filter: filters.length ? filters : undefined, ...parameters, }), } @@ -20,8 +20,8 @@ const analyticsDataQuery = { }, params: ({ dimensions, filters, parameters }) => { return { - dimension: dimensions, - filter: filters, + dimension: dimensions.length ? dimensions : undefined, + filter: filters.length ? filters : undefined, ...parameters, skipMeta: true, skipData: false, @@ -35,8 +35,8 @@ const analyticsMetaDataQuery = { return [path, program].filter(Boolean).join('/') }, params: ({ dimensions, filters, parameters }) => ({ - dimension: dimensions, - filter: filters, + dimension: dimensions.length ? dimensions : undefined, + filter: filters.length ? filters : undefined, ...parameters, skipMeta: false, skipData: true, @@ -44,7 +44,7 @@ const analyticsMetaDataQuery = { }), } -const generateDimensionStrings = (dimensions = [], options) => { +export const generateDimensionStrings = (dimensions = [], options) => { if (options && options.sorted) { dimensions = sortBy(dimensions, 'dimension') } diff --git a/src/api/analytics/AnalyticsRequestBase.js b/src/api/analytics/AnalyticsRequestBase.js index 780d3e6cb..2bc24b108 100644 --- a/src/api/analytics/AnalyticsRequestBase.js +++ b/src/api/analytics/AnalyticsRequestBase.js @@ -46,14 +46,14 @@ class AnalyticsRequestBase { // at least 1 dimension is required let dimensions = this.dimensions - if (options && options.sorted) { + if (dimensions.length && options?.sorted) { dimensions = sortBy(dimensions, 'dimension') } const encodedDimensions = dimensions.map(({ dimension, items }) => { if (Array.isArray(items) && items.length) { const encodedItems = items.map(customEncodeURIComponent) - if (options && options.sorted) { + if (options?.sorted) { encodedItems.sort() } @@ -67,9 +67,13 @@ class AnalyticsRequestBase { .filter((e) => !!e) .join('/') - return `${endPoint}.${this.format}?dimension=${encodedDimensions.join( - '&dimension=' - )}` + let url = `${endPoint}.${this.format}` + + if (encodedDimensions.length) { + url += `?dimension=${encodedDimensions.join('&dimension=')}` + } + + return url } /** @@ -88,14 +92,14 @@ class AnalyticsRequestBase { buildQuery(options) { let filters = this.filters - if (options && options.sorted) { + if (filters.length && options?.sorted) { filters = sortBy(filters, 'dimension') } const encodedFilters = filters.map(({ dimension, items }) => { if (Array.isArray(items) && items.length) { const encodedItems = items.map(customEncodeURIComponent) - if (options && options.sorted) { + if (options?.sorted) { encodedItems.sort() } diff --git a/src/api/analytics/AnalyticsResponse.js b/src/api/analytics/AnalyticsResponse.js index a7bcd8014..525393de6 100644 --- a/src/api/analytics/AnalyticsResponse.js +++ b/src/api/analytics/AnalyticsResponse.js @@ -81,7 +81,8 @@ class AnalyticsResponse { } extractHeaders() { - const dimensions = this.response.metaData.dimensions + // some endpoints (ie. outlierDetection) don't return dimensions in metaData + const dimensions = this.response.metaData.dimensions || {} const headers = this.response.headers || [] return headers.map( @@ -125,59 +126,61 @@ class AnalyticsResponse { const { dimensions, items } = metaData - // populate metaData dimensions and items - this.headers - .filter( - (header) => - !DEFAULT_COLLECT_IGNORE_HEADERS.includes(header.name) - ) - .forEach((header) => { - let ids - - // collect row values - if (header.isCollect) { - ids = this.getSortedUniqueRowIdStringsByHeader(header) - dimensions[header.name] = ids - } else { - ids = dimensions[header.name] - } + // some endpoints (ie. outlierDetection) don't return dimensions or items + if (dimensions && items) { + this.headers + .filter( + (header) => + !DEFAULT_COLLECT_IGNORE_HEADERS.includes(header.name) + ) + .forEach((header) => { + let ids + + // collect row values + if (header.isCollect) { + ids = this.getSortedUniqueRowIdStringsByHeader(header) + dimensions[header.name] = ids + } else { + ids = dimensions[header.name] + } - if (header.isPrefix) { - // create prefixed dimensions array - dimensions[header.name] = ids.map((id) => - getPrefixedId(id, header.name) - ) + if (header.isPrefix) { + // create prefixed dimensions array + dimensions[header.name] = ids.map((id) => + getPrefixedId(id, header.name) + ) - // create items - dimensions[header.name].forEach((prefixedId, index) => { - const id = ids[index] - const valueType = header.valueType + // create items + dimensions[header.name].forEach((prefixedId, index) => { + const id = ids[index] + const valueType = header.valueType - const name = getNameByIdsByValueType(id, valueType) + const name = getNameByIdsByValueType(id, valueType) - items[prefixedId] = { name } - }) - } - }) + items[prefixedId] = { name } + }) + } + }) - // for events, add items from 'ouname' - if (this.hasHeader(OUNAME) && this.hasHeader(OU)) { - const ouNameHeaderIndex = this.getHeader(OUNAME).getIndex() - const ouHeaderIndex = this.getHeader(OU).getIndex() - let ouId - let ouName + // for events, add items from 'ouname' + if (this.hasHeader(OUNAME) && this.hasHeader(OU)) { + const ouNameHeaderIndex = this.getHeader(OUNAME).getIndex() + const ouHeaderIndex = this.getHeader(OU).getIndex() + let ouId + let ouName - this.rows.forEach((row) => { - ouId = row[ouHeaderIndex] + this.rows.forEach((row) => { + ouId = row[ouHeaderIndex] - if (items[ouId] === undefined) { - ouName = row[ouNameHeaderIndex] + if (items[ouId] === undefined) { + ouName = row[ouNameHeaderIndex] - items[ouId] = { - name: ouName, + items[ouId] = { + name: ouName, + } } - } - }) + }) + } } return metaData diff --git a/src/api/analytics/__tests__/Analytics.spec.js b/src/api/analytics/__tests__/Analytics.spec.js index 4add9c1b1..17d88344c 100644 --- a/src/api/analytics/__tests__/Analytics.spec.js +++ b/src/api/analytics/__tests__/Analytics.spec.js @@ -1,5 +1,6 @@ import Analytics from '../Analytics.js' import AnalyticsAggregate from '../AnalyticsAggregate.js' +import AnalyticsEnrollments from '../AnalyticsEnrollments.js' import AnalyticsEvents from '../AnalyticsEvents.js' import AnalyticsRequest from '../AnalyticsRequest.js' @@ -9,6 +10,7 @@ describe('Analytics', () => { beforeEach(() => { analytics = new Analytics({ aggregate: new AnalyticsAggregate(), + enrollments: new AnalyticsEnrollments(), events: new AnalyticsEvents(), request: AnalyticsRequest, }) @@ -26,6 +28,10 @@ describe('Analytics', () => { expect(analytics.aggregate).toBeInstanceOf(AnalyticsAggregate) }) + it('should contain an instance of AnalyticsEnrollments', () => { + expect(analytics.enrollments).toBeInstanceOf(AnalyticsEnrollments) + }) + it('should contain an instance of AnalyticsEvents', () => { expect(analytics.events).toBeInstanceOf(AnalyticsEvents) }) diff --git a/src/api/analytics/__tests__/AnalyticsAggregate.spec.js b/src/api/analytics/__tests__/AnalyticsAggregate.spec.js index f7fecba11..4a5d40d54 100644 --- a/src/api/analytics/__tests__/AnalyticsAggregate.spec.js +++ b/src/api/analytics/__tests__/AnalyticsAggregate.spec.js @@ -133,4 +133,44 @@ describe('Analytics.aggregate', () => { expect(data.height).toEqual(0) })) }) + + describe('.getOutliersData', () => { + beforeEach(() => { + aggregate = new AnalyticsAggregate(new DataEngineMock()) + + request = new AnalyticsRequest() + + request.withParameters({ + dx: 'fbfJHSPpUQD,cYeuwXTCPkU', + pe: 'THIS_YEAR', + ou: 'at6UHUQatSo', + headers: + 'dxname,pename,ouname,value,absdev,modifiedzscore,median,lowerbound,upperbound', + algorithm: 'MOD_Z_SCORE', + maxResults: 100, + threshold: 3, + }) + + fixture = fixtures.get('/api/analytics/outlierDetection') + + dataEngineMock.query.mockReturnValue( + Promise.resolve({ data: fixture }) + ) + }) + + it('should be a function', () => { + expect(aggregate.getOutliersData).toBeInstanceOf(Function) + }) + + it('should resolve a promise with data', () => + aggregate.getOutliersData(request).then((data) => { + expect(data.metaData.items).toEqual(fixture.metaData.items) + expect(data.metaData.dimensions).toEqual( + fixture.metaData.dimensions + ) + expect(data.headers).toEqual(fixture.headers) + expect(data.width).toEqual(8) + expect(data.height).toEqual(13) + })) + }) }) diff --git a/src/api/analytics/__tests__/AnalyticsBase.spec.js b/src/api/analytics/__tests__/AnalyticsBase.spec.js index 729eebdb7..c5d3fb1d8 100644 --- a/src/api/analytics/__tests__/AnalyticsBase.spec.js +++ b/src/api/analytics/__tests__/AnalyticsBase.spec.js @@ -1,4 +1,4 @@ -import AnalyticsBase from '../AnalyticsBase.js' +import AnalyticsBase, { generateDimensionStrings } from '../AnalyticsBase.js' let base @@ -19,3 +19,36 @@ describe('constructor', () => { expect(base.dataEngine).toBe(dataEngineMock) }) }) + +describe('generateDimensionString', () => { + const tests = [ + { + input: [ + { + dimension: 'dim2', + items: ['item2', 'item1'], + }, + { + dimension: 'dim1', + items: ['item1'], + }, + ], + output: ['dim2:item2;item1', 'dim1:item1'], + outputSorted: ['dim1:item1', 'dim2:item1;item2'], + }, + ] + + it('should return dimension strings correctly formatted', () => { + tests.forEach(({ input, output }) => { + expect(generateDimensionStrings(input)).toEqual(output) + }) + }) + + it('should return dimension strings correctly formatted and sorted', () => { + tests.forEach(({ input, outputSorted }) => { + expect(generateDimensionStrings(input, { sorted: true })).toEqual( + outputSorted + ) + }) + }) +}) diff --git a/src/components/DataDimension/DataDimension.js b/src/components/DataDimension/DataDimension.js index 52f2c33ea..2ed618b35 100644 --- a/src/components/DataDimension/DataDimension.js +++ b/src/components/DataDimension/DataDimension.js @@ -1,21 +1,46 @@ import { useConfig } from '@dhis2/app-runtime' import PropTypes from 'prop-types' -import React from 'react' +import React, { + createContext, + useContext, + useCallback, + useEffect, + useState, +} from 'react' +import { + dataTypeMap, + DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, +} from '../../modules/dataTypes.js' import { DIMENSION_ID_DATA } from '../../modules/predefinedDimensions.js' import ItemSelector from './ItemSelector.js' +const DataDimensionCtx = createContext({}) + const DataDimension = ({ onSelect, selectedDimensions, displayNameProp, + enabledDataTypes, infoBoxMessage, onCalculationSave, + visType, }) => { const { serverVersion } = useConfig() - const supportsEDI = - `${serverVersion.major}.${serverVersion.minor}.${ - serverVersion.patch || 0 - }` >= '2.40.0' + + const filterDataTypesByVersion = useCallback( + (dataTypes) => + dataTypes.filter( + ({ id }) => + // Calculations only available from 2.40 + id !== DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM || + serverVersion.minor >= 40 + ), + [serverVersion.minor] + ) + + const [dataTypes, setDataTypes] = useState( + filterDataTypesByVersion(enabledDataTypes || Object.values(dataTypeMap)) + ) const onSelectItems = (selectedItem) => onSelect({ @@ -28,23 +53,32 @@ const DataDimension = ({ })), }) + useEffect( + () => + enabledDataTypes && + setDataTypes(filterDataTypesByVersion(enabledDataTypes)), + [enabledDataTypes, filterDataTypesByVersion] + ) + return ( - ({ - value: item.id, - label: item.name, - isActive: item.isActive, - type: item.type, - expression: item.expression, - access: item.access, - }))} - onSelect={onSelectItems} - displayNameProp={displayNameProp} - infoBoxMessage={infoBoxMessage} - dataTest={'data-dimension'} - supportsEDI={supportsEDI} - onEDISave={onCalculationSave} - /> + + ({ + value: item.id, + label: item.name, + isActive: item.isActive, + type: item.type, + expression: item.expression, + access: item.access, + }))} + onSelect={onSelectItems} + displayNameProp={displayNameProp} + infoBoxMessage={infoBoxMessage} + dataTest={'data-dimension'} + dataTypes={dataTypes} + onEDISave={onCalculationSave} + /> + ) } @@ -60,7 +94,9 @@ DataDimension.propTypes = { }) ).isRequired, onSelect: PropTypes.func.isRequired, + enabledDataTypes: PropTypes.array, infoBoxMessage: PropTypes.string, + visType: PropTypes.string, onCalculationSave: PropTypes.func, } @@ -69,4 +105,6 @@ DataDimension.defaultProps = { onSelect: Function.prototype, } +export const useDataDimensionContext = () => useContext(DataDimensionCtx) + export default DataDimension diff --git a/src/components/DataDimension/DataTypeSelector.js b/src/components/DataDimension/DataTypeSelector.js index b5485e9ed..62d273097 100644 --- a/src/components/DataDimension/DataTypeSelector.js +++ b/src/components/DataDimension/DataTypeSelector.js @@ -2,57 +2,88 @@ import { SingleSelectField, SingleSelectOption } from '@dhis2/ui' import PropTypes from 'prop-types' import React from 'react' import i18n from '../../locales/index.js' -import { - DIMENSION_TYPE_ALL, - dataTypeMap as dataTypes, - DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, -} from '../../modules/dataTypes.js' +import { DIMENSION_TYPE_ALL, dataTypeMap } from '../../modules/dataTypes.js' +import { getDisplayNameByVisType } from '../../modules/visTypes.js' +import { useDataDimensionContext } from './DataDimension.js' import styles from './styles/DataTypeSelector.style.js' const DataTypeSelector = ({ currentDataType, + dataTypes, onChange, dataTest, - includeCalculations, -}) => ( -
- onChange(ref.selected)} - dense - > - - {Object.values(dataTypes) - .filter( - (type) => - type.id !== DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM || - includeCalculations - ) - .map((type) => ( +}) => { + const { visType } = useDataDimensionContext() + const label = i18n.t('Data Type') + + return ( +
+ {dataTypes.length === 1 ? ( + onChange(ref.selected)} + dense + disabled={true} + helpText={ + visType + ? i18n.t( + 'Only {{dataType}} can be used in {{visType}}', + { + dataType: + dataTypeMap[ + dataTypes[0].id + ].getName(), + visType: getDisplayNameByVisType(visType), + } + ) + : '' + } + > + {dataTypes.map((type) => ( + + ))} + + ) : ( + onChange(ref.selected)} + dense + > - ))} - - -
-) + {dataTypes.map((type) => ( + + ))} +
+ )} + +
+ ) +} DataTypeSelector.propTypes = { currentDataType: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, dataTest: PropTypes.string, - includeCalculations: PropTypes.bool, + dataTypes: PropTypes.array, } export default DataTypeSelector diff --git a/src/components/DataDimension/GroupSelector.js b/src/components/DataDimension/GroupSelector.js index 7b6173b42..d20fd866c 100644 --- a/src/components/DataDimension/GroupSelector.js +++ b/src/components/DataDimension/GroupSelector.js @@ -28,20 +28,20 @@ const GroupSelector = ({ const defaultGroup = dataTypes[dataType]?.defaultGroup const subGroupType = dataTypes[dataType]?.subGroup - const fetchGroups = async () => { - setIsLoading(true) - const result = await apiFetchGroups( - dataEngine, - dataType, - displayNameProp - ) - setGroups(result) - setIsLoading(false) - } - useEffect(() => { + const fetchGroups = async () => { + setIsLoading(true) + const result = await apiFetchGroups( + dataEngine, + dataType, + displayNameProp + ) + setGroups(result) + setIsLoading(false) + } + fetchGroups() - }, [dataType]) + }, [dataEngine, dataType, displayNameProp]) return (
diff --git a/src/components/DataDimension/ItemSelector.js b/src/components/DataDimension/ItemSelector.js index 6256be9ba..5c36b478a 100644 --- a/src/components/DataDimension/ItemSelector.js +++ b/src/components/DataDimension/ItemSelector.js @@ -6,7 +6,6 @@ import { apiFetchOptions } from '../../api/dimensions.js' import i18n from '../../locales/index.js' import { DATA_SETS_CONSTANTS, REPORTING_RATE } from '../../modules/dataSets.js' import { - dataTypeMap as dataTypes, DIMENSION_TYPE_ALL, DIMENSION_TYPE_DATA_ELEMENT, DIMENSION_TYPE_DATA_SET, @@ -33,14 +32,15 @@ const LeftHeader = ({ searchTerm, setSearchTerm, dataType, + dataTypes, setDataType, group, setGroup, subGroup, setSubGroup, displayNameProp, + dataTest, - supportsEDI, }) => ( <>
@@ -57,20 +57,22 @@ const LeftHeader = ({ currentDataType={dataType} onChange={setDataType} dataTest={`${dataTest}-data-types-select-field`} - includeCalculations={supportsEDI} + dataTypes={dataTypes} /> - {dataTypes[dataType] && - dataType !== DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM && ( - - )} + {![ + DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM, + DIMENSION_TYPE_ALL, + ].includes(dataType) && ( + + )}
@@ -79,6 +81,7 @@ const LeftHeader = ({ LeftHeader.propTypes = { dataTest: PropTypes.string, dataType: PropTypes.string, + dataTypes: PropTypes.array, displayNameProp: PropTypes.string, group: PropTypes.string, searchTerm: PropTypes.string, @@ -87,7 +90,6 @@ LeftHeader.propTypes = { setSearchTerm: PropTypes.func, setSubGroup: PropTypes.func, subGroup: PropTypes.string, - supportsEDI: PropTypes.bool, } const EmptySelection = () => ( @@ -96,23 +98,26 @@ const EmptySelection = () => ( ) -const RightHeader = ({ infoText }) => ( + +const RightHeader = ({ infoBoxMessage }) => ( <>

{i18n.t('Selected Items')}

- {infoText && ( + {infoBoxMessage && (
- {infoText} + {infoBoxMessage}
)} ) + RightHeader.propTypes = { - infoText: PropTypes.string, + infoBoxMessage: PropTypes.string, } + const SourceEmptyPlaceholder = ({ loading, searchTerm, @@ -142,6 +147,9 @@ const SourceEmptyPlaceholder = ({ case DIMENSION_TYPE_PROGRAM_INDICATOR: message = i18n.t('No program indicators found') break + case DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM: + message = i18n.t('No calculations found') + break default: message = i18n.t('No data') break @@ -151,41 +159,43 @@ const SourceEmptyPlaceholder = ({ switch (dataType) { case DIMENSION_TYPE_INDICATOR: message = i18n.t('No indicators found for "{{- searchTerm}}"', { - searchTerm: searchTerm, + searchTerm, }) break case DIMENSION_TYPE_DATA_ELEMENT: message = i18n.t( 'No data elements found for "{{- searchTerm}}"', { - searchTerm: searchTerm, + searchTerm, } ) break case DIMENSION_TYPE_DATA_SET: message = i18n.t('No data sets found for "{{- searchTerm}}"', { - searchTerm: searchTerm, + searchTerm, }) break case DIMENSION_TYPE_EVENT_DATA_ITEM: message = i18n.t( 'No event data items found for "{{- searchTerm}}"', - { - searchTerm: searchTerm, - } + { searchTerm } ) break case DIMENSION_TYPE_PROGRAM_INDICATOR: message = i18n.t( 'No program indicators found for "{{- searchTerm}}"', - { - searchTerm: searchTerm, - } + { searchTerm } + ) + break + case DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM: + message = i18n.t( + 'No calculations found for "{{- searchTerm}}"', + { searchTerm } ) break default: message = i18n.t('Nothing found for "{{- searchTerm}}"', { - searchTerm: searchTerm, + searchTerm, }) break } @@ -218,27 +228,42 @@ const ItemSelector = ({ rightFooter, displayNameProp, infoBoxMessage, + dataTypes, dataTest, - supportsEDI, onEDISave, }) => { const [state, setState] = useState({ searchTerm: '', + dataTypes, filter: { - dataType: DIMENSION_TYPE_ALL, + dataType: + dataTypes.length === 1 ? dataTypes[0].id : DIMENSION_TYPE_ALL, + group: null, + subGroup: + dataTypes.length === 1 && + dataTypes[0].id === DIMENSION_TYPE_DATA_ELEMENT + ? TOTALS + : null, }, options: [], loading: true, nextPage: 1, + supportsEDI: dataTypes + .map(({ id }) => id) + .includes(DIMENSION_TYPE_EXPRESSION_DIMENSION_ITEM), }) const [currentCalculation, setCurrentCalculation] = useState() const dataEngine = useDataEngine() const setSearchTerm = (searchTerm) => setState((state) => ({ ...state, searchTerm })) - const setFilter = (filter) => setState((state) => ({ ...state, filter })) const debouncedSearchTerm = useDebounce(state.searchTerm, 500) + const fetchItems = async (page) => { - setState((state) => ({ ...state, loading: true })) + setState((state) => ({ + ...state, + nextPage: page === 1 ? 1 : state.nextPage, + loading: true, + })) const result = await apiFetchOptions({ dataEngine, nameProp: displayNameProp, @@ -314,13 +339,9 @@ const ItemSelector = ({ } useDidUpdateEffect(() => { - setState((state) => ({ - ...state, - loading: true, - nextPage: 1, - })) fetchItems(1) }, [debouncedSearchTerm, state.filter]) + const onChange = (newSelected) => { onSelect( newSelected.map((value) => { @@ -391,6 +412,39 @@ const ItemSelector = ({ onSelect([...selectedItems.filter((item) => item.value !== id)]) } + const onSetGroup = (group) => + setState((state) => ({ + ...state, + nextPage: 1, + filter: { + ...state.filter, + group, + }, + })) + + const onSetSubGroup = (subGroup) => + setState((state) => ({ + ...state, + nextPage: 1, + filter: { + ...state.filter, + subGroup, + }, + })) + + const onSetDataType = (dataType) => + setState((state) => ({ + ...state, + nextPage: 1, + filter: { + ...state.filter, + dataType, + group: null, + subGroup: + dataType === DIMENSION_TYPE_DATA_ELEMENT ? TOTALS : null, + }, + })) + return ( <> { - setFilter({ - ...state.filter, - dataType, - group: null, - subGroup: - dataType === DIMENSION_TYPE_DATA_ELEMENT - ? TOTALS - : null, - }) - }} + setDataType={onSetDataType} group={state.filter.group} - setGroup={(group) => { - setFilter({ ...state.filter, group }) - }} + setGroup={onSetGroup} subGroup={state.filter.subGroup} - setSubGroup={(subGroup) => { - setFilter({ ...state.filter, subGroup }) - }} + setSubGroup={onSetSubGroup} searchTerm={state.searchTerm} setSearchTerm={setSearchTerm} displayNameProp={displayNameProp} dataTest={`${dataTest}-left-header`} - supportsEDI={supportsEDI} /> } leftFooter={ - supportsEDI ? ( + state.supportsEDI ? (
+const OptionsButton = ({ onClick }) => ( + <> + + + ) OptionsButton.propTypes = { - style: PropTypes.object, onClick: PropTypes.func, } diff --git a/src/components/DimensionsPanel/List/RecommendedIcon.js b/src/components/DimensionsPanel/List/RecommendedIcon.js index 3a8b75dc9..12609d3a7 100644 --- a/src/components/DimensionsPanel/List/RecommendedIcon.js +++ b/src/components/DimensionsPanel/List/RecommendedIcon.js @@ -9,7 +9,6 @@ const RecommendedIcon = ({ isRecommended, dataTest }) =>
diff --git a/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap b/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap index 1c33a04bf..f58b07faa 100644 --- a/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap +++ b/src/components/DimensionsPanel/List/__tests__/__snapshots__/DimensionItem.spec.js.snap @@ -1,405 +1,283 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DimensionItem matches the snapshot 1`] = ` -
  • -
    + +
  • - -
    -
    - - Period - - + +
    +
    + + + Period + + + +
  • -
    - + +
    diff --git a/src/components/Interpretations/common/RichTextEditor/index.js b/src/components/Interpretations/common/RichTextEditor/index.js deleted file mode 100644 index 31c0113ca..000000000 --- a/src/components/Interpretations/common/RichTextEditor/index.js +++ /dev/null @@ -1 +0,0 @@ -export { RichTextEditor } from './RichTextEditor.js' diff --git a/src/components/Interpretations/common/index.js b/src/components/Interpretations/common/index.js index 562614fb1..d3473298f 100644 --- a/src/components/Interpretations/common/index.js +++ b/src/components/Interpretations/common/index.js @@ -1,4 +1,3 @@ export * from './Interpretation/index.js' export * from './Message/index.js' -export * from './RichTextEditor/index.js' export * from './getInterpretationAccess.js' diff --git a/src/components/Interpretations/common/RichTextEditor/RichTextEditor.js b/src/components/RichText/Editor/Editor.js similarity index 78% rename from src/components/Interpretations/common/RichTextEditor/RichTextEditor.js rename to src/components/RichText/Editor/Editor.js index e8ad9216d..6fdbf558e 100644 --- a/src/components/Interpretations/common/RichTextEditor/RichTextEditor.js +++ b/src/components/RichText/Editor/Editor.js @@ -1,10 +1,9 @@ import i18n from '@dhis2/d2-i18n' -import { Parser as RichTextParser } from '@dhis2/d2-ui-rich-text' import { Button, Popover, Tooltip, - Field, + Help, IconAt24, IconFaceAdd24, IconLink24, @@ -12,9 +11,11 @@ import { IconTextItalic24, colors, } from '@dhis2/ui' +import cx from 'classnames' import PropTypes from 'prop-types' import React, { forwardRef, useRef, useEffect, useState } from 'react' -import { UserMentionWrapper } from '../UserMention/UserMentionWrapper.js' +import { UserMentionWrapper } from '../../UserMention/UserMentionWrapper.js' +import { Parser } from '../Parser/Parser.js' import { convertCtrlKey, insertMarkdown, @@ -33,22 +34,22 @@ import { toolbarClasses, tooltipAnchorClasses, emojisPopoverClasses, -} from './styles/RichTextEditor.style.js' +} from './styles/Editor.style.js' const EmojisPopover = ({ onInsertMarkdown, onClose, reference }) => (
    • onInsertMarkdown(EMOJI_SMILEY_FACE)}> - {emojis[EMOJI_SMILEY_FACE]} + {emojis[EMOJI_SMILEY_FACE]}
    • onInsertMarkdown(EMOJI_SAD_FACE)}> - {emojis[EMOJI_SAD_FACE]} + {emojis[EMOJI_SAD_FACE]}
    • onInsertMarkdown(EMOJI_THUMBS_UP)}> - {emojis[EMOJI_THUMBS_UP]} + {emojis[EMOJI_THUMBS_UP]}
    • onInsertMarkdown(EMOJI_THUMBS_DOWN)}> - {emojis[EMOJI_THUMBS_DOWN]} + {emojis[EMOJI_THUMBS_DOWN]}
    @@ -190,29 +191,59 @@ Toolbar.propTypes = { disabled: PropTypes.bool, } -export const RichTextEditor = forwardRef( +export const Editor = forwardRef( ( - { value, disabled, inputPlaceholder, onChange, errorText, helpText }, + { + value, + disabled, + inputPlaceholder, + onChange, + errorText, + helpText, + initialFocus, + resizable, + }, externalRef ) => { const [previewMode, setPreviewMode] = useState(false) const internalRef = useRef() const textareaRef = externalRef || internalRef + const caretPosRef = useRef(undefined) - useEffect(() => textareaRef.current?.focus(), [textareaRef]) + const insertMarkdownCallback = (text, caretPos) => { + caretPosRef.current = caretPos + onChange(text) + textareaRef.current.focus() + } + + useEffect(() => { + if (initialFocus) { + textareaRef.current?.focus() + } + }, [initialFocus, textareaRef]) + + useEffect(() => { + if (caretPosRef.current) { + textareaRef.current?.setSelectionRange( + caretPosRef.current, + caretPosRef.current + ) + + caretPosRef.current = undefined + } + }, [value, textareaRef]) return ( -
    +
    { insertMarkdown( markdown, textareaRef.current, - (text, caretPos) => { - onChange(text) - textareaRef.current.focus() - textareaRef.current.selectionEnd = caretPos - } + insertMarkdownCallback ) if (markdown === MENTION) { @@ -231,20 +262,18 @@ export const RichTextEditor = forwardRef( /> {previewMode ? (
    - {value} + {value}
    ) : ( - +