deprecation_status-8.x-3.x-dev/src/Form/ProjectsForm.php

src/Form/ProjectsForm.php
<?php

namespace Drupal\deprecation_status\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Utility\TableSort;
use Drupal\deprecation_status\DataSource;
use Symfony\Component\HttpFoundation\RedirectResponse;

class ProjectsForm extends FormBase {

  /**
   * Stored sorting criteria.
   *
   * @type int
   */
  protected $column;

  /**
   * Stored sorting criteria ('asc' or 'desc').
   *
   * @type string
   */
  protected $sort;

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'deprecation_status_projects_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    // Get dataset metadata for outside links to error logs.
    // list ($data_id, $data_date) = DataSource::getMetaData();
    // $target_version_result_url = 'https://dispatcher.drupalci.org/job/project_analysis_d10/' . $data_id;

    $form['#cache']['tags'][] = 'deprecation_status';
    $form['#attached']['library'][] = 'deprecation_status/deprecation_status.lists';

    $form['filters'] = [
      '#type' => 'details',
      '#title' => $this->t('Filter projects'),
      '#attributes' => ['class' => ['container-inline']],
      '#open' => TRUE,
    ];

    $form['filters']['names'] = [
      '#title' => 'Names',
      '#type' => 'textfield',
      '#default_value' => @$_GET['names'],
      '#attributes' => ['placeholder' => "Use *, eg. 'commerce*'"],
      '#size' => 20,
    ];
    $form['filters']['next_step'] = [
      '#title' => 'Next step',
      '#type' => 'select',
      '#options' => [
        'Release as Drupal 10-ready' => 'Release as Drupal 10-ready',
        '-- Stable, congrats' => '-- Stable, congrats',
        '-- Improve stability of the release' => '-- Improve stability of the release',
        '-- Make tagged release available' => '-- Make tagged release available',
        'Fix deprecation errors found' => 'Fix deprecation errors found',
        '-- Fix info.yml/composer.json errors found' => '-- Fix info.yml/composer.json errors found',
        '-- Run Rector to fix all errors' => '-- Run Rector to fix all errors',
        '-- Run Rector to fix some errors' => '-- Run Rector to fix some errors',
        '-- Manually review and fix errors' => '-- Manually review and fix errors',
        'Resolve pre-scanning errors' => 'Resolve pre-scanning errors',
        '-- Resolve fatal error from PHPStan' => '-- Resolve fatal error from PHPStan',
        '-- Fix invalid PHPStan results' => '-- Fix invalid PHPStan results',
        '-- Fix malformed info.yml file' => '-- Fix malformed info.yml file',
        '-- Fix machine name issue' => '-- Fix machine name issue',
        '-- Fix composer installation issue' => '-- Fix composer installation issue',
        '-- Fix PHP fatal error' => '-- Fix PHP fatal error',
        '-- Fix PHP environment constraints if possible' => '-- Fix PHP environment constraints if possible',
        '-- Check uncategorized error leading to missing results' => '-- Check uncategorized error leading to missing results',
        '-- Check uncategorized error leading to empty results' => '-- Check uncategorized error leading to empty results',
      ],
      '#default_value' => @$_GET['next_step'],
      '#required' => FALSE,
      '#empty_option' => '- No filter -',
    ];
    $form['filters']['maintainer'] = [
      '#title' => 'Maintainer',
      '#type' => 'textfield',
      '#default_value' => @$_GET['maintainer'],
      '#attributes' => ['placeholder' => "eg. Dries"],
      '#size' => 20,
    ];
    $form['filters']['type'] = [
      '#title' => 'Type',
      '#type' => 'select',
      '#options' => [
        'Module' => 'Module',
        'Theme' => 'Theme',
        'Theme_engine' => 'Theme engine',
        'Distribution' => 'Distribution',
      ],
      '#default_value' => @$_GET['type'],
      '#required' => FALSE,
      '#empty_option' => '- No filter -',
    ];
    $form['filters']['topx'] = [
      '#title' => 'Top X by usage',
      '#type' => 'select',
      '#options' => [],
      '#default_value' => @$_GET['topx'],
      '#required' => FALSE,
      '#empty_option' => '- No filter -',
    ];
    foreach([50, 100, 200, 500, 1000] as $number) {
      $form['filters']['topx']['#options'][$number] = '<= ' . $number;
    }
    /*$form['filters']['plan'] = [
      '#title' => 'Upgrade plan',
      '#type' => 'select',
      '#options' => ['none' => 'None', 'specified' => 'Specified'],
      '#default_value' => @$_GET['plan'],
      '#required' => FALSE,
      '#empty_option' => '- No filter -',
    ];
    $form['filters']['patch'] = [
      '#title' => 'Rector patch',
      '#type' => 'select',
      '#options' => ['none' => 'None', 'available' => 'Available'],
      '#default_value' => @$_GET['patch'],
      '#required' => FALSE,
      '#empty_option' => '- No filter -',
    ];*/
    $form['filters']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];

    // Read all projects into one big array.
    $projects = [];
    $file = fopen(DataSource::getFullPath('projects_detail.csv'), 'r');
    $i = 0;
    while ($line = fgetcsv($file, 0, ";")) {
      // Skip the header row.
      if ($i > 0) {
        $projects[] = $line;
      }
      $i++;
    }
    fclose($file);

    /*
    "Name and release";"General next step";"Specific next step";"Project type";
    "Usage count";"Top X by usage";"Highest compatible stability level";
    "Highest compatible stability version";"Total errors";"Total parse errors";
    "Total deprecated API uses";"Drupal API, rector covered";
    "Drupal API, not rector covered";"Symfony API";"Twig API";"PHPUnit API";
    "Guzzle API";"Frontend API";"Info.yml or composer.json";"Other problem";
    "Drupal 8 API deprecations";"Upgrade plan";"Rector patchfile";
    "Pre-scan error log";Maintainers
    */

    // Set up columns for the table.
    $columns = [
      'name'      => ['data' => 'Name and release', 'field' => 0],
      'status'    => ['data' => 'Status', 'field' => 1],
      'next_step' => ['data' => 'Next step', 'field' => 2],
      'usage'     => ['data' => 'Usage', 'field' => 4, 'sort' => 'desc'],
      'topx'      => ['data' => 'Top X by usage', 'field' => 5],
      'total'     => ['data' => 'Total errors', 'field' => 8],
      'issues'    => ['data' => 'Drupal.org issues'],
    ];

    // Sort the data array based on query tablesort information.
    $order = TableSort::getOrder($columns, \Drupal::request());
    $this->column = $order['sql'];
    $this->sort = TableSort::getSort($columns, \Drupal::request());
    uasort($projects, [$this, 'tableSort']);

    $common_script = <<<SCRIPTEND
      legend: {
        display: true,
        position: window.innerWidth < 800 ? 'top' : 'right',
        onClick: null
      }
