Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 151
TrackCommand
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 12
1190
0.00% covered (danger)
0.00%
0 / 151
 getBaseDir
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getGeneratedDir
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 4
 getConfigDir
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getConfigPath
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 initializeTrackDate
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 getTrackDate
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 getReportPath
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
 configure
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 24
 interact
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 23
 execute
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 67
 loadDataSeries
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 14
 getAbsoluteDirPath
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 6
<?php
namespace Alxvng\QATracker\Command;
use Alxvng\QATracker\Chart\Chart;
use Alxvng\QATracker\Chart\ChartGenerator;
use Alxvng\QATracker\Configuration\Configuration;
use Alxvng\QATracker\DataProvider\Model\AbstractDataSerie;
use Alxvng\QATracker\DataProvider\Model\DataPercentSerie;
use Alxvng\QATracker\DataProvider\Model\DataStandardSerie;
use Alxvng\QATracker\Root\Root;
use Alxvng\QATracker\Twig\TwigFactory;
use DateTime;
use Goat1000\SVGGraph\SVGGraph;
use JsonException;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
class TrackCommand extends Command
{
    public const VERSION = 'v0.6.0';
    public const EXIT_SUCCESS = 0;
    public const EXIT_FAILURE = 1;
    public const BASE_DIR = '.qatracker';
    public const GENERATED_DIR = 'generated';
    public const REPORT_DIR = 'report';
    public const REPORT_FILENAME = 'index.html';
    public const DEFAULT_TEMPLATE = 'index.html.twig';
    public const CONFIG_FILENAME = 'config.yaml';
    protected const OUTPUT_DONE = ' <fg=green>done</>.';
    protected static ?string $baseDir = null;
    protected static ?string $configDir = null;
    protected static DateTime $trackDate;
    protected static $defaultName = 'track';
    public static function getBaseDir(): string
    {
        return static::$baseDir ?? Root::external();
    }
    public static function getGeneratedDir(): string
    {
        $dir = static::getConfigDir().'/'.static::GENERATED_DIR;
        if (!is_dir($dir) && !mkdir($dir, 0777, true)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $dir));
        }
        return $dir;
    }
    public static function getConfigDir(): string
    {
        return static::$configDir ?? static::getBaseDir().'/'.static::BASE_DIR;
    }
    public static function getConfigPath(): string
    {
        return static::getConfigDir().'/'.static::CONFIG_FILENAME;
    }
    /**
     * @param $trackDate
     */
    public static function initializeTrackDate($trackDate): void
    {
        static::$trackDate = DateTime::createFromFormat(AbstractDataSerie::DATE_FORMAT, $trackDate);
        if (!static::$trackDate) {
            throw new \RuntimeException(sprintf('Unable to create date from option with value %s', $trackDate));
        }
    }
    /**
     * @return DateTime|false
     */
    public static function getTrackDate()
    {
        return static::$trackDate;
    }
    protected static function getReportPath()
    {
        $path = static::getGeneratedDir().'/'.static::REPORT_DIR.'/'.static::REPORT_FILENAME;
        $dir = dirname($path);
        if (!is_dir($dir) && !mkdir($dir, 0777, true)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $dir));
        }
        return $path;
    }
    protected function configure()
    {
        $outputDir = static::getGeneratedDir().'/'.static::REPORT_DIR;
        $this
            ->setDescription('Track your QA indicators')
            ->setHelp('This command allows you to fetch some indicators and build simple QA charts...')
            ->addOption(
                'date',
                null,
                InputOption::VALUE_REQUIRED,
                sprintf('Use this date instead today to collect data (use format "%s")', AbstractDataSerie::DATE_FORMAT),
                (new DateTime())->format(AbstractDataSerie::DATE_FORMAT)
            )
            ->addOption('no-report', null, InputOption::VALUE_NONE, 'Do not execute the report rendering step')
            ->addOption('no-track', null, InputOption::VALUE_NONE, 'Do not execute the tracking step')
            ->addOption(
                'base-dir',
                null,
                InputOption::VALUE_REQUIRED,
                'Define a custom base directory for qatracker',
                static::getBaseDir()
            )
            ->addOption(
                'config-dir',
                null,
                InputOption::VALUE_REQUIRED,
                'Define a custom config directory for qatracker',
                static::getConfigDir()
            )
        ;
    }
    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);
        $baseDir = (string) $input->getOption('base-dir') ?: static::getConfigDir();
        $configDir = (string) $input->getOption('config-dir') ?: static::getConfigDir();
        static::$baseDir = $this->getAbsoluteDirPath($baseDir);
        static::$configDir = $this->getAbsoluteDirPath($configDir);
        if (file_exists(static::getConfigPath())) {
            return;
        }
        $helper = $this->getHelper('question');
        $question = new ConfirmationQuestion(
            sprintf(
                "\nFile %s does not exists.\nDo you want to create it from the sample file ? (Y/n)",
                static::getConfigPath()
            ),
            true
        );
        if (!$helper->ask($input, $output, $question)) {
            $io->warning(sprintf('The config file has not been created'));
        } else {
            $fs = new Filesystem();
            $fs->copy(Configuration::exampleConfigPath(), static::getConfigPath());
            $io->success(sprintf(
                "The config file has been created at \"%s\".\nYou can now edit it to put your own configuration.",
                static::getConfigPath()
            ));
        }
        $this->setCode(function () {
            return static::EXIT_FAILURE;
        });
    }
    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @throws RuntimeError
     * @throws SyntaxError
     * @throws JsonException
     * @throws LoaderError
     *
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);
        $twig = TwigFactory::getTwig();
        try {
            $io->title('Track your QA indicators');
            $io->write('Configuration');
            $io->listing(
                [
                    'Files read from : '.static::getBaseDir(),
                    'Config read from : '.static::getConfigPath(),
                    'Generated files write in : '.static::getGeneratedDir(),
                ]
            );
            /** @var ConsoleSectionOutput $section */
            $section = $output->section();
            $message = 'Initializing...';
            $section->writeln($message);
            $trackDate = $input->getOption('date');
            static::initializeTrackDate($trackDate);
            $config = Configuration::load(static::getConfigPath());
            $section->overwrite($message.static::OUTPUT_DONE);
            /** @var ConsoleSectionOutput $section */
            $section = $output->section();
            $message = 'Loading data series...';
            $section->writeln($message);
            $dataSeriesConfig = $config['qatracker']['dataSeries'];
            $dataSeriesStack = $this->loadDataSeries($dataSeriesConfig);
            $section->overwrite($message.static::OUTPUT_DONE);
            $io->newLine();
            $withTrack = !$input->getOption('no-track');
            $withReport = !$input->getOption('no-report');
            $graphs = [];
            if ($withTrack) {
                /** @var AbstractDataSerie $dataSerie */
                foreach ($dataSeriesStack as $dataSerie) {
                    /** @var ConsoleSectionOutput $section */
                    $section = $output->section();
                    $message = sprintf('Collecting new indicator for "%s"...', $dataSerie->getId());
                    $section->writeln($message);
                    $dataSerie->collect(static::getTrackDate());
                    $section->overwrite($message.static::OUTPUT_DONE);
                }
                $io->newLine();
            }
            if ($withReport) {
                /** @var ConsoleSectionOutput $section */
                $section = $output->section();
                $message = 'Generating charts : ';
                $section->writeln($message);
                $charts = $config['qatracker']['charts'];
                foreach ($charts as $chart) {
                    $chart = new Chart($chart, $dataSeriesStack);
                    $graphs[] = ChartGenerator::generate(
                        $chart->getFirstProvider()->getData(),
                        $chart->getType(),
                        $chart->getGraphSettings()
                    );
                    $message .= '.';
                    $section->overwrite($message);
                }
                $io->newLine();
            }
            if (!empty($graphs)) {
                /** @var ConsoleSectionOutput $section */
                $section = $output->section();
                $message = sprintf('Rendering report...');
                $section->writeln($message);
                $html = $twig->render(static::DEFAULT_TEMPLATE, [
                    'graphs' => $graphs,
                    'js' => SVGGraph::fetchJavascript(),
                    'version' => static::VERSION,
                    'generatedAt' => new DateTime(),
                ]);
                file_put_contents(static::getReportPath(), $html);
                $section->overwrite($message.static::OUTPUT_DONE);
                $io->newLine();
            }
            $io->success(
                [
                    sprintf(
                        "Well done ! You have track new QA indicators !\n".
                        'Report generated at : %s',
                        static::getReportPath()
                    ),
                ]
            );
            return static::EXIT_SUCCESS;
        } catch (\RuntimeException $e) {
            $io->error($e->getMessage());
            return static::EXIT_FAILURE;
        }
    }
    /**
     * @param $providersConfig
     *
     * @throws JsonException
     *
     * @return array
     */
    protected function loadDataSeries($providersConfig): array
    {
        $dataSeriesStack = [];
        /*
         * Load standard providers on a first time
         */
        foreach ($providersConfig as $key => $provider) {
            if (!AbstractDataSerie::isStandard($provider)) {
                continue;
            }
            $provider = new DataStandardSerie($provider, static::getBaseDir(), static::getGeneratedDir());
            $dataSeriesStack[$provider->getId()] = $provider;
            unset($providersConfig[$key]);
        }
        /*
         * Load other providers on a second time
         */
        foreach ($providersConfig as $key => $provider) {
            if (!AbstractDataSerie::isPercent($provider)) {
                continue;
            }
            $provider = new DataPercentSerie($provider, static::getGeneratedDir(), $dataSeriesStack);
            $dataSeriesStack[$provider->getId()] = $provider;
            unset($providersConfig[$key]);
        }
        return $dataSeriesStack;
    }
    /**
     * @param string $dirPath
     *
     * @return string
     */
    protected function getAbsoluteDirPath(string $dirPath): string
    {
        $parent = dirname($dirPath);
        if (!is_dir($parent)) {
            throw new RuntimeException(sprintf('Unable to read directory "%s"', $parent));
        }
        if (!is_dir($dirPath) && !mkdir($dirPath, 0777, true)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $dirPath));
        }
        return realpath($dirPath);
    }
}