From 624de526c4d2436b87e7568dc6bdb86d95c5fb0d Mon Sep 17 00:00:00 2001 From: Boubaker Khanfir Date: Fri, 10 Jan 2025 18:33:58 +0100 Subject: [PATCH] feat: Add Category Analytics Fields - MEED-8054 - Meeds-io/meeds#2727 This change will add some event listeners to handle category management and objects link to categories, such as spaces. In addition, this change will implement some UI extensions to well display the category names in Analytics Table and Analytics Charts. --- .../meeds/analytics/utils/AnalyticsUtils.java | 60 +++++++++- .../social/AnalyticsActivityTagsListener.java | 9 +- .../social/AnalyticsCategoryLinkListener.java | 72 +++++++++++ .../social/AnalyticsCategoryListener.java | 76 ++++++++++++ .../locale/portlet/Analytics_en.properties | 17 +++ .../main/webapp/WEB-INF/gatein-resources.xml | 6 + .../components/samples/CategoryAttribute.vue | 113 ++++++++++++++++++ .../components/samples/ViewSamplesDrawer.vue | 3 + .../settings/form/CategoryFieldFilter.vue | 111 +++++++++++++++++ .../components/settings/form/FieldFilter.vue | 4 + .../settings/form/MultipleValuesSelection.vue | 4 + .../vue-app/common-components/extensions.js | 30 ++++- .../common-components/initComponents.js | 4 + .../components/chart/AnalyticsChart.vue | 32 +++-- .../webapp/vue-app/generic-portlet/main.js | 13 ++ .../table/AnalyticsTableCellCategoryValue.vue | 48 ++++++++ .../vue-app/table-portlet/extensions.js | 9 ++ .../vue-app/table-portlet/initComponents.js | 2 + 18 files changed, 590 insertions(+), 23 deletions(-) create mode 100644 analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsCategoryLinkListener.java create mode 100644 analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsCategoryListener.java create mode 100644 analytics-webapps/src/main/webapp/vue-app/common-components/components/samples/CategoryAttribute.vue create mode 100644 analytics-webapps/src/main/webapp/vue-app/common-components/components/settings/form/CategoryFieldFilter.vue create mode 100644 analytics-webapps/src/main/webapp/vue-app/table-portlet/components/table/AnalyticsTableCellCategoryValue.vue diff --git a/analytics-api/src/main/java/io/meeds/analytics/utils/AnalyticsUtils.java b/analytics-api/src/main/java/io/meeds/analytics/utils/AnalyticsUtils.java index 64636d75a..b2f94b691 100644 --- a/analytics-api/src/main/java/io/meeds/analytics/utils/AnalyticsUtils.java +++ b/analytics-api/src/main/java/io/meeds/analytics/utils/AnalyticsUtils.java @@ -49,6 +49,7 @@ import org.exoplatform.commons.api.persistence.ExoTransactional; import org.exoplatform.commons.utils.CommonsUtils; +import org.exoplatform.container.ExoContainerContext; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.security.ConversationState; @@ -68,6 +69,9 @@ import io.meeds.analytics.api.service.StatisticDataQueueService; import io.meeds.analytics.model.StatisticData; import io.meeds.analytics.model.StatisticData.StatisticStatus; +import io.meeds.social.category.model.Category; +import io.meeds.social.category.model.CategoryObject; +import io.meeds.social.category.service.CategoryService; public class AnalyticsUtils { private static final Log LOG = ExoLogger.getLogger(AnalyticsUtils.class); @@ -237,7 +241,7 @@ public static final String compueLabel(String chartKey, String chartValue) { if (identityManager == null) { return defaultLabel; } else { - Identity identity = identityManager.getIdentity(chartValue); + Identity identity = getIdentity(chartValue); if (identity == null) { return defaultLabel; } else { @@ -334,6 +338,12 @@ public static final void addStatisticData(StatisticData statisticData) { if (statisticData.getStatus() == null) { statisticData.setStatus(StatisticStatus.OK); } + if (statisticData.getUserId() == 0 + && ConversationState.getCurrent() != null + && ConversationState.getCurrent().getIdentity() != null) { + statisticData.setUserId(getIdentityId(ActivityStream.ORGANIZATION_PROVIDER_ID, + ConversationState.getCurrent().getIdentity().getUserId())); + } try { StatisticDataQueueService analyticsQueueService = CommonsUtils.getService(StatisticDataQueueService.class); @@ -357,12 +367,13 @@ public static Space getSpaceById(String spaceId) { return spaceService.getSpaceById(spaceId); } - public static Identity getIdentity(String identityId) { - if (StringUtils.isBlank(identityId)) { + public static Identity getIdentity(String identityIdString) { + if (StringUtils.isBlank(identityIdString)) { return null; } IdentityManager identityManager = CommonsUtils.getService(IdentityManager.class); - return identityManager.getIdentity(identityId); + long identityId = parseId(identityIdString); + return identityId > 0 ? identityManager.getIdentity(identityId) : null; } public static long getIdentityId(String identityId) { @@ -423,6 +434,7 @@ public static void addSpaceStatistics(StatisticData statisticData, Space space) } statisticData.setSpaceId(Long.parseLong(space.getId())); statisticData.addParameter("spaceTemplateId", space.getTemplateId()); + statisticData.addParameter("spaceCategoryIds", space.getCategoryIds()); statisticData.addParameter("spaceVisibility", space.getVisibility()); statisticData.addParameter("spaceRegistration", space.getRegistration()); statisticData.addParameter("spaceCreatedTime", space.getCreatedTime()); @@ -454,6 +466,37 @@ public static void addActivityStatisticsData(StatisticData statisticData, ExoSoc } } + public static void addCategoryStatistics(StatisticData statisticData, long categoryId) { + CategoryService categoryService = ExoContainerContext.getService(CategoryService.class); + addCategoryStatistics(statisticData, categoryService.getCategory(categoryId)); + } + + public static void addCategoryStatistics(StatisticData statisticData, Category category) { + if (category == null) { + return; + } + statisticData.addParameter("categoryId", category.getId()); + statisticData.addParameter("categoryIcon", category.getIcon()); + statisticData.addParameter("categoryAccessPermissionIds", category.getAccessPermissionIds()); + statisticData.addParameter("categoryLinkPermissionIds", category.getLinkPermissionIds()); + statisticData.addParameter("categoryCreatorId", category.getCreatorId()); + statisticData.addParameter("categoryOwnerId", category.getOwnerId()); + statisticData.addParameter("categoryParentId", category.getParentId()); + } + + public static void addCategoryLinkStatistics(StatisticData statisticData, CategoryObject categoryObject) { + statisticData.addParameter("categoryObjectType", categoryObject.getType()); + statisticData.addParameter("categoryObjectId", categoryObject.getId()); + statisticData.addParameter("categoryObjectParentId", categoryObject.getParentId()); + if (categoryObject.getSpaceId() > 0) { + SpaceService spaceService = CommonsUtils.getService(SpaceService.class); + Space space = spaceService.getSpaceById(categoryObject.getSpaceId()); + if (space != null) { + addSpaceStatistics(statisticData, space); + } + } + } + private static int getSize(String[] array) { return array == null ? 0 : (int) Arrays.stream(array).filter(Objects::nonNull).distinct().count(); } @@ -472,4 +515,13 @@ private static String getActivityType(ExoSocialActivity activity) { } return type; } + + public static long parseId(String id) { + try { + return Long.parseLong(id); + } catch (NumberFormatException ex) { + return 0; + } + } + } diff --git a/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityTagsListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityTagsListener.java index 5f10f9981..55a391b9f 100644 --- a/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityTagsListener.java +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsActivityTagsListener.java @@ -22,10 +22,10 @@ import java.util.Date; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.apache.commons.lang3.StringUtils; import org.exoplatform.commons.api.persistence.ExoTransactional; import org.exoplatform.services.listener.Asynchronous; @@ -36,7 +36,6 @@ import org.exoplatform.social.core.activity.model.ExoSocialActivityImpl; import org.exoplatform.social.core.identity.model.Identity; import org.exoplatform.social.core.manager.ActivityManager; -import org.exoplatform.social.core.manager.IdentityManager; import org.exoplatform.social.metadata.tag.model.TagName; import org.exoplatform.social.metadata.tag.model.TagObject; @@ -54,9 +53,6 @@ public class AnalyticsActivityTagsListener extends Listener tagNames, String activityId) { private String getActivityPoster(ExoSocialActivity activity) { Validate.notNull(activity.getUserId(), "activity.getUserId() must not be null!"); - return identityManager.getIdentity(activity.getUserId()).getRemoteId(); + Identity identity = AnalyticsUtils.getIdentity(activity.getUserId()); + return identity == null ? null : identity.getRemoteId(); } } diff --git a/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsCategoryLinkListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsCategoryLinkListener.java new file mode 100644 index 000000000..6a678cfa4 --- /dev/null +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsCategoryLinkListener.java @@ -0,0 +1,72 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.analytics.listener.social; + +import static io.meeds.social.category.service.CategoryService.EVENT_SOCIAL_CATEGORY_ITEM_LINKED; +import static io.meeds.social.category.service.CategoryService.EVENT_SOCIAL_CATEGORY_ITEM_UNLINKED; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import org.exoplatform.services.listener.Event; +import org.exoplatform.services.listener.Listener; +import org.exoplatform.services.listener.ListenerService; + +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.utils.AnalyticsUtils; +import io.meeds.social.category.model.CategoryObject; + +import jakarta.annotation.PostConstruct; + +@Component +public class AnalyticsCategoryLinkListener extends Listener { + + @Autowired + private ListenerService listenerService; + + @PostConstruct + public void init() { + listenerService.addListener(EVENT_SOCIAL_CATEGORY_ITEM_LINKED, this); + listenerService.addListener(EVENT_SOCIAL_CATEGORY_ITEM_UNLINKED, this); + } + + @Override + public void onEvent(Event event) throws Exception { + StatisticData statisticData = new StatisticData(); + statisticData.setTimestamp(new Date().getTime()); + statisticData.setModule("social"); + statisticData.setSubModule("category"); + switch (event.getEventName()) { + case EVENT_SOCIAL_CATEGORY_ITEM_LINKED: + statisticData.setOperation("categoryObjectLinked"); + break; + case EVENT_SOCIAL_CATEGORY_ITEM_UNLINKED: + statisticData.setOperation("categoryObjectUnlinked"); + break; + default: + break; + } + AnalyticsUtils.addCategoryStatistics(statisticData, event.getData()); + AnalyticsUtils.addCategoryLinkStatistics(statisticData, event.getSource()); + AnalyticsUtils.addStatisticData(statisticData); + } + +} diff --git a/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsCategoryListener.java b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsCategoryListener.java new file mode 100644 index 000000000..c4870ec3d --- /dev/null +++ b/analytics-listeners/src/main/java/io/meeds/analytics/listener/social/AnalyticsCategoryListener.java @@ -0,0 +1,76 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.analytics.listener.social; + +import static io.meeds.social.category.service.CategoryService.EVENT_SOCIAL_CATEGORY_CREATED; +import static io.meeds.social.category.service.CategoryService.EVENT_SOCIAL_CATEGORY_DELETED; +import static io.meeds.social.category.service.CategoryService.EVENT_SOCIAL_CATEGORY_UPDATED; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import org.exoplatform.services.listener.Event; +import org.exoplatform.services.listener.Listener; +import org.exoplatform.services.listener.ListenerService; + +import io.meeds.analytics.model.StatisticData; +import io.meeds.analytics.utils.AnalyticsUtils; +import io.meeds.social.category.model.Category; + +import jakarta.annotation.PostConstruct; + +@Component +public class AnalyticsCategoryListener extends Listener { + + @Autowired + private ListenerService listenerService; + + @PostConstruct + public void init() { + listenerService.addListener(EVENT_SOCIAL_CATEGORY_CREATED, this); + listenerService.addListener(EVENT_SOCIAL_CATEGORY_UPDATED, this); + listenerService.addListener(EVENT_SOCIAL_CATEGORY_DELETED, this); + } + + @Override + public void onEvent(Event event) throws Exception { + StatisticData statisticData = new StatisticData(); + statisticData.setTimestamp(new Date().getTime()); + statisticData.setModule("social"); + statisticData.setSubModule("category"); + switch (event.getEventName()) { + case EVENT_SOCIAL_CATEGORY_CREATED: + statisticData.setOperation("categoryCreated"); + break; + case EVENT_SOCIAL_CATEGORY_UPDATED: + statisticData.setOperation("categoryUpdated"); + break; + case EVENT_SOCIAL_CATEGORY_DELETED: + statisticData.setOperation("categoryDeleted"); + break; + default: + break; + } + AnalyticsUtils.addCategoryStatistics(statisticData, event.getSource()); + AnalyticsUtils.addStatisticData(statisticData); + } + +} diff --git a/analytics-webapps/src/main/resources/locale/portlet/Analytics_en.properties b/analytics-webapps/src/main/resources/locale/portlet/Analytics_en.properties index 51fbd7415..ab87fe471 100644 --- a/analytics-webapps/src/main/resources/locale/portlet/Analytics_en.properties +++ b/analytics-webapps/src/main/resources/locale/portlet/Analytics_en.properties @@ -452,3 +452,20 @@ analytics.jsonSettings.drawer.title=Edit JSON Settings analytics.jsonSettings.edit.button=Edit JSON analytics.cancel=Cancel analytics.spaceTemplateId=Space Template Identifier +analytics.field.label.category=Category +analytics.categoryCreated=Create category +analytics.categoryUpdated=Update category +analytics.categoryDeleted=Delete category +analytics.categoryObjectLinked=Link a category to an object +analytics.categoryObjectUnlinked=Delete a category from an object +analytics.field.label.categoryId=Category +analytics.field.label.categoryAccessPermissionIds=Category access permission identity ids +analytics.field.label.categoryCreatorId=Category creator identity id +analytics.field.label.categoryIcon=Category icon +analytics.field.label.categoryLinkPermissionIds=Category link permission identity ids +analytics.field.label.categoryObjectId=Category: Object Type +analytics.field.label.categoryObjectType=Category: Object Identifier +analytics.field.label.categoryOwnerId=Category Owner Identity Id +analytics.field.label.categoryParentId=Parent Category +analytics.field.label.spaceCategoryIds=Space Categories +analytics.deletedCategory=Deleted category \ No newline at end of file diff --git a/analytics-webapps/src/main/webapp/WEB-INF/gatein-resources.xml b/analytics-webapps/src/main/webapp/WEB-INF/gatein-resources.xml index 742aae061..c3a1305b7 100644 --- a/analytics-webapps/src/main/webapp/WEB-INF/gatein-resources.xml +++ b/analytics-webapps/src/main/webapp/WEB-INF/gatein-resources.xml @@ -251,6 +251,12 @@ extensionRegistry + + categoryVueComponents + + + commonVueComponents + jquery $ diff --git a/analytics-webapps/src/main/webapp/vue-app/common-components/components/samples/CategoryAttribute.vue b/analytics-webapps/src/main/webapp/vue-app/common-components/components/samples/CategoryAttribute.vue new file mode 100644 index 000000000..37e77e509 --- /dev/null +++ b/analytics-webapps/src/main/webapp/vue-app/common-components/components/samples/CategoryAttribute.vue @@ -0,0 +1,113 @@ + + + \ No newline at end of file diff --git a/analytics-webapps/src/main/webapp/vue-app/common-components/components/samples/ViewSamplesDrawer.vue b/analytics-webapps/src/main/webapp/vue-app/common-components/components/samples/ViewSamplesDrawer.vue index 4372d5d46..b8b1985a9 100644 --- a/analytics-webapps/src/main/webapp/vue-app/common-components/components/samples/ViewSamplesDrawer.vue +++ b/analytics-webapps/src/main/webapp/vue-app/common-components/components/samples/ViewSamplesDrawer.vue @@ -117,6 +117,9 @@ export default { document.addEventListener(`extension-${this.extensionApp}-${this.sampleItemExtensionType}-updated`, this.refreshSampleItemExtensions); this.refreshSampleItemExtensions(); }, + beforeDestroy() { + document.removeEventListener(`extension-${this.extensionApp}-${this.sampleItemExtensionType}-updated`, this.refreshSampleItemExtensions); + }, methods: { open() { this.$refs.samplesDrawer.open(); diff --git a/analytics-webapps/src/main/webapp/vue-app/common-components/components/settings/form/CategoryFieldFilter.vue b/analytics-webapps/src/main/webapp/vue-app/common-components/components/settings/form/CategoryFieldFilter.vue new file mode 100644 index 000000000..4be6768ca --- /dev/null +++ b/analytics-webapps/src/main/webapp/vue-app/common-components/components/settings/form/CategoryFieldFilter.vue @@ -0,0 +1,111 @@ + + + \ No newline at end of file diff --git a/analytics-webapps/src/main/webapp/vue-app/common-components/components/settings/form/FieldFilter.vue b/analytics-webapps/src/main/webapp/vue-app/common-components/components/settings/form/FieldFilter.vue index c03840255..46758fa8e 100644 --- a/analytics-webapps/src/main/webapp/vue-app/common-components/components/settings/form/FieldFilter.vue +++ b/analytics-webapps/src/main/webapp/vue-app/common-components/components/settings/form/FieldFilter.vue @@ -61,6 +61,10 @@ v-else-if="filter.field === 'userId'" :filter="filter" :suggester-labels="suggesterLabels" /> + + fieldName === 'spaceTemplateId', }, -}); \ No newline at end of file +}); + +extensionRegistry.registerExtension('AnalyticsSamples', 'SampleItem', { + type: 'categoryId', + options: { + rank: 50, + vueComponent: Vue.options.components['analytics-category-sample-item-attribute'], + match: fieldName => fieldName === 'categoryId' || fieldName === 'spaceCategoryIds' || fieldName === 'categoryParentId', + }, +}); + +extensionRegistry.registerExtension('AnalyticsChart', 'FieldValueName', { + type: 'categoryId', + match: fieldName => fieldName === 'categoryId' || fieldName === 'categoryParentId', + getLabel: async (fieldName, fieldValue) => { + try { + const translations = await fetch(`${eXo.env.portal.context}/${eXo.env.portal.rest}/social/translations/category/${fieldValue}/name`, { + method: 'GET', + credentials: 'include', + }).then(resp => resp?.json?.()); + return translations?.[eXo.env.portal.language] + || translations?.[eXo.env.portal.defaultLanguage] + || translations?.['en'] + || fieldValue; + } catch (e) { + return `${fieldName}=${fieldValue}`; + } + }, +}); diff --git a/analytics-webapps/src/main/webapp/vue-app/common-components/initComponents.js b/analytics-webapps/src/main/webapp/vue-app/common-components/initComponents.js index 460119297..de939cd75 100644 --- a/analytics-webapps/src/main/webapp/vue-app/common-components/initComponents.js +++ b/analytics-webapps/src/main/webapp/vue-app/common-components/initComponents.js @@ -25,6 +25,7 @@ import FieldSelection from './components/settings/form/FieldSelection.vue'; import FieldFilter from './components/settings/form/FieldFilter.vue'; import SpaceFieldFilter from './components/settings/form/SpaceFieldFilter.vue'; import UserFieldFilter from './components/settings/form/UserFieldFilter.vue'; +import CategoryFieldFilter from './components/settings/form/CategoryFieldFilter.vue'; import TextValuesFilter from './components/settings/form/TextValuesFilter.vue'; import TextValueSuggester from './components/settings/form/TextValueSuggester.vue'; import MultipleValuesSelection from './components/settings/form/MultipleValuesSelection.vue'; @@ -48,6 +49,7 @@ import ProfileListSampleItemAttribute from './components/samples/ProfileListSamp import DateSampleItemAttribute from './components/samples/DateSampleItemAttribute.vue'; import DurationSampleItemAttribute from './components/samples/DurationSampleItemAttribute.vue'; import SpaceTemplateItemAttribute from './components/samples/SpaceTemplateItemAttribute.vue'; +import CategoryAttribute from './components/samples/CategoryAttribute.vue'; import SelectPeriod from './components/common/SelectPeriod.vue'; @@ -60,12 +62,14 @@ const components = { 'analytics-date-sample-item-attribute': DateSampleItemAttribute, 'analytics-duration-sample-item-attribute': DurationSampleItemAttribute, 'analytics-space-template-sample-item-attribute': SpaceTemplateItemAttribute, + 'analytics-category-sample-item-attribute': CategoryAttribute, 'analytics-setting-color-picker': SettingColorPicker, 'analytics-identity-field-selection': IdentityFieldSelection, 'analytics-field-selection': FieldSelection, 'analytics-multiple-values-selection': MultipleValuesSelection, 'analytics-space-field-filter': SpaceFieldFilter, 'analytics-user-field-filter': UserFieldFilter, + 'analytics-category-field-filter': CategoryFieldFilter, 'analytics-text-value-filter': TextValuesFilter, 'analytics-text-value-suggester': TextValueSuggester, 'analytics-field-filter': FieldFilter, diff --git a/analytics-webapps/src/main/webapp/vue-app/generic-portlet/components/chart/AnalyticsChart.vue b/analytics-webapps/src/main/webapp/vue-app/generic-portlet/components/chart/AnalyticsChart.vue index 7b81c634b..3651c1585 100644 --- a/analytics-webapps/src/main/webapp/vue-app/generic-portlet/components/chart/AnalyticsChart.vue +++ b/analytics-webapps/src/main/webapp/vue-app/generic-portlet/components/chart/AnalyticsChart.vue @@ -68,9 +68,9 @@ export default { } }, methods: { - init(chartsData) { + async init(chartsData) { const charts = (chartsData && chartsData.charts) || []; - const labels = (chartsData && chartsData.labels) || []; + const labels = await Promise.all(((chartsData && chartsData.labels) || []).map(l => this.getI18N(l))); const $container = $(`#${this.id}`); if (!$container.length) { @@ -125,7 +125,7 @@ export default { chartOptions.xAxis[0].axisTick = {alignWithLabel: true}; } if (charts.length === 1 && (!charts[0].chartLabel || charts[0].chartLabel === 'null')) { - chartOptions.tooltip.formatter = '{b}
{c}
'; + chartOptions.tooltip.formatter = '
{c}
'; } const isMultipleCharts = charts && charts.length > 1; charts.forEach(chartData => { @@ -139,8 +139,8 @@ export default { serie.areaStyle = { opacity: 0.8 }; serie.lineStyle = { width: 1 }; } - if (chartData.chartLabel){ - serie.name = this.getI18N(chartData.chartLabel); + if (chartData.chartLabel) { + this.retrieveI18N(serie, chartData.chartLabel); } series.push(serie); @@ -186,7 +186,7 @@ export default { value: result.result, }; if (result.label) { - chartDataValues.name = this.getI18N(result.label); + this.retrieveI18N(chartDataValues, result.label); } return chartDataValues; }).sort((v1, v2) => parseFloat(v2.value) - parseFloat(v1.value)); @@ -236,12 +236,20 @@ export default { const chart = echarts.init($container[0]); chart.setOption(chartOptions, true); }, - getI18N(label){ - const field = label.split('=')[1]; - if (field) { - const fieldLabelI18NKey = `analytics.${field}`; - const fieldLabelI18NValue = this.$t(fieldLabelI18NKey); - return fieldLabelI18NValue === fieldLabelI18NKey ? field : fieldLabelI18NValue; + async retrieveI18N(data, label) { + data.name = await this.getI18N(label); + }, + async getI18N(label) { + const fieldName = label.split('=')[0]; + const fieldValue = label.split('=')[1]; + if (fieldValue) { + const extension = this.$root.fieldNameValueExtensions.find(ext => ext.match(fieldName, fieldValue)); + if (extension) { + return await extension.getLabel(fieldName, fieldValue); + } else { + const fieldLabelI18NKey = `analytics.${fieldValue}`; + return this.$te(fieldLabelI18NKey) ? this.$t(fieldLabelI18NKey) : label; + } } else { return label; } diff --git a/analytics-webapps/src/main/webapp/vue-app/generic-portlet/main.js b/analytics-webapps/src/main/webapp/vue-app/generic-portlet/main.js index ad3ffad5f..4a9980052 100644 --- a/analytics-webapps/src/main/webapp/vue-app/generic-portlet/main.js +++ b/analytics-webapps/src/main/webapp/vue-app/generic-portlet/main.js @@ -52,11 +52,24 @@ export function init(dataId) { retrieveChartSamplesURL: retrieveChartSamplesURL, retrieveFieldValuesUrl: retrieveFieldValuesUrl, saveSettingsURL: saveSettingsURL, + fieldNameValueExtensions: null, }), + created() { + document.addEventListener('extension-AnalyticsChart-FieldValueName-updated', this.refreshSampleItemExtensions); + this.refreshSampleItemExtensions(); + }, mounted() { // Hide loading toolbar each time a portlet is mounted document.dispatchEvent(new CustomEvent('hideTopBarLoading')); }, + beforeDestroy() { + document.removeEventListener('extension-AnalyticsChart-FieldValueName-updated', this.refreshSampleItemExtensions); + }, + methods: { + refreshSampleItemExtensions() { + this.fieldNameValueExtensions = extensionRegistry.loadExtensions('AnalyticsChart', 'FieldValueName'); + }, + }, template: ` + + diff --git a/analytics-webapps/src/main/webapp/vue-app/table-portlet/extensions.js b/analytics-webapps/src/main/webapp/vue-app/table-portlet/extensions.js index 02faac812..bceb6d0b2 100644 --- a/analytics-webapps/src/main/webapp/vue-app/table-portlet/extensions.js +++ b/analytics-webapps/src/main/webapp/vue-app/table-portlet/extensions.js @@ -49,3 +49,12 @@ extensionRegistry.registerExtension('AnalyticsTable', 'CellValue', { match: (fieldName, aggregationType) => fieldName === 'spaceTemplateId' && aggregationType === 'TERMS', }, }); + +extensionRegistry.registerExtension('AnalyticsTable', 'CellValue', { + type: 'spaceTemplate', + options: { + rank: 200, + vueComponent: Vue.options.components['analytics-table-cell-category-value'], + match: (fieldName, aggregationType) => aggregationType === 'TERMS' && (fieldName === 'categoryId' || fieldName === 'spaceCategoryIds' || fieldName === 'categoryParentId'), + }, +}); diff --git a/analytics-webapps/src/main/webapp/vue-app/table-portlet/initComponents.js b/analytics-webapps/src/main/webapp/vue-app/table-portlet/initComponents.js index 23f245108..862873d3a 100644 --- a/analytics-webapps/src/main/webapp/vue-app/table-portlet/initComponents.js +++ b/analytics-webapps/src/main/webapp/vue-app/table-portlet/initComponents.js @@ -28,6 +28,7 @@ import AnalyticsTableCellValue from './components/table/AnalyticsTableCellValue. import AnalyticsTableCellUserValue from './components/table/AnalyticsTableCellUserValue.vue'; import AnalyticsTableCellSpaceValue from './components/table/AnalyticsTableCellSpaceValue.vue'; import AnalyticsTableCellSpaceTemplateValue from './components/table/AnalyticsTableCellSpaceTemplateValue.vue'; +import AnalyticsTableCellCategoryValue from './components/table/AnalyticsTableCellCategoryValue.vue'; const components = { 'analytics-table-application': AnalyticsTableApplication, @@ -41,6 +42,7 @@ const components = { 'analytics-table-cell-user-value': AnalyticsTableCellUserValue, 'analytics-table-cell-space-value': AnalyticsTableCellSpaceValue, 'analytics-table-cell-space-template-value': AnalyticsTableCellSpaceTemplateValue, + 'analytics-table-cell-category-value': AnalyticsTableCellCategoryValue, }; for (const key in components) {