Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 151 |
| TrackCommand | |
0.00% |
0 / 1 |
|
0.00% |
0 / 12 |
1190 | |
0.00% |
0 / 151 |
| getBaseDir | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getGeneratedDir | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 4 |
|||
| getConfigDir | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getConfigPath | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| initializeTrackDate | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
| getTrackDate | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getReportPath | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 5 |
|||
| configure | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 24 |
|||
| interact | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 23 |
|||
| execute | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 67 |
|||
| loadDataSeries | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 14 |
|||
| getAbsoluteDirPath | |
0.00% |
0 / 1 |
20 | |
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); | |
| } | |
| } |