deprecation_status-8.x-3.x-dev/src/Controller/ChartController.php
src/Controller/ChartController.php
<?php
namespace Drupal\deprecation_status\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\deprecation_status\DataSource;
class ChartController extends ControllerBase {
public function chartPage() {
$build = ['#cache' => ['tags' => ['deprecation_status']]];
$build['chartjs'] = [
'#children' =>
'<script src="https://cdn.jsdelivr.net/npm/moment@2.24.0/moment.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
<script>
const verticalLinePlugin = {
getLinePosition: function (chart, pointIndex) {
const meta = chart.getDatasetMeta(0); // first dataset is used to discover X coordinate of a point
const data = meta.data;
return data[pointIndex.index]._model.x;
},
renderVerticalLine: function (chartInstance, pointIndex) {
const lineLeftOffset = this.getLinePosition(chartInstance, pointIndex);
const scale = chartInstance.scales[\'y-axis-0\'];
const context = chartInstance.chart.ctx;
// render vertical line
context.beginPath();
context.strokeStyle = \'#48494b\';
context.moveTo(lineLeftOffset, scale.top);
context.lineTo(lineLeftOffset, scale.bottom - 20);
context.setLineDash([3, 3]);
context.lineWidth = 2;
context.stroke();
// write label
var width = ctx.measureText(pointIndex.label).width;
context.fillStyle = "#f5f5f5";
context.fillRect(lineLeftOffset - 2 - width/2 - 4, scale.bottom - 19, width + 8, 19);
context.fillStyle = "#48494b";
context.textAlign = \'center\';
context.fillText(pointIndex.label, lineLeftOffset - 2, scale.bottom - 10);
},
afterDatasetsDraw: function (chart, easing) {
if (chart.config.markerLines) {
chart.config.markerLines.forEach(pointIndex => this.renderVerticalLine(chart, pointIndex));
}
}
};
Chart.plugins.register(verticalLinePlugin);</script>',
];
return $build + $this->graphProjectsAndErrors() /*+ $this->graph10Deprecations()*/;
}
public function graphProjectsAndErrors() {
$markers = "{label: 'Drupal 9.4', index: 8}, {label: 'Drupal 10.0', index: 31}, {label: 'Drupal 10.1', index: 47}";
// "#5EBF69", "#E0E552", "#FDBD65", "#F9A148", "#F56E44", "#F12D36"
$breakdown_legend = [
'Stable, congrats' => ['#005F73', 2, 1],
'Improve stability of the release' => ['#0A9396', 1, 1],
'Make tagged release available' => ['#94D2BD', 0, 1],
'Fix info.yml/composer.json errors found' => ['#E9D8A6', 3, 1],
'Run Rector to fix all errors' => ['#EE9B00', 4, 1],
'Run Rector to fix some errors' => ['#CA6702', 5, 1],
'Manually review and fix errors' => ['#BB3E03', 6, 1],
'Resolve pre-scanning errors' => ['#9B2226', 7, 9],
];
$groups = "'Top 50', 'Top 100', 'Top200', 'Top 500', 'Top 1000', 'All'";
// Read project segment data.
$data = [];
$file = fopen(DataSource::getFullPath('next_steps_specific_segments.csv'), 'r');
$i = 0;
while ($line = fgetcsv($file, 0, ";")) {
if ($i != 0) {
$segment = array_shift($line);
$sum = array_sum($line);
if ($segment == '10000') {
$groups = str_replace("'All'", "'All (" . $sum . ")'", $groups);
}
$items = [];
foreach($breakdown_legend as $label => $structure) {
$items[] = array_sum(array_slice($line, $structure[1], $structure[2]));
}
$row = [];
foreach($items as $item) {
$row[] = round($item/($sum/100), 2);
}
$data[] = $row;
}
$i++;
}
fclose($file);
$datasets = [];
$i = 0;
foreach($breakdown_legend as $label => $structure) {
$raw = '{label: "'. $label . '", data: [';
foreach($data as $values) {
$raw .= $values[$i] . ',';
}
$datasets[] = trim($raw, ',') . '], backgroundColor: "' . $structure[0] . '"}';
$i++;
}
$datasets = join(',', $datasets);
$build['breakdown_segments'] = [
'#markup' => '<h2>Percentage of contributed projects by next step in usage groups</h2><p>Larger groups include results for smaller usage groups.</p>',
'#children' => <<<PROJECTSEND
<canvas id="SegmentsStatus"></canvas>
<script type="text/javascript">
var ctx = document.getElementById('SegmentsStatus').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: [$groups],
datasets: [$datasets]
},
options: {
aspectRatio: window.innerWidth < 800 ? 1 : 2,
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true,
ticks: { max: 100 }
}]
}
}
});
</script>
PROJECTSEND
];
// =========================================================================
$project_graphs = [
'_200' => 'Top 200 drupal.org contributed projects by next step over time',
'' => 'All drupal.org contributed projects by next step over time',
];
foreach ($project_graphs as $graph_key => $graph_label) {
// Read historic project data.
$projects = [];
$legend = [];
$file = fopen(DataSource::getFullPath('next_steps_specific' . $graph_key . '.csv'), 'r');
$i = 0;
while ($line = fgetcsv($file, 0, ";")) {
if ($i != 0) {
$time = array_shift($line);
$items = [];
foreach($breakdown_legend as $label => $structure) {
$projects[$label][$time] = array_sum(array_slice($line, $structure[1], $structure[2]));
}
}
$i++;
}
fclose($file);
$datasets = [];
foreach($projects as $label => $dataset) {
$raw = '{label: "'. $label . '", data: [';
foreach($dataset as $time => $value) {
$raw .= '{x: new Date(' . $time . '*1000), y: ' . $value . '},';
}
$raw = trim($raw, ',') . ']';
if (isset($breakdown_legend[$label])) {
$raw .= ', borderColor: "' . $breakdown_legend[$label][0] . '", backgroundColor: "' . $breakdown_legend[$label][0] . '"';
}
$raw .= '}';
$datasets[] = $raw;
}
$datasets = join(',', $datasets);
$build[$graph_key . '_projects'] = [
'#markup' => '<h2>' . $graph_label . '</h2><p>Projects have errors that can be <a href="https://www.drupal.org/project/rector">automatically fixed with drupal-rector</a>.</p>',
'#children' => <<<PROJECTSEND
<canvas id="{$graph_key}ProjectStatus"></canvas>
<script type="text/javascript">
var ctx = document.getElementById('{$graph_key}ProjectStatus').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [$datasets]
},
options: {
aspectRatio: window.innerWidth < 800 ? 1 : 2,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'week',
max: new Date('2023-12-31')
}
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
}
},
markerLines: [$markers]
});
</script>
PROJECTSEND
];
}
// =========================================================================
$count_graphs = [
'_200' => 'Top 200 drupal.org contributed projects by error counts over time',
'' => 'All drupal.org contributed projects by error counts over time',
];
foreach ($count_graphs as $graph_key => $graph_label) {
// Read historic count data.
$projects = [];
$legend = [];
$file = fopen(DataSource::getFullPath('error_counts' . $graph_key . '.csv'), 'r');
$i = 0;
while ($line = fgetcsv($file, 0, ";")) {
if ($i == 0) {
$legend = $line;
array_shift($legend);
}
else {
$time = array_shift($line);
foreach($legend as $index => $label) {
$projects[$label][$time] = $line[$index];
}
}
$i++;
}
fclose($file);
$breakdown_colors = [
'No errors' => "#264653",
'Less than 5' => "#2A9D8F",
'Less than 10' => "#E9C46A",
'10 or more' => "#F4A261",
'Pre-scanning errors' => "#E76F51",
];
$datasets = [];
foreach($projects as $label => $dataset) {
$raw = '{label: "'. $label . '", data: [';
foreach($dataset as $time => $value) {
$raw .= '{x: new Date(' . $time . '*1000), y: ' . $value . '},';
}
$raw = trim($raw, ',') . ']';
if (isset($breakdown_colors[$label])) {
$raw .= ', borderColor: "' . $breakdown_colors[$label] . '", backgroundColor: "' . $breakdown_colors[$label] . '"';
}
$raw .= '}';
$datasets[] = $raw;
}
$datasets = join(',', $datasets);
$build[$graph_key . '_counts'] = [
'#markup' => '<h2>' . $graph_label . '</h2><p>Number of projects by how many errors are in them. No results for projects with pre-scanning errors, counting them as one each for this chart.</p>',
'#children' => <<<PROJECTSEND
<canvas id="{$graph_key}CountStatus"></canvas>
<script type="text/javascript">
var ctx = document.getElementById('{$graph_key}CountStatus').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [$datasets]
},
options: {
aspectRatio: window.innerWidth < 800 ? 1 : 2,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'week',
max: new Date('2023-12-31')
}
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
}
},
markerLines: [$markers]
});
</script>
PROJECTSEND
];
}
// =========================================================================
$type_graphs = [
'_200' => 'Top 200 drupal.org contributed projects\' errors by fixability over time',
'' => 'All drupal.org contributed projects\'s errors by fixability over time',
];
foreach ($type_graphs as $graph_key => $graph_label) {
$type_legend = [
'Info.yml or composer.json' => '#e9d8a6',
'Drupal API, rector covered' => '#fdae61',
'Drupal API, not rector covered' => '#f46d43',
'Symfony API' => '#d73027',
'Twig API' => '#a50026',
'PHPUnit API' => '#e0f3f8',
'Guzzle API' => '#abd9e9',
'Frontend API' => '#74add1',
'Other problem' => '#4575b4',
'Parse error' => '#313695',
];
// Read historic error data.
$errors = [];
$legend = [];
$file = fopen(DataSource::getFullPath('error_types' . $graph_key . '.csv'), 'r');
$i = 0;
while ($line = fgetcsv($file, 0, ";")) {
if ($i == 0) {
$legend = $line;
array_shift($legend);
}
else {
$time = array_shift($line);
foreach($legend as $index => $label) {
$errors[$label][$time] = $line[$index];
}
}
$i++;
}
fclose($file);
$datasets = [];
foreach($errors as $label => $dataset) {
$raw = '{label: "'. $label . '", data: [';
foreach($dataset as $time => $value) {
$raw .= '{x: new Date(' . $time . '*1000), y: ' . $value . '},';
}
$raw = trim($raw, ',') . ']';
if (!empty($type_legend[$label])) {
$raw .= ', borderColor: "' . $type_legend[$label] . '", backgroundColor: "' . $type_legend[$label] . '"';
}
$raw .= '}';
$datasets[] = $raw;
}
$datasets = join(',', $datasets);
$build[$graph_key . '_errors'] = [
'#markup' => '<h2>' . $graph_label . '</h2><p><a href="https://www.drupal.org/project/rector">Use drupal-rector</a> to fix rector-covered errors. <a href="https://www.drupal.org/project/rector">Help expand rector coverage</a> to make all our lives easier.</p>',
'#children' => <<<ERRORSEND
<canvas id="{$graph_key}ErrorStatus"></canvas>
<script type="text/javascript">
var ctx = document.getElementById('{$graph_key}ErrorStatus').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [$datasets]
},
options: {
aspectRatio: window.innerWidth < 800 ? 1 : 2,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'week',
max: new Date('2023-12-31')
}
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
}
},
markerLines: [$markers]
});
</script>
ERRORSEND
];
}
return $build;
}
public function graph10Deprecations() {
// git checkout `git rev-list -1 --before="February 28 2022" 10.0.x`
// $ grep -r "@deprecated" core --exclude-dir=node_modules | wc -l
// $ grep -r "@deprecated" core --exclude-dir=node_modules | grep -o "11.0.0" | wc -l
// $ grep -ro "E_USER_DEPRECATED" core --exclude-dir=node_modules | wc -l
// $ grep -r "E_USER_DEPRECATED" core --exclude-dir=node_modules | grep -o "11.0.0" | wc -l
// Read historic core deprecation count data.
$at_deprecated = '';
$e_user_deprecated = '';
$file = fopen(DataSource::getFullPath('core_deprecation_count.csv'), 'r');
$i = 0;
while ($line = fgetcsv($file, 0, ";")) {
// Skip the header row.
if ($i > 0) {
$at_deprecated .= '{x: new Date("' . $line[0] . '"), y: ' . $line[1] . '},';
$e_user_deprecated .= '{x: new Date("' . $line[0] . '"), y: ' . $line[2] . '},';
}
$i++;
}
fclose($file);
$at_deprecated = trim($at_deprecated, ',');
$e_user_deprecated = trim($e_user_deprecated, ',');
$build['core'] = [
'#markup' => '<h2><a href="https://api.drupal.org/api/drupal/deprecated/9.4.x">Deprecated APIs</a> are removed from the Drupal 10 branch.</h2><p>Tracking based on @deprecated and E_USER_DEPRECATED mentions in Drupal 10 core for illustration, even though there are some other ways to deprecate things (libraries, extensions, etc).</p>',
'#children' => <<<COREEND
<canvas id="coreDeprecated"></canvas>
<script type="text/javascript">
var ctx = document.getElementById('coreDeprecated').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: 'Drupal core @deprecated mentions',
borderColor: "#FDC70F",
borderWidth: 4,
data: [$at_deprecated],
cubicInterpolationMode: 'monotone',
fill: false,
pointRadius: 4,
pointHoverRadius: 10,
pointBackgroundColor: "#FDC70F",
},
{
label: 'Drupal core E_USER_DEPRECATED mentions',
borderColor: "#1F72B2",
borderWidth: 4,
data: [$e_user_deprecated],
cubicInterpolationMode: 'monotone',
fill: false,
pointRadius: 4,
pointHoverRadius: 10,
pointBackgroundColor: "#1F72B2",
}]
},
options: {
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'week',
max: new Date('2023-12-31')
}
}],
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
</script>
COREEND
];
return $build;
}
}