SCRIPTEND;
    $form['chart'] = [
      '#children' => <<<SCRIPTEND
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.6.2/dist/chart.min.js"></script>
<div id="charts">
 <canvas id="totalProjects"></canvas>
 <canvas id="totalDeprecations"></canvas>
</div>
<script type="text/javascript">
var ctx = document.getElementById('totalProjects').getContext('2d');
var chart = new Chart(ctx, {
  type: 'pie',
  data: {
    labels: [@totalProjectsLabels],
    datasets: [{
      data: [@totalProjectsData],
      backgroundColor: ["#005F73", "#0A9396", "#94D2BD", "#E9D8A6", "#EE9B00", "#CA6702", "#BB3E03", "#9B2226"]
    }]
  },
  options: {
    aspectRatio: window.innerWidth < 800 ? 1.5 : 2,
    responsive: false,
    plugins: {
      title: {
        display: true,
        text: '@projectcount projects by next steps',
        position: 'bottom'
      },
      $common_script
    }
  }
});
var ctx = document.getElementById('totalDeprecations').getContext('2d');
var chart = new Chart(ctx, {
  type: 'pie',
  data: {
    labels: [@totalDeprecationsLabels],
    datasets: [{
      data: [@totalDeprecationsData],
      backgroundColor: ['#e9d8a6', '#fdae61', '#f46d43', '#d73027', '#a50026', '#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695']
    }]
  },
  options: {
    aspectRatio: window.innerWidth < 800 ? 1.5 : 2,
    responsive: false,
    plugins: {
      title: {
        display: true,
        text: '@deprecatedcount occurences of errors found',
        position: 'bottom'
      },
      $common_script
    }
  }
});
</script>
SCRIPTEND
    ];

    // Filter projects down based on form input. Add data summaries of overall
    // results or filtered results.
    $projects = $this->tableFilter($projects);
    if (count($projects) == 0) {
      unset($form['chart']);
    }
    else {
      $summary = $this->dataSummary($projects);
      $project_labels = "'" . join("','", array_keys($summary['results'])) . "'";
      $project_data = join(',', array_values($summary['results']));
      $deprecation_labels = "'" . join("','", array_keys($summary['errors'])) . "'";
      $deprecation_data = join(',', array_values($summary['errors']));
      $deprecation_count = $summary['total'];
      $form['chart']['#children'] = str_replace(
        [
         '@projectcount', '@totalProjectsLabels', '@totalProjectsData',
         '@deprecatedcount', '@totalDeprecationsLabels', '@totalDeprecationsData'
        ],
        [
          count($projects), $project_labels, $project_data,
          $deprecation_count, $deprecation_labels, $deprecation_data
        ],
        $form['chart']['#children']
      );
    }

    if (!empty(@$_GET['next_step'])) {
      // Get next steps descriptions to display.
      $file = fopen(DataSource::getFullPath('next_steps_help.csv'), 'r');
      $step_help = '';
      $i = 0;
      while ($line = fgetcsv($file, 0, ";")) {
        if ($i > 0 && $line[0] == $_GET['next_step']) {
          $step_help = $line[1];
        }
        $i++;
      }
      fclose($file);
      // Add a step help element if we found the help. This also validates the
      // GET value, so we can use it without fear of XSS.
      if (!empty($step_help)) {
        $form['step_help'] = [
          '#type' => 'markup',
          '#markup' => '<h2>' . str_replace('-- ', '', $_GET['next_step']) . ': ' . count($projects) . ' projects</h2><p>' . $step_help . '</p>'
        ];
      }
    }

    // Display the resulting data.
    $form['table'] = [
      '#type' => 'table',
      '#header' => $columns,
      '#empty' => t('No matching projects found.'),
    ];

    // Page the results based on query information.
    $page = \Drupal::service('pager.parameters')->findPage();
    $num_per_page = 30;
    \Drupal::service('pager.manager')->createPager(count($projects), $num_per_page);
    $offset = $num_per_page * $page;
    $project_page = array_slice($projects, $offset, $num_per_page);

    // Key results based on header key names.
    $keys = array_keys($columns);
    foreach ($project_page as $project) {
      list ($machine_name) = explode(' ', $project[0]);
      $info = [
        'name' => [
          'data' => [
            '#type' => 'link',
            '#title' => $project[0],
            '#url' => Url::fromRoute('deprecation_status.project', ['machine_name' => $machine_name]),
          ],
        ],
      ];

      $class = 'warning';
      if ($project[1] == 'Resolve pre-scanning errors') {
        $class = 'unknown';
      }
      elseif ($project[1] == 'Release as Drupal 10-ready') {
        $class = 'passed';
      }
      elseif (in_array($project[2], ['Run Rector to fix all errors', 'Run Rector to fix some errors', 'Manually review and fix errors'])) {
        $class = 'error';
      }
      $info['#attributes']['class'] = ['item-' . $class];

      $info['status'] = [
        '#type' => 'link',
        '#prefix' => '<span class="category">',
        '#title' => $project[1],
        '#url' => Url::fromRoute('deprecation_status.projects_form', [], ['query' => ['next_step' => $project[1]]]),
        '#suffix' => '</span>',
      ];
      $info['next_step'] = [
        '#type' => 'link',
        '#title' => $project[2],
        '#url' => Url::fromRoute('deprecation_status.projects_form', [], ['query' => ['next_step' => '-- ' . $project[2]]]),
      ];

      foreach($keys as $key) {
        if (!isset($info[$key])) {
          $info[$key] = [
            '#type' => 'markup',
            '#markup' => $project[$columns[$key]['field']],
          ];
        }
      }
      $info['issues'] = [
        '#type' => 'markup',
        '#markup' => '<a href="https://drupal.org/project/issues/search/' . $machine_name . '?issue_tags=Drupal+10+compatibility">Compatibility issues</a>',
      ];
      $info['topx'] = [
        '#type' => 'link',
        '#title' => $project[5],
        '#url' => Url::fromRoute('deprecation_status.projects_form', [], ['query' => ['topx' => $project[5]]]),
      ];

      /*// Format drupal.org issue links in plans and fix relative links.
      $plan = preg_replace('!(\[#([0-9]+)\])!', '<a target="_blank" href="https://drupal.org/node/\2">\1</a>', $project[21]);
      $plan = preg_replace('!href="/!', 'href="https://drupal.org/', $plan);
      $info['plan'] = [
        '#type' => 'markup',
        '#markup' => $plan,
      ];
      $info['total']['#markup'] = '<strong>' . $info['total']['#markup'] . '</strong>';

      if (!empty($project[22]) && $project[1] != 'Improve stability of the release') {
        $patch_uri = $target_version_result_url . '/artifact/phpstan-results/' . $project[22] . '/*view*' . '/';
        $info['patch'] = [
          'data' => [
            '#type' => 'link',
            '#title' => $this->t('Available'),
            '#url' => Url::fromUri($patch_uri),
          ],
        ];
      }
      else {
        $info['patch'] = [
          '#type' => 'markup',
          '#markup' => ''
        ];
      }*/

      $form['table'][] = $info;
    }

    $form['pager'] = array(
      '#type' => 'pager'
    );

    $form['data_info'] = DataSource::getDataInfo();
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
  $keys = ['names', 'next_step', 'type', 'topx', 'maintainer', /*'plan', 'patch'*/];
    $submitted = array_filter($form_state->getValues());
    $query = [];
    foreach ($keys as $key) {
      if (!empty($submitted[$key])) {
        $query[$key] = $submitted[$key];
      }
    }
    $form_state->setRedirect('deprecation_status.projects_form', [], ['query' => $query]);
  }

  /**
   * Custom table sorting based on stored sorting criteria.
   */
  protected function tableSort($a, $b) {
    if ($a[$this->column] == $b[$this->column]) {
      return 0;
    }
    $cmp = ($a[$this->column] < $b[$this->column]) ? -1 : 1;
    return ($this->sort == 'desc') ? $cmp * -1 : $cmp;
  }

  /**
   * Custom table filter based on form values.
   */
  protected function tableFilter($projects) {
  $keys = ['names', 'next_step', 'type', 'topx', 'maintainer', /*'plan', 'patch'*/];
    foreach ($keys as $key) {
      $$key = @$_GET[$key];
    }

    // Process peoject name matching for preg_match() later.
    $name_list = [];
    if (!empty($names)) {
      $all_names = explode(',', $names);
      foreach($all_names as $one_name) {
        $one_name = trim($one_name);
        // If this name does not contain a wildcard and also does not contain a
        // target_version number (which should have a dot), then fix it for the user
        // and add a space and wildcard at the end. This would turn searches for
        // 'google_analytics' into 'google_analytics *' which does match.
        if (strpos($one_name, '.') === FALSE && strpos($one_name, '*') === FALSE) {
          $one_name .= ' *';
        }
        $parts = explode('*', trim($one_name));
        $processed_parts = [];
        foreach ($parts as $part) {
          $processed_parts[] = preg_quote($part, '!');
        }
        $name_list[] = join('.*', $processed_parts);
      }
    }

    $filtered = [];
    foreach ($projects as $project) {
      // Assume this row is a match.
      $match = TRUE;

      // Go through each filter and if the row is still a match, see if this
      // filter will invalidate it.
      if (!empty($name_list) && $match) {
        $one_match = FALSE;
        foreach($name_list as $name) {
          if (preg_match("!^$name$!", $project[0])) {
            $one_match = TRUE;
            break;
          }
        }
        $match = $one_match;
      }
      if (!empty($next_step) && $match) {
        if (strpos($next_step, '--') === 0) {
          $match = ($project[2] == str_replace('-- ', '', $next_step));
        }
        else {
          $match = ($project[1] == $next_step);
        }
      }
      if (!empty($error) && $match) {
        $match = ($project[2] == $error);
      }
      if (!empty($type) && $match) {
        $match = ($project[3] == $type);
      }
      if (!empty($topx) && $match) {
        $match = ($project[5] <= $topx);
      }
      if (!empty($maintainer) && $match) {
        // "Berdir, Dave Reid, eaton, fago, greggles, mikeryan, tsvenson"
        $regex = preg_quote($maintainer, '!');
        $match = preg_match('!(^|, )' . $maintainer . '($|, )!', $project[24]);
      }
      /*if (!empty($plan) && $match) {
        $match = ($plan == 'none' ? empty($project[21]) : !empty($project[21]));
      }
      if (!empty($patch) && $match) {
        $match = ($patch == 'none' ? empty($project[22]) : !empty($project[22]) && $project[1] != 'Improve stability of the release');
      }*/

      // If the row was still a match, continue.
      if ($match) {
        $filtered[] = $project;
      }
    }

    return $filtered;
  }

  /**
   * Summarize results from $projects for charts.
   *
   * @param array $projects
   *   Project data array
   *
   * @return array
   *   Summary array with project, total and errors / next step breakdown.
   */
  protected function dataSummary($projects) {
    $summary = [
      'projects' => count($projects),
      'total' => 0,
      'errors' => [
        'Info.yml or composer.json' => 0,
        'Drupal API, rector covered' => 0,
        'Drupal API, not rector covered' => 0,
        'Symfony API' => 0,
        'Twig API' => 0,
        'PHPUnit API' => 0,
        'Guzzle API' => 0,
        'Frontend API' => 0,
        'Other problem' => 0,
        'Parse error' => 0,
      ],
      'results' => [
        'Stable, congrats' => 0,
        'Improve stability of the release' => 0,
        'Make tagged release available' => 0,
        'Fix info.yml/composer.json errors found' => 0,
        'Run Rector to fix all errors' => 0,
        'Run Rector to fix some errors' => 0,
        'Manually review and fix errors' => 0,
        'Resolve pre-scanning errors' => 0,
      ],
    ];
    foreach($projects as $project) {
      if ($project[1] == 'Resolve pre-scanning errors') {
        $summary['results'][$project[1]]++;
      }
      else {
        $summary['results'][$project[2]]++;
      }
      if ($project[1] != 'Release as Drupal 10-ready') {
        $summary['total'] += $project[8];
        $summary['errors']['Info.yml or composer.json'] += $project[18];
        $summary['errors']['Drupal API, rector covered'] += $project[11];
        $summary['errors']['Drupal API, not rector covered'] += $project[12];
        $summary['errors']['Symfony API'] += $project[13];
        $summary['errors']['Twig API'] += $project[14];
        $summary['errors']['PHPUnit API'] += $project[15];
        $summary['errors']['Guzzle API'] += $project[16];
        $summary['errors']['Frontend API'] += $project[17];
        $summary['errors']['Other problem'] += $project[19];
        $summary['errors']['Parse error'] += $project[9];
      }
    }

    // Update project results with percentages in labels.
    $results = [];
    foreach ($summary['results'] as $key => $result) {
      $percentage = count($projects) ? round($result / (count($projects) / 100), 2) : 0;
      $results[$key . ' (' . $percentage . '%)'] = $result;
    }
    $summary['results'] = $results;

    // Update error results with percentages in labels.
    if ($summary['total'] > 0) {
      $errors = [];
      foreach ($summary['errors'] as $key => $result) {
        $percentage = round($result / ($summary['total'] / 100), 2);
        $errors[$key . ' (' . $percentage . '%)'] = $result;
      }
      $summary['errors'] = $errors;
    }

    return $summary;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc