From 88dc219a2fbdacf118a40af771215fe88ce8842a Mon Sep 17 00:00:00 2001 From: btry Date: Thu, 8 Oct 2026 14:08:36 +0200 Subject: [PATCH 1/2] refactor: Replace apexcharts with echarts (#376) echarts is available in GLPI core, saving 1 library from the project --- .github/workflows/continuous-integration.yml | 2 +- install/migration/update_1.4.0_to_1.4.1.php | 81 +++ .../01_update_handledd_ratio_card.php | 29 +- package.json | 1 - setup.php | 10 +- src/Dashboard/DemoProvider.php | 37 ++ src/Dashboard/Widget.php | 624 +++++------------- src/UsageInfo.php | 6 +- templates/dashboard/apex_radar.html.twig | 68 +- .../graph-carbon-emission-per-model.html.twig | 36 +- .../graph-carbon-emission-per-month.html.twig | 36 +- templates/environmentalimpact-item.html.twig | 5 + tests/install/PluginInstallTest.php | 2 - tests/units/UsageInfoTest.php | 6 +- 14 files changed, 398 insertions(+), 545 deletions(-) create mode 100644 install/migration/update_1.4.0_to_1.4.1.php rename lib/bundles/apexcharts.js => install/migration/update_1.4.0_to_1.4.1/01_update_handledd_ratio_card.php (63%) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 418376a5..af90652f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -49,7 +49,7 @@ jobs: run: "npm install" - name: "Check generated assets are present" run: | - for file in public/lib/apexcharts.js public/lib/carbon.js public/lib/carbon.css; do + for file in public/lib/carbon.js public/lib/carbon.css; do if [ ! -s "$file" ]; then echo "::error::Missing or empty build asset: $file" exit 1 diff --git a/install/migration/update_1.4.0_to_1.4.1.php b/install/migration/update_1.4.0_to_1.4.1.php new file mode 100644 index 00000000..1d2821a0 --- /dev/null +++ b/install/migration/update_1.4.0_to_1.4.1.php @@ -0,0 +1,81 @@ +. + * + * ------------------------------------------------------------------------- + */ + +use Safe\Exceptions\FilesystemException; + +use function Safe\preg_match; +use function Safe\scandir; + +function update140to141(Migration $migration) +{ + /** @var DBmysql $DB */ + global $DB; + + $updateresult = true; + $from_version = '1.4.0'; + $to_version = '1.4.1'; + $update_dir = __DIR__ . "/update_{$from_version}_to_{$to_version}/"; + + //TRANS: %s is the number of new version + $migration->addInfoMessage(sprintf(__('Update to %s'), $to_version)); + $migration->setVersion($to_version); + + // New tables from enpty.sql file after the migration + // If a script requires a new table, it may create it by itself + + $update_scripts = scandir($update_dir); + natcasesort($update_scripts); + foreach ($update_scripts as $update_script) { + if (preg_match('/\.php$/', $update_script) !== 1) { + continue; + } + require $update_dir . $update_script; + } + + $dbFile = plugin_carbon_getSchemaPath($to_version); + if ($dbFile === null) { + $migration->addWarningMessage("Error creating tables : " . $DB->error()); + $updateresult = false; + } + try { + $DB->runFile($dbFile); + } catch (RuntimeException|FilesystemException $e) { + $migration->addWarningMessage("Error creating tables : " . $e->getMessage()); + $updateresult = false; + } + + + // ************ Keep it at the end ************** + $migration->executeMigration(); + + return $updateresult; +} diff --git a/lib/bundles/apexcharts.js b/install/migration/update_1.4.0_to_1.4.1/01_update_handledd_ratio_card.php similarity index 63% rename from lib/bundles/apexcharts.js rename to install/migration/update_1.4.0_to_1.4.1/01_update_handledd_ratio_card.php index 50f835c5..a663dc06 100644 --- a/lib/bundles/apexcharts.js +++ b/install/migration/update_1.4.0_to_1.4.1/01_update_handledd_ratio_card.php @@ -1,3 +1,5 @@ +find([ + 'card_id' => 'plugin_carbon_assets_completeness_ratio', +]); + +foreach ($rows as $row) { + $card_options = json_decode($row['card_options'], true); + if (!is_array($card_options) || ($card_options['widgettype'] ?? null) !== 'apex_radar') { + continue; + } + + $card_options['widgettype'] = 'radar'; + $dashboard_item->update([ + 'id' => $row['id'], + 'card_options' => json_encode($card_options), + ]); +} diff --git a/package.json b/package.json index 6847189a..d866aa0d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "node": ">= 18.12.0" }, "dependencies": { - "apexcharts": "^7.0" }, "scripts": { "build": "webpack --config .webpack.config.js", diff --git a/setup.php b/setup.php index 0adbc1b7..01541e71 100644 --- a/setup.php +++ b/setup.php @@ -107,6 +107,15 @@ function plugin_init_carbon() plugin_carbon_registerClasses(); $CFG_GLPI['javascript']['tools'][strtolower(Report::class)] = ['dashboard']; + foreach (PLUGIN_CARBON_TYPES as $itemtype) { + $form_url = $itemtype::getFormURL(); + if (strpos($_SERVER['REQUEST_URI'] ?? '', $form_url) !== false) { + continue; + } + if (($_SESSION['glpi_tabs'][strtolower($itemtype)] ?? '') == 'GlpiPlugin\Carbon\UsageInfo$1') { + Html::requireJs('charts'); + } + } $proxy_exclusions = $CFG_GLPI['possible_proxy_exclusions']; $proxy_exclusions->addExclusion(new ProxyExclusion( Config::class, @@ -163,7 +172,6 @@ function plugin_carbon_setupHooks() $PLUGIN_HOOKS[Hooks::PRE_ITEM_UPDATE]['carbon'][GlpiProfile::class] = 'plugin_carbon_profileUpdate'; $PLUGIN_HOOKS[Hooks::PRE_ITEM_ADD]['carbon'][GlpiProfile::class] = 'plugin_carbon_profileAdd'; - $PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['carbon'][] = 'lib/apexcharts.js'; $PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['carbon'][] = 'lib/carbon.js'; // Import CSS diff --git a/src/Dashboard/DemoProvider.php b/src/Dashboard/DemoProvider.php index 0103fe00..ff59dad9 100644 --- a/src/Dashboard/DemoProvider.php +++ b/src/Dashboard/DemoProvider.php @@ -353,6 +353,10 @@ public static function getSumUsageEmissionsPerModel(array $params = [], array $w ], 'url' => [ '', + '', + '', + '', + '', ], 'unit' => 'g CO₂eq', ]; @@ -481,4 +485,37 @@ public static function getImpactOfEmbodiedAndUsageCriteria(string $impact_type, 'doc_url' => Type::getCriteriaInfoLink($impact_type), ]; } + + public static function getHandledAssetsRatio(array $params = []) + { + $default_params = [ + 'label' => __('handled assets ratio', 'carbon'), + 'icon' => '', + ]; + $params = array_merge($default_params, $params); + + $data = []; + + $data[] = [ + 'number' => 83, + 'url' => '', + 'label' => Computer::getTypeName(Session::getPluralNumber()), + ]; + $data[] = [ + 'number' => 71, + 'url' => '', + 'label' => Monitor::getTypeName(Session::getPluralNumber()), + ]; + $data[] = [ + 'number' => 90, + 'url' => '', + 'label' => NetworkEquipment::getTypeName(Session::getPluralNumber()), + ]; + + return [ + 'data' => $data, + 'label' => $params['label'], + 'icon' => $params['icon'], + ]; + } } diff --git a/src/Dashboard/Widget.php b/src/Dashboard/Widget.php index 863d49ba..32c0c197 100644 --- a/src/Dashboard/Widget.php +++ b/src/Dashboard/Widget.php @@ -148,329 +148,18 @@ public static function WidgetTypes(?array $types = null): array } $types += [ - 'apex_radar' => [ + 'radar' => [ 'label' => __('Radar chart', 'carbon'), - 'function' => self::class . '::apexRadar', + 'function' => self::class . '::HandledRatioRadar', 'image' => '', 'width' => 4, 'height' => 4, ], ]; - // 'graphpertype' => [ - // 'label' => __('Carbon Emission Per Type', 'carbon'), - // 'function' => self::class . '::DisplayGraphCarbonEmissionPerType', - // 'image' => '', - // 'limit' => true, - // 'width' => 12, - // 'height' => 10, - // ], - // 'totalcarbonemission' => [ - // 'label' => __('Total Carbon Emission', 'carbon'), - // 'function' => self::class . '::DisplayTotalCarbonEmission', - // 'image' => '', - // 'width' => 5, - // 'height' => 4, - // ], - // 'apex_lines' => [ - // 'label' => __('Multiple lines', 'carbon'), - // 'function' => self::class . '::multipleLines', - // 'image' => $CFG_GLPI['root_doc'] . '/pics/charts/line.png', - // 'width' => 5, - // 'height' => 4, - // ] - // 'apex_pie' => [ - // 'label' => __('Pie', 'carbon'), - // 'function' => self::class . '::apex_pie', - // 'image' => $CFG_GLPI['root_doc'] . '/pics/charts/line.png', - // 'width' => 5, - // 'height' => 4, - // ], return $types; } - /** - * Display a widget with a multiple line chart (with multiple series) - * @see self::getLinesGraph for params - * - * @return string html - */ - // public static function multipleLines(array $params = []): string - // { - // return self::getLinesGraph( - // array_merge($params, [ - // 'legend' => true, - // 'multiple' => true, - // ]), - // $params['data']['labels'], - // $params['data']['series'] - // ); - // } - - // /** - // * Display a widget with a lines chart - // * - // * @param array $params contains these keys: - // * - array 'data': represents the lines to display - // * - string 'url': url to redirect when clicking on the line - // * - string 'label': title of the line - // * - int 'number': number of the line - // * - string 'label': global title of the widget - // * - string 'alt': tooltip - // * - string 'color': hex color of the widget - // * - string 'icon': font awesome class to display an icon side of the label - // * - string 'id': unique dom identifier - // * - bool 'area': do we want an area chart - // * - bool 'legend': do we display a legend for the graph - // * - bool 'use_gradient': gradient or generic palette - // * - bool 'point_labels': display labels (for values) directly on graph - // * - int 'limit': the number of lines - // * - array 'filters': array of filter's id to apply classes on widget html - // * @param array $labels title of the lines (if a single array is given, we have a single line graph) - // * @param array $series values of the line (if a single array is given, we have a single line graph) - // * - // * @return string html of the widget - // */ - // private static function getLinesGraph( - // array $params = [], - // array $labels = [], - // array $series = [] - // ): string { - // $defaults = [ - // 'data' => [], - // 'label' => '', - // 'alt' => '', - // 'color' => '', - // 'icon' => '', - // 'area' => false, - // 'legend' => false, - // 'multiple' => false, - // 'use_gradient' => false, - // 'point_labels' => false, - // 'limit' => 99999, - // 'filters' => [], - // 'rand' => mt_rand(), - // ]; - - // $p = array_merge($defaults, $params); - // $p['cache_key'] = $p['cache_key'] ?? $p['rand']; - - // $nb_series = count($series); - // $nb_labels = min($p['limit'], count($labels)); - // array_splice($labels, 0, -$nb_labels); - // if ($p['multiple']) { - // foreach ($series as &$serie) { - // if (isset($serie['data'])) { - // array_splice($serie['data'], 0, -$nb_labels); - // } - // } - // unset($serie); - // } else { - // array_splice($series[0], 0, -$nb_labels); - // } - - // // Chart title - // $chart_title = $p['label']; - - // // Line or area ? - // $chart_type = $p['area'] ? 'area' : 'line'; - - // // legend - // $show_legend = $p['legend'] ? true : false; - - // // Series and y axis - // $yaxis = []; - // $stroke = []; - // foreach ($series as $key => $serie) { - // $yaxis[$key] = [ - // 'title' => [ - // 'text' => $serie['name'], - // ], - // 'opposite' => ($key % 2 > 0), - // ]; - // $stroke['width'][] = (($serie['type'] ?? 'line') == 'line') ? 4 : 0; - // } - - // $fg_color = GlpiToolbox::getFgColor($p['color']); - // $line_color = GlpiToolbox::getFgColor($p['color'], 10); - // $dark_bg_color = GlpiToolbox::getFgColor($p['color'], 80); - // $dark_fg_color = GlpiToolbox::getFgColor($p['color'], 40); - // $dark_line_color = GlpiToolbox::getFgColor($p['color'], 90); - - // $chart_id = "chart-{$p['cache_key']}"; - - // $palette_style = ""; - // if (!$p['multiple'] || $p['use_gradient']) { - // $palette_style = self::getGradientPalette($p['color'], $nb_series); - // } - - // $chart_id = 'chart_' . $p['cache_key']; - // $class = "line"; - // $class .= $p['area'] ? " area" : ""; - // $class .= $p['multiple'] ? " multiple" : ""; - // $class .= count($p['filters']) > 0 ? " filter-" . implode(' filter-', $p['filters']) : ""; - // $categories = json_encode($labels); - // $series = json_encode($series); - // $yaxis = json_encode($yaxis); - // $stroke = json_encode($stroke); - // $class = count($p['filters']) > 0 ? " filter-" . implode(' filter-', $p['filters']) : ""; - - // return TemplateRenderer::getInstance()->render('@carbon/dashboard/multiple-lines.html.twig', [ - // 'class' => $class, - // 'chart_id' => $chart_id, - // 'chart_type' => $chart_type, - // 'chart_title' => $chart_title, - // 'show_legend' => $show_legend, - // 'series' => $series, - // 'categories' => $categories, - // 'yaxis' => $yaxis, - // 'icon' => $p['icon'], - // 'label_class' => $p['label'], - // 'color' => $p['color'], - // 'palette_style' => $palette_style, - // 'fg_color' => $fg_color, - // 'line_color' => $line_color, - // 'dark_bg_color' => $dark_bg_color, - // 'dark_fg_color' => $dark_fg_color, - // 'dark_line_color' => $dark_line_color, - // 'stroke' => $stroke, - // ]); - // } - - // /** - // * Display a widget with a pie chart - // * - // * @param array $params contains these keys: - // * - array 'data': represents the slices to display - // * - int 'number': number of the slice - // * - string 'url': url to redirect when clicking on the slice - // * - string 'label': title of the slice - // * - string 'label': global title of the widget - // * - string 'alt': tooltip - // * - string 'color': hex color of the widget - // * - string 'icon': font awesome class to display an icon side of the label - // * - string 'id': unique dom identifier - // * - bool 'use_gradient': gradient or generic palette - // * - int 'limit': the number of slices - // * - bool 'donut': do we want a "holed" pie - // * - bool 'gauge': do we want an half pie - // * - array 'filters': array of filter's id to apply classes on widget html - // * - // * @return string html of the widget - // */ - // public static function pie( - // array $params = [] - // ): string { - // $default = [ - // 'type' => 'pie', - // 'data' => [], - // 'label' => '', - // 'alt' => '', - // 'color' => '', - // 'icon' => '', - // 'donut' => false, - // 'half' => false, - // 'legend' => false, - // 'use_gradient' => false, - // 'limit' => 99999, - // 'filters' => [], - // 'rand' => mt_rand(), - // ]; - // $p = array_merge($default, $params); - // $p['cache_key'] = $p['cache_key'] ?? $p['rand']; - - // $chart_id = "chart-{$p['cache_key']}"; - - // $nb_slices = min($p['limit'], count($p['series'])); - // array_splice($p['series'], $nb_slices); - // array_splice($p['labels'], $nb_slices); - // $nb_series = min($p['limit'], count($p['series'])); - - // $options = ['pie' => [ - // 'startAngle' => 0, - // 'endAngle' => 360, - // 'offsetY' => 0, - // ] - // ]; - // if ($p['donut']) { - // $p['type'] = 'donut'; - // } - // if ($p['half']) { - // $options['pie'] = [ - // 'startAngle' => -90, - // 'endAngle' => 90, - // 'offsetY' => 10, - // ]; - // } - - // $nodata = isset($p['data']['nodata']) && $p['data']['nodata']; - - // $fg_color = GlpiToolbox::getFgColor($p['color']); - // $dark_bg_color = GlpiToolbox::getFgColor($p['color'], 80); - // $dark_fg_color = GlpiToolbox::getFgColor($p['color'], 40); - - // $palette_style = ""; - // if ($p['use_gradient']) { - // $palette_style = self::getGradientPalette( - // $p['color'], - // $nb_series - // ); - // } - - // // Chart title - // $chart_title = $p['label']; - - // // legend - // $show_legend = $p['legend'] ? true : false; - - // return TemplateRenderer::getInstance()->render('@carbon/dashboard/pie.html.twig', [ - // 'no_data' => $nodata ? 'true' : 'false', - // 'chart_type' => $p['type'], - // 'plot_options' => json_encode($options), - // 'chart_id' => $chart_id, - // 'icon' => $p['icon'], - // 'label_class' => $p['label'], - // 'color' => $p['color'], - // 'chart_title' => $chart_title, - // 'show_legend' => $show_legend, - // 'palette_style' => $palette_style, - // 'fg_color' => $fg_color, - // 'dark_bg_color' => $dark_bg_color, - // 'dark_fg_color' => $dark_fg_color, - // 'series' => json_encode($p['series']), - // 'labels' => json_encode($p['labels']), - // ]); - // } - - // public static function donut( - // array $params = [], - // array $labels = [], - // array $series = [] - // ): string { - // return self::pie( - // array_merge($params, ['donut' => true]), - // $labels, - // $series - // ); - // } - - // public static function halfDonut( - // array $params = [], - // array $labels = [], - // array $series = [] - // ): string { - // return self::pie( - // array_merge($params, ['donut' => true, 'half' => true]), - // $labels, - // $series - // ); - // } - - // public static function displayGraphCarbonEmissionPerType(array $params = []): string - // { - // return self::halfDonut($params); - // } - public static function displayGraphUsageCarbonEmissionPerMonth(array $params = []): string { $default = [ @@ -490,77 +179,68 @@ public static function displayGraphUsageCarbonEmissionPerMonth(array $params = [ $fg_hover_color = GlpiToolbox::getFgColor($p['color'], 15); $fb_hover_border = GlpiToolbox::getFgColor($p['color'], 30); - $apex_data = [ - 'chart' => [ - 'type' => 'line', - 'height' => 350, - ], + $data = $p['data']; + $energy = array_column($data['series'][1]['data'], 'y'); + $energy_min = count($energy) > 0 ? 0.8 * min($energy) : 0; + $echarts_data = [ 'title' => [ 'text' => $p['label'], - ], - 'colors' => ['#BBDA50', '#A00'], - 'plotOptions' => [ - 'bar' => [ - 'horizontal' => false, - 'columnWidth' => '55%', - 'endingShape' => 'rounded', + 'textStyle' => [ + 'color' => $fg_color, ], ], - 'dataLabels' => [ - 'enabled' => false, - 'enabledOnSeries' => [0, 1], - 'style' => [ - 'colors' => ['#145161', '#800'], - ], + 'color' => ['#BBDA50', '#A00'], + 'tooltip' => [ + 'trigger' => 'axis', ], - 'labels' => [], - 'stroke' => [ - 'width' => [0, 4], - 'curve' => 'smooth', + 'legend' => [ + 'data' => array_column($data['series'], 'name'), ], - 'series' => [ + 'grid' => [ + 'containLabel' => true, + ], + 'xAxis' => [ + 'type' => 'category', + 'data' => $data['labels'], + ], + 'yAxis' => [ [ - 'name' => __('Carbon emission', 'carbon'), - 'type' => 'bar', - 'data' => [], + 'type' => 'value', + 'position' => 'left', + 'name' => $data['series'][0]['name'], + 'nameLocation' => 'middle', + 'nameRotate' => 90, + 'nameGap' => 40, + 'splitLine' => ['show' => false], ], [ - 'name' => __('Consumed energy', 'carbon'), - 'type' => 'line', - 'data' => [], + 'type' => 'value', + 'position' => 'right', + 'name' => $data['series'][1]['name'], + 'nameLocation' => 'middle', + 'nameRotate' => 90, + 'nameGap' => 40, + 'min' => $energy_min, + 'splitLine' => ['show' => false], ], ], - 'xaxis' => [ - 'categories' => [], - ], - 'yaxis' => [ + 'series' => [ [ - 'title' => ['text' => __('Carbon emission', 'carbon')], - ], [ - 'opposite' => true, - 'title' => ['text' => __('Consumed energy', 'carbon')], + 'name' => $data['series'][0]['name'], + 'type' => 'bar', + 'yAxisIndex' => 0, + 'data' => array_column($data['series'][0]['data'], 'y'), + ], + [ + 'name' => $data['series'][1]['name'], + 'type' => 'line', + 'yAxisIndex' => 1, + 'smooth' => true, + 'symbolSize' => 6, + 'data' => $energy, ], - ], - 'markers' => [ - 'size' => [3, 3], - ], - 'tooltip' => [ - 'enabled' => true, ], ]; - $data = $p['data']; - foreach ($data['series'] as $key => $serie) { - $apex_data['series'][$key]['name'] = $serie['name']; - $apex_data['series'][$key]['data'] = $serie['data']; - } - $apex_data['labels'] = $data['labels']; - $apex_data['xaxis']['categories'] = $data['labels']; - - $apex_data['yaxis'][1]['min'] = 0; - $energy = array_column($apex_data['series'][1]['data'], 'y'); - if (count($energy) > 0) { - $apex_data['yaxis'][1]['min'] = 0.8 * min($energy); - } return TemplateRenderer::getInstance()->render('@carbon/dashboard/graph-carbon-emission-per-month.html.twig', [ 'id' => $p['id'], @@ -570,7 +250,7 @@ public static function displayGraphUsageCarbonEmissionPerMonth(array $params = [ 'dark_bg_color' => $dark_bg_color, 'fg_hover_color' => $fg_hover_color, 'fg_hover_border' => $fb_hover_border, - 'data' => $apex_data, + 'data' => $echarts_data, ]); } @@ -587,59 +267,81 @@ public static function displayGraphUsageCarbonEmissionPerModel(array $params = [ ]; $p = array_merge($default, $params); $fg_color = GlpiToolbox::getFgColor($p['color']); - $dark_fg_color = GlpiToolbox::getFgColor($p['color'], 40); + $data = $p['data']; + $source_values = $data['series'] ?? []; + $source_labels = $data['labels'] ?? []; + $source_urls = $data['url'] ?? []; + $limit = min($params['limit'] ?? count($source_values), count($source_values)); + $labels = array_slice($source_labels, 0, $limit); + $values = array_slice($source_values, 0, $limit); + $urls = array_slice($source_urls, 0, $limit); + $series_data = []; + foreach ($values as $index => $value) { + $series_data[] = [ + 'name' => $labels[$index], + 'value' => $value, + 'url' => $urls[$index], + ]; + } + $series_data[] = [ + 'name' => '', + 'value' => array_sum($values), + 'itemStyle' => ['color' => 'transparent'], + 'tooltip' => ['show' => false], + 'label' => ['show' => false], + ]; - $apex_data = [ - 'colors' => ['#146151', '#FEEC5C', '#BBDA50', '#F78343', '#97989C'], - 'chart' => [ - 'type' => 'donut', - ], + $echarts_data = [ 'title' => [ 'text' => $p['label'], - ], - 'plotOptions' => [ - 'pie' => [ - 'startAngle' => -90, - 'endAngle' => 90, - 'offsetY' => 10, + 'textStyle' => [ + 'color' => $fg_color, ], ], - 'grid' => [ - 'padding' => [ - 'bottom' => -80, + 'color' => ['#146151', '#FEEC5C', '#BBDA50', '#F78343', '#97989C'], + 'tooltip' => [ + 'trigger' => 'item', + 'appendToBody' => true, + ], + 'legend' => [ + 'show' => true, + 'type' => 'scroll', + 'orient' => 'vertical', + 'data' => $labels, + // 'left' => '68%', + // 'top' => '50%', + 'right' => '5%', + 'top' => '25%', + 'textStyle' => [ + 'color' => $fg_color, ], ], - 'responsive' => [[ - 'breakpoint' => 480, - 'options' => [ - 'chart' => [ - 'width' => 200, + 'series' => [ + [ + 'type' => 'pie', + 'radius' => ['40%', '70%'], + // 'center' => ['32%', '68%'], + 'center' => ['25%', '68%'], + 'startAngle' => 180, + 'avoidLabelOverlap' => true, + 'data' => $series_data, + 'label' => [ + 'show' => false, ], - 'legend' => [ - 'position' => 'bottom', + 'labelLine' => [ + 'show' => false, ], ], ], - ], - 'subtitle' => [ - 'style' => [], - ], - 'series' => [], - 'labels' => [], ]; - $apex_data = array_merge($apex_data, $p['data']); - $limit = min($params['limit'], count($p['data'])); - $apex_data['series'] = array_slice($apex_data['series'], 0, $limit); - $apex_data['labels'] = array_slice($apex_data['labels'], 0, $limit); return TemplateRenderer::getInstance()->render('@carbon/dashboard/graph-carbon-emission-per-model.html.twig', [ 'id' => $p['id'], 'color' => $p['color'], 'fg_color' => $fg_color, - 'dark_fg_color' => $dark_fg_color, 'fg_hover_color' => GlpiToolbox::getFgColor($p['color'], 15), 'fg_hover_border' => GlpiToolbox::getFgColor($p['color'], 30), - 'data' => $apex_data, + 'data' => $echarts_data, ]); } @@ -1040,7 +742,7 @@ public static function displayInformationMethodology(array $params = []): string * @param array $params * @return string */ - public static function apexRadar(array $params = []): string + public static function HandledRatioRadar(array $params = []): string { $default = [ 'data' => [], @@ -1058,83 +760,71 @@ public static function apexRadar(array $params = []): string $p = array_merge($default, $params); $p['cache_key'] ??= $p['rand']; - $nodata = isset($p['data']['nodata']) && $p['data']['nodata']; - $fg_color = GlpiToolbox::getFgColor($p['color']); $dark_bg_color = GlpiToolbox::getFgColor($p['color'], 80); - $dark_fg_color = GlpiToolbox::getFgColor($p['color'], 40); $chart_id = GlpiToolbox::slugify("chart_{$p['cache_key']}"); $class = "radar"; $class .= count($p['filters']) > 0 ? " filter-" . implode(' filter-', $p['filters']) : ""; - $series = [ - [ - 'name' => __('Handled percentage', 'carbon'), - 'data' => [], - ], - ]; - - $categories = []; + $indicators = []; + $values = []; foreach ($p['data'] as $itemtype_data) { - $categories[] = $itemtype_data['label']; - $series[0]['data'][] = $itemtype_data['number']; - } - - $nb_series = count($series); - $palette_style = ""; - if ($p['use_gradient']) { - $palette_style = self::getGradientPalette( - $p['color'], - $nb_series, - false - ); - } - - $no_data_html = ""; - if ($nodata) { - $no_data_html = " -
" . __('No data found') . "
- "; + $indicators[] = [ + 'name' => $itemtype_data['label'], + 'max' => 100, + ]; + $values[] = (float) $itemtype_data['number']; } $data = [ - 'series' => $series, - 'chart' => [ - 'width' => '100%', - 'height' => '95%', - 'redrawOnParentResize' => true, - 'type' => 'radar', - 'toolbar' => [ - 'show' => false, - ], - ], - 'yaxis' => [ - 'stepSize' => 20, - ], - 'xaxis' => [ - 'categories' => $categories, - ], + 'color' => [$fg_color], 'title' => [ 'text' => $p['label'], + 'textStyle' => [ + 'color' => $fg_color, + ], ], - 'dataLabels' => [ - // 'style' => [ - // 'colors' => [$fg_color], - // ] - 'background' => [ - 'enabled' => true, - 'foreColor' => $fg_color, + 'tooltip' => [ + 'trigger' => 'item', + ], + 'radar' => [ + 'indicator' => $indicators, + 'shape' => 'polygon', + 'radius' => '65%', + 'center' => ['50%', '55%'], + 'axisName' => [ + 'color' => $fg_color, + ], + 'axisLine' => [ + 'lineStyle' => [ + 'color' => $fg_color, + ], + ], + 'splitLine' => [ + 'lineStyle' => [ + 'color' => $fg_color, + ], + ], + 'splitArea' => [ + 'show' => false, ], ], - 'colors' => [ - $fg_color, + 'series' => [ + [ + 'type' => 'radar', + 'areaStyle' => [ + 'opacity' => 0.2, + ], + 'data' => [ + [ + 'name' => __('Handled percentage', 'carbon'), + 'value' => $values, + ], + ], + ], ], - // 'legend' => [ - // 'show' => true, - // 'showForSingleSeries' => true, - // ], ]; $output = TemplateRenderer::getInstance()->render('@carbon/dashboard/apex_radar.html.twig', [ @@ -1142,11 +832,13 @@ public static function apexRadar(array $params = []): string 'class' => $class, 'color' => $p['color'], 'fg_color' => $fg_color, - 'dark_fg_color' => $dark_fg_color, + 'fg_hover_color' => GlpiToolbox::getFgColor($p['color'], 15), + 'fg_hover_border' => GlpiToolbox::getFgColor($p['color'], 30), + 'dark_fg_color' => GlpiToolbox::getFgColor($p['color'], 40), 'dark_bg_color' => $dark_bg_color, - 'palette_style' => $palette_style, 'label' => $p['label'], 'data' => $data, + 'icon' => $p['icon'], ]); return $output; diff --git a/src/UsageInfo.php b/src/UsageInfo.php index 4fb4f955..423b0fe3 100644 --- a/src/UsageInfo.php +++ b/src/UsageInfo.php @@ -269,6 +269,9 @@ public static function showCharts(CommonDBTM $asset) $usage_impact_action_url = $CFG_GLPI['root_doc'] . '/plugins/carbon/front/usageimpact.form.php'; $embodied_impact_action_url = $CFG_GLPI['root_doc'] . '/plugins/carbon/front/embodiedimpact.form.php'; + $usage_carbon_emissions_chart_id = 'plugin_carbon_usage_carbon_emissions_' . mt_rand(); + $data['id'] = $usage_carbon_emissions_chart_id; + $usage_carbon_emission_chart = Widget::DisplayGraphUsageCarbonEmissionPerMonth($data); TemplateRenderer::getInstance()->display('@carbon/environmentalimpact-item.html.twig', [ 'usage_info' => $usage_info, 'asset' => $asset, @@ -279,7 +282,8 @@ public static function showCharts(CommonDBTM $asset) 'usage_labels' => $usage_labels, 'embodied_tooltips' => $embodied_tooltips, 'usage_tooltips' => $usage_tooltips, - 'usage_carbon_emission_graph' => Widget::DisplayGraphUsageCarbonEmissionPerMonth($data), + 'usage_carbon_emissions_chart_id' => $usage_carbon_emissions_chart_id, + 'usage_carbon_emission_graph' => $usage_carbon_emission_chart, 'usage_impact_action_url' => $usage_impact_action_url, 'embodied_impact_action_url' => $embodied_impact_action_url, ]); diff --git a/templates/dashboard/apex_radar.html.twig b/templates/dashboard/apex_radar.html.twig index 78ddab1a..70b88836 100644 --- a/templates/dashboard/apex_radar.html.twig +++ b/templates/dashboard/apex_radar.html.twig @@ -45,53 +45,33 @@ color: {{ dark_fg_color }}; } - #{{ chart_id }} .ct-label { - fill: {{ fg_color }}; - color: {{ fg_color }}; - } - - .theme-dark #{{ chart_id }} .ct-label { - fill: {{ dark_fg_color }}; - color: {{ dark_fg_color }}; - } - - #{{ chart_id }} .apexcharts-xaxis text{ - fill: {{ fg_color }}; - } - - .theme-dark #{{ chart_id }} .apexcharts-xaxis text{ - fill: {{ dark_fg_color }}; + #{{ chart_id }} .chart { + height: 100%; + min-height: 200px; + width: 100%; } - - #{{ chart_id }} polygon, #{{ chart_id }} .apexcharts-plot-series line { - stroke: {{ fg_color }}; - } - - .theme-dark #{{ chart_id }}, #{{ chart_id }} .apexcharts-plot-series line { - stroke: {{ dark_fg_color }}; - } - - {{ palette_style }} -
-
-
{{ no_data_html }}
- {# {{ label }} #} + +
+ {% if data.radar.indicator|length == 0 %} +
{{ __('No data found') }}
-
+ {% else %} +
+ {% endif %}
-{% block scripts %} - -{% endblock %} + const chartResizeObserver = new ResizeObserver(() => chart.resize()); + chartResizeObserver.observe(chartElement); + })(); + + {% endblock %} +{% endif %} \ No newline at end of file diff --git a/templates/dashboard/graph-carbon-emission-per-model.html.twig b/templates/dashboard/graph-carbon-emission-per-model.html.twig index 6028d6b0..370d0a83 100644 --- a/templates/dashboard/graph-carbon-emission-per-model.html.twig +++ b/templates/dashboard/graph-carbon-emission-per-model.html.twig @@ -51,26 +51,40 @@ border: 1px solid {{ fg_hover_border }}; } - #{{ id }} .apexcharts-title-text { - fill: {{ fg_color }}; + #{{ id }} .card-body { + display: flex; + flex-direction: column; + height: 100%; } - .theme-dark #{{ id }} .apexcharts-title-text { - fill: {{ dark_fg_color }}; + #{{ id }}_carbon_emission_per_model_chart { + flex: 1 1 auto; + height: 100%; + min-height: 0; + width: 100%; }
-
-
-
+
+
{% block scripts %} {% endblock %} diff --git a/templates/dashboard/graph-carbon-emission-per-month.html.twig b/templates/dashboard/graph-carbon-emission-per-month.html.twig index ea89904e..cb7c1ce0 100644 --- a/templates/dashboard/graph-carbon-emission-per-month.html.twig +++ b/templates/dashboard/graph-carbon-emission-per-month.html.twig @@ -51,32 +51,42 @@ border: 1px solid {{ fg_hover_border }}; } - #{{ id }} .apexcharts-title-text { - fill: {{ fg_color }}; + #{{ id }} .card-body { + display: flex; + flex-direction: column; + height: 100%; } - .theme-dark #{{ id }} .apexcharts-title-text { - fill: {{ dark_fg_color }}; + #{{ id }}_carbon_emission_chart { + flex: 1 1 auto; + height: 100%; + min-height: 0; + width: 100%; }
-
-
+
{% block scripts %} {% endblock %} diff --git a/templates/environmentalimpact-item.html.twig b/templates/environmentalimpact-item.html.twig index 352d8043..455b502a 100644 --- a/templates/environmentalimpact-item.html.twig +++ b/templates/environmentalimpact-item.html.twig @@ -36,6 +36,11 @@ 'fas fa-chart-pie' ) }} + {{ usage_carbon_emission_graph|raw }}
diff --git a/tests/install/PluginInstallTest.php b/tests/install/PluginInstallTest.php index 5ffe1c5a..25360b3d 100644 --- a/tests/install/PluginInstallTest.php +++ b/tests/install/PluginInstallTest.php @@ -530,10 +530,8 @@ public function checkBuiltFiles() $plugin_dir = dirname(__DIR__, 2); $this->assertTrue(file_exists($plugin_dir . 'lib/carbon.css')); $this->assertTrue(file_exists($plugin_dir . 'lib/carbon.js')); - $this->assertTrue(file_exists($plugin_dir . 'lib/apexcharts.js')); $this->assertTrue(in_array('lib/carbon.css', $PLUGIN_HOOKS[Hooks::ADD_CSS]['carbon'])); - $this->assertTrue(in_array('lib/apexcharts.js', $PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['carbon'])); } #[Depends('testInstallPlugin')] diff --git a/tests/units/UsageInfoTest.php b/tests/units/UsageInfoTest.php index 46ed3aec..79ebbc17 100644 --- a/tests/units/UsageInfoTest.php +++ b/tests/units/UsageInfoTest.php @@ -199,7 +199,7 @@ public function testShowcharts() UsageInfo::showCharts($item); $output = ob_get_clean(); $crawler = new Crawler($output); - $monthlyCarbonEmissionChart = $crawler->filter('#carbonEmissionPerMonthChart'); + $monthlyCarbonEmissionChart = $crawler->filter('[id$="_carbon_emission_chart"]'); $this->assertEquals(1, $monthlyCarbonEmissionChart->count()); $this->assertTrue($this->testEmbodiedGwp($crawler)); $this->assertTrue($this->testEmbodiedAdp($crawler)); @@ -215,7 +215,7 @@ public function testShowcharts() UsageInfo::showCharts($item); $output = ob_get_clean(); $crawler = new Crawler($output); - $monthlyCarbonEmissionChart = $crawler->filter('#carbonEmissionPerMonthChart'); + $monthlyCarbonEmissionChart = $crawler->filter('[id$="_carbon_emission_chart"]'); $this->assertEquals(1, $monthlyCarbonEmissionChart->count()); $this->assertFalse($this->testEmbodiedGwp($crawler)); $this->assertFalse($this->testEmbodiedAdp($crawler)); @@ -238,7 +238,7 @@ public function testShowcharts() UsageInfo::showCharts($item); $output = ob_get_clean(); $crawler = new Crawler($output); - $monthlyCarbonEmissionChart = $crawler->filter('#carbonEmissionPerMonthChart'); + $monthlyCarbonEmissionChart = $crawler->filter('[id$="_carbon_emission_chart"]'); $this->assertEquals(1, $monthlyCarbonEmissionChart->count()); $this->assertFalse($this->testEmbodiedGwp($crawler)); $this->assertFalse($this->testEmbodiedAdp($crawler)); From e7977418909129ff20fc1a714206218650bb3418 Mon Sep 17 00:00:00 2001 From: btry Date: Fri, 9 Oct 2026 08:19:10 +0200 Subject: [PATCH 2/2] fix(Dashboard\Widget): various fixes --- setup.php | 8 +- src/Dashboard/Grid.php | 2 +- src/Dashboard/Widget.php | 2 +- .../{apex_radar.html.twig => radar.html.twig} | 0 tests/migration/migration_to_1_4_1_Test.php | 100 ++++++++++++++++++ 5 files changed, 103 insertions(+), 9 deletions(-) rename templates/dashboard/{apex_radar.html.twig => radar.html.twig} (100%) create mode 100644 tests/migration/migration_to_1_4_1_Test.php diff --git a/setup.php b/setup.php index 01541e71..8e88770f 100644 --- a/setup.php +++ b/setup.php @@ -108,13 +108,7 @@ function plugin_init_carbon() $CFG_GLPI['javascript']['tools'][strtolower(Report::class)] = ['dashboard']; foreach (PLUGIN_CARBON_TYPES as $itemtype) { - $form_url = $itemtype::getFormURL(); - if (strpos($_SERVER['REQUEST_URI'] ?? '', $form_url) !== false) { - continue; - } - if (($_SESSION['glpi_tabs'][strtolower($itemtype)] ?? '') == 'GlpiPlugin\Carbon\UsageInfo$1') { - Html::requireJs('charts'); - } + $CFG_GLPI['javascript']['assets'][strtolower($itemtype)][] = 'charts'; } $proxy_exclusions = $CFG_GLPI['possible_proxy_exclusions']; $proxy_exclusions->addExclusion(new ProxyExclusion( diff --git a/src/Dashboard/Grid.php b/src/Dashboard/Grid.php index a4569e78..5180276b 100644 --- a/src/Dashboard/Grid.php +++ b/src/Dashboard/Grid.php @@ -74,7 +74,7 @@ public static function getDashboardCards(?array $cards = null): array $group = __('Carbon', 'carbon'); $cards += [ 'plugin_carbon_assets_completeness_ratio' => [ - 'widgettype' => ['apex_radar', 'multipleNumber'], + 'widgettype' => ['radar', 'multipleNumber'], 'group' => $group, 'label' => __('Handled assets ratio', 'carbon'), 'provider' => Provider::class . '::getHandledAssetsRatio', diff --git a/src/Dashboard/Widget.php b/src/Dashboard/Widget.php index 32c0c197..7c03c95f 100644 --- a/src/Dashboard/Widget.php +++ b/src/Dashboard/Widget.php @@ -827,7 +827,7 @@ public static function HandledRatioRadar(array $params = []): string ], ]; - $output = TemplateRenderer::getInstance()->render('@carbon/dashboard/apex_radar.html.twig', [ + $output = TemplateRenderer::getInstance()->render('@carbon/dashboard/radar.html.twig', [ 'chart_id' => $chart_id, 'class' => $class, 'color' => $p['color'], diff --git a/templates/dashboard/apex_radar.html.twig b/templates/dashboard/radar.html.twig similarity index 100% rename from templates/dashboard/apex_radar.html.twig rename to templates/dashboard/radar.html.twig diff --git a/tests/migration/migration_to_1_4_1_Test.php b/tests/migration/migration_to_1_4_1_Test.php new file mode 100644 index 00000000..ac9369ce --- /dev/null +++ b/tests/migration/migration_to_1_4_1_Test.php @@ -0,0 +1,100 @@ +. + * + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Carbon\Tests; + +use DBmysql; +use GlpiPlugin\Carbon\Install; +use Migration; + +use function Safe\json_decode; +use function Safe\json_encode; + +class migration_to_1_4_1_Test extends CommonTestCase +{ + public function testUpdateHandledRatioCard() + { + /** @var DBmysql $DB */ + global $DB; + + require_once(__DIR__ . '/../../install/Install.php'); + + $DB->insert('glpi_dashboards_dashboards', [ + 'key' => 'plugin_carbon_migration_test', + 'name' => 'plugin_carbon_migration_test', + ]); + $dashboard_id = $DB->insertId(); + $cards = [ + 'radar_card' => [ + 'card_id' => 'plugin_carbon_assets_completeness_ratio', + 'widgettype' => 'apex_radar', + ], + 'number_card' => [ + 'card_id' => 'plugin_carbon_assets_completeness_ratio', + 'widgettype' => 'multipleNumber', + ], + 'other_card' => [ + 'card_id' => 'plugin_carbon_assets_completeness', + 'widgettype' => 'apex_radar', + ], + ]; + $ids = []; + foreach ($cards as $key => $card) { + $DB->insert('glpi_dashboards_items', [ + 'dashboards_dashboards_id' => $dashboard_id, + 'gridstack_id' => $card['card_id'] . '_' . $key, + 'card_id' => $card['card_id'], + 'card_options' => json_encode(['color' => '#FAFAFA', 'widgettype' => $card['widgettype']]), + ]); + $ids[$key] = $DB->insertId(); + } + + $install = new Install(new Migration('1.4.1')); + $migrations = $install->getMigrationsToDo('1.4.0'); + $install->upgradeOneVersion(key($migrations), current($migrations)); + + $expected = [ + 'radar_card' => 'radar', + 'number_card' => 'multipleNumber', + 'other_card' => 'apex_radar', + ]; + foreach ($expected as $key => $widgettype) { + $row = $DB->request([ + 'FROM' => 'glpi_dashboards_items', + 'WHERE' => ['id' => $ids[$key]], + ])->current(); + $card_options = json_decode($row['card_options'], true); + $this->assertSame($widgettype, $card_options['widgettype']); + $this->assertSame('#FAFAFA', $card_options['color']); + } + } +}