diff --git a/.travis.yml b/.travis.yml index 9e888de..054d397 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: php sudo: false php: - - 5.6 - - 5.5 - 7.1 - 7.2 @@ -18,5 +16,6 @@ cache: script: - composer install + - ./vendor/bin/phpcs --report=checkstyle - phpunit --coverage-text diff --git a/Readme.md b/Readme.md index ec26e33..b595142 100644 --- a/Readme.md +++ b/Readme.md @@ -2,13 +2,13 @@ ## Command line time-logger * Entries stored in sqlite in your home directory -* Sends complete entries to redmine +* Sends complete entries to redmine or jira, or both at the same time ## Requirements -* PHP 5.5.23+ -* php5-sqlite3 OR php7.0-sqlite3 -* php5-pdo_sqlite OR php7.0-pdo_sqlite +* PHP 7.1+ +* php7.1-sqlite3 +* php7.1-pdo_sqlite ## Installation @@ -57,7 +57,7 @@ tl self-update ## Usage -Assuming you have two redmine tickets number 3546 and 4791. +Assuming you have two tickets number 3546 and 4791. ```bash # start work on 3546 tl start 3546 diff --git a/bin/tl.php b/bin/tl.php index 5783430..3f6c365 100755 --- a/bin/tl.php +++ b/bin/tl.php @@ -18,6 +18,7 @@ $loader->load('services.yml'); $home = $_SERVER['HOME']; $container->setParameter('directory', $home); +$container->set('container', $container); $application = new Application('Time logger', '@package_version@', $container); $application->run(); diff --git a/composer.json b/composer.json index ab61f31..6ad03ea 100644 --- a/composer.json +++ b/composer.json @@ -12,12 +12,14 @@ "herrera-io/phar-update": "^2.0", "stecman/symfony-console-completion": "^0.6.0", "monolog/monolog": "^1.17", - "php": ">=5.5.23", + "php": ">=7.1", "ext-sqlite3": "*", - "ext-pdo_sqlite": "*" + "ext-pdo_sqlite": "*", + "lesstif/php-jira-rest-client": "^1.35" }, "require-dev": { - "phpunit/phpunit": "^4.8" + "phpunit/phpunit": "~6", + "drupal/coder": "~8" }, "license": "GPL2", "autoload": { diff --git a/composer.lock b/composer.lock index a6d932a..dc1d0da 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "ef372f1c94bfaf2cfd83abe221c64aec", + "content-hash": "2afe9257c203b8242097939279ba9982", "packages": [ { "name": "doctrine/annotations", @@ -893,6 +893,66 @@ ], "time": "2016-01-25T15:43:01+00:00" }, + { + "name": "lesstif/php-jira-rest-client", + "version": "1.35.0", + "source": { + "type": "git", + "url": "https://github.com/lesstif/php-jira-rest-client.git", + "reference": "45987fd5b66e80cfb64d94955f478136d03eb597" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lesstif/php-jira-rest-client/zipball/45987fd5b66e80cfb64d94955f478136d03eb597", + "reference": "45987fd5b66e80cfb64d94955f478136d03eb597", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "monolog/monolog": "~1.12", + "netresearch/jsonmapper": "~0.11|^1.0", + "php": ">=5.5.9", + "vlucas/phpdotenv": "~1.0|~2.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": ">=5.7 <6", + "symfony/var-dumper": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "JiraRestApi\\JiraRestApiServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "JiraRestApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "KwangSeob Jeong", + "email": "lesstif@gmail.com", + "homepage": "http://lesstif.com/" + } + ], + "description": "JIRA REST API Client for PHP Users.", + "keywords": [ + "jira", + "jira-php", + "jira-rest", + "rest" + ], + "time": "2019-01-07T15:34:05+00:00" + }, { "name": "monolog/monolog", "version": "1.23.0", @@ -971,6 +1031,48 @@ ], "time": "2017-06-19T01:22:40+00:00" }, + { + "name": "netresearch/jsonmapper", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "3868fe1128ce1169228acdb623359dca74db5ef3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/3868fe1128ce1169228acdb623359dca74db5ef3", + "reference": "3868fe1128ce1169228acdb623359dca74db5ef3", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "time": "2017-11-28T21:30:01+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -1555,37 +1657,87 @@ "description": "Symfony Yaml Component", "homepage": "https://symfony.com", "time": "2018-01-03T07:36:31+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "cfd5dc225767ca154853752abc93aeec040fcf36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/cfd5dc225767ca154853752abc93aeec040fcf36", + "reference": "cfd5dc225767ca154853752abc93aeec040fcf36", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2018-10-30T17:29:25+00:00" } ], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { "athletic/athletic": "~0.1.8", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -1610,7 +1762,194 @@ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2017-07-22T11:58:36+00:00" + }, + { + "name": "drupal/coder", + "version": "8.3.1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/coder.git", + "reference": "29a25627e7148b3119c84f18e087fc3b8c85b959" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0", + "squizlabs/php_codesniffer": "^3.0.1", + "symfony/yaml": ">=2.0.0" + }, + "require-dev": { + "phpunit/phpunit": ">=3.7 <6" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-0": { + "Drupal\\": "coder_sniffer/Drupal/", + "DrupalPractice\\": "coder_sniffer/Drupal/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "description": "Coder is a library to review Drupal code.", + "homepage": "https://www.drupal.org/project/coder", + "keywords": [ + "code review", + "phpcs", + "standards" + ], + "time": "2018-09-21T14:22:49+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2018-06-11T23:09:50+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -1668,29 +2007,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.2.2", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157" + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157", - "reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", "shasum": "" }, "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.3.0", + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", "webmozart/assert": "^1.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, "autoload": { "psr-4": { "phpDocumentor\\Reflection\\": [ @@ -1709,20 +2054,20 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-08-08T06:39:58+00:00" + "time": "2017-11-30T07:14:17+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.3.0", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773" + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fb3933512008d8162b3cdf9e18dba9309b7c3773", - "reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", "shasum": "" }, "require": { @@ -1756,37 +2101,37 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-06-03T08:32:36+00:00" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpspec/prophecy", - "version": "1.7.5", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -1819,43 +2164,44 @@ "spy", "stub" ], - "time": "2018-02-19T10:16:54+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "2.2.4", + "version": "5.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", "shasum": "" }, "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0" + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" }, "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~4" + "phpunit/phpunit": "^6.0" }, "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.2.1", - "ext-xmlwriter": "*" + "ext-xdebug": "^2.5.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev" + "dev-master": "5.3.x-dev" } }, "autoload": { @@ -1870,7 +2216,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1881,7 +2227,7 @@ "testing", "xunit" ], - "time": "2015-10-06T15:47:00+00:00" + "time": "2018-04-06T15:36:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2022,29 +2368,29 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.12", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + "reference": "791198a2c6254db10131eecfe8c06670700904db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.2.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2067,45 +2413,57 @@ "keywords": [ "tokenizer" ], - "time": "2017-12-04T08:55:13+00:00" + "time": "2017-11-27T05:48:46+00:00" }, { "name": "phpunit/phpunit", - "version": "4.8.36", + "version": "6.5.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", - "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", "shasum": "" }, "require": { "ext-dom": "*", "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "~2.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.2.2", - "sebastian/diff": "~1.2", - "sebastian/environment": "~1.3", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/version": "~1.0", - "symfony/yaml": "~2.1|~3.0" + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" }, "suggest": { - "phpunit/php-invoker": "~1.1" + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" }, "bin": [ "phpunit" @@ -2113,7 +2471,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.8.x-dev" + "dev-master": "6.5.x-dev" } }, "autoload": { @@ -2139,30 +2497,33 @@ "testing", "xunit" ], - "time": "2017-06-21T08:07:12+00:00" + "time": "2018-09-08T15:10:43+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "2.3.8", + "version": "5.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": ">=5.3.3", - "phpunit/php-text-template": "~1.2", - "sebastian/exporter": "~1.2" + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.5.11" }, "suggest": { "ext-soap": "*" @@ -2170,7 +2531,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3.x-dev" + "dev-master": "5.0.x-dev" } }, "autoload": { @@ -2185,7 +2546,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -2195,34 +2556,79 @@ "mock", "xunit" ], - "time": "2015-10-02T06:51:40+00:00" + "time": "2018-08-09T05:50:03+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" }, { "name": "sebastian/comparator", - "version": "1.2.4", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { @@ -2253,38 +2659,38 @@ } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", "equality" ], - "time": "2017-01-29T09:50:25+00:00" + "time": "2018-02-01T13:46:46+00:00" }, { "name": "sebastian/diff", - "version": "1.4.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2311,32 +2717,32 @@ "keywords": [ "diff" ], - "time": "2017-05-22T07:24:03+00:00" + "time": "2017-08-03T08:09:46+00:00" }, { "name": "sebastian/environment", - "version": "1.3.8", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" + "phpunit/phpunit": "^6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -2361,34 +2767,34 @@ "environment", "hhvm" ], - "time": "2016-08-18T05:49:44+00:00" + "time": "2017-07-01T08:51:00+00:00" }, { "name": "sebastian/exporter", - "version": "1.2.2", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" + "php": "^7.0", + "sebastian/recursion-context": "^3.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -2428,27 +2834,27 @@ "export", "exporter" ], - "time": "2016-06-17T09:04:28+00:00" + "time": "2017-04-03T13:19:02+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-uopz": "*" @@ -2456,7 +2862,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2479,32 +2885,124 @@ "keywords": [ "global state" ], - "time": "2015-10-12T03:26:01+00:00" + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" }, { "name": "sebastian/recursion-context", - "version": "1.0.5", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", - "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -2532,23 +3030,73 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-10-03T07:41:43+00:00" + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" }, { "name": "sebastian/version", - "version": "1.0.6", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", "shasum": "" }, + "require": { + "php": ">=5.6" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -2567,24 +3115,174 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21T13:59:46+00:00" + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "379deb987e26c7cd103a7b387aea178baec96e48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/379deb987e26c7cd103a7b387aea178baec96e48", + "reference": "379deb987e26c7cd103a7b387aea178baec96e48", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-12-19T23:57:18+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-08-06T14:22:27+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" }, { "name": "webmozart/assert", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -2617,7 +3315,7 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2018-12-25T11:19:39+00:00" } ], "aliases": [], @@ -2626,7 +3324,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.5", + "php": ">=5.5.23", "ext-sqlite3": "*", "ext-pdo_sqlite": "*" }, diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..a14c934 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,26 @@ + + + Default PHP CodeSniffer configuration for tl. + ./src + ./tests + + + Commands + tests + + + 0 + + + 0 + + + Repository.php + + + Schema.php + + + Application.php + + diff --git a/services.yml b/services.yml index 6784ff9..20064a2 100644 --- a/services.yml +++ b/services.yml @@ -26,10 +26,20 @@ services: class: Larowlan\Tl\Repository\DbRepository arguments: ["@connection"] connector: + class: Larowlan\Tl\Connector\Manager + arguments: ['@container', '@cache', '%config%', '%version%'] + tags: + - { name: configurable } + connector.redmine: class: Larowlan\Tl\Connector\RedmineConnector arguments: ['@http_client', '@cache', '%config%', '%version%'] tags: - - { name: configurable } + - { name: connector } + connector.jira: + class: Larowlan\Tl\Connector\JiraConnector + arguments: ['%config%', '@cache', '%config%', '%version%', '@http_client'] + tags: + - { name: connector } reviewer: class: Larowlan\Tl\Reviewer arguments: ["@connector", "@repository"] diff --git a/src/Application.php b/src/Application.php index 72caff4..420cce9 100644 --- a/src/Application.php +++ b/src/Application.php @@ -1,8 +1,4 @@ container->get('config.processor'); - /** @var ConfigurationCollector|ConfigurationInterface $configuration */ + /** @var \Larowlan\Tl\Configuration\ConfigurationCollector|ConfigurationInterface|\Larowlan\Tl\Configuration\LoggerConfiguration $configuration */ $configuration = $this->container->get('config.configuration'); + $configuration->setContainerBuilder($this->container); $needs_config_ids = $this->container->getParameter('configurable_service_ids'); $needs_config = []; foreach ($needs_config_ids as $id) { diff --git a/src/CacheFactory.php b/src/CacheFactory.php index f99086f..8318217 100644 --- a/src/CacheFactory.php +++ b/src/CacheFactory.php @@ -1,16 +1,20 @@ connector = $connector; $this->repository = $repository; diff --git a/src/Commands/Assigned.php b/src/Commands/Assigned.php index 1a3a0e2..2fc8396 100644 --- a/src/Commands/Assigned.php +++ b/src/Commands/Assigned.php @@ -1,23 +1,19 @@ connector = $connector; $this->repository = $repository; @@ -56,20 +55,25 @@ protected function execute(InputInterface $input, OutputInterface $output) { $table->setHeaders(['JobId', 'Title', 'Status']); $rows = []; $first = TRUE; - foreach ($data as $project => $tickets) { - if (!$first) { - $rows[] = new TableSeparator(); - } - $rows[] = ['', '' . $project . '']; - $rows[] = new TableSeparator(); - foreach ($tickets as $id => $ticket_info) { + foreach ($data as $connector_id => $connector_data) { + foreach ($connector_data as $project => $tickets) { + if (!$first) { + $rows[] = new TableSeparator(); + } $rows[] = [ - $id, - $ticket_info['title'], - $ticket_info['status'], + '', + sprintf('%s [%s]', $project, $connector_id), ]; + $rows[] = new TableSeparator(); + foreach ($tickets as $id => $ticket_info) { + $rows[] = [ + $id, + substr($ticket_info['title'], 0, 50) . '...', + $ticket_info['status'], + ]; + } + $first = FALSE; } - $first = FALSE; } $table->setRows($rows); $table->render(); diff --git a/src/Commands/Billable.php b/src/Commands/Billable.php index a8a8d3d..5d7a573 100644 --- a/src/Commands/Billable.php +++ b/src/Commands/Billable.php @@ -1,8 +1,4 @@ connector = $connector; $this->repository = $repository; - $config = static::getDefaults($config); + $config = static::getDefaults($config, new ContainerBuilder()); $this->billablePercentage = $config['billable_percentage']; $this->hoursPerDay = $config['hours_per_day']; $this->targets = $config['days_per_month']; @@ -162,27 +165,29 @@ protected function execute(InputInterface $input, OutputInterface $output) { $projects = []; $billable_projects = []; $non_billable_projects = []; - foreach ($this->repository->totalByTicket($date->getTimestamp(), $end->getTimestamp()) as $tid => $duration) { - $details = $this->connector->ticketDetails($tid); - if ($details) { - if (!isset($projects[$details->getProjectId()])) { - $projects[$details->getProjectId()] = 0; - } - if ($details->isBillable()) { - $billable += $duration; - $projects[$details->getProjectId()] += $duration; - $billable_projects[$details->getProjectId()] = $details->getProjectId(); + foreach ($this->repository->totalByTicket($date->getTimestamp(), $end->getTimestamp()) as $connector_id => $items) { + foreach ($items as $tid => $duration) { + $details = $this->connector->ticketDetails($tid, $connector_id); + if ($details) { + if (!isset($projects[$connector_id][$details->getProjectId()])) { + $projects[$connector_id][$details->getProjectId()] = 0; + } + if ($details->isBillable()) { + $billable += $duration; + $projects[$connector_id][$details->getProjectId()] += $duration; + $billable_projects[$connector_id][$details->getProjectId()] = $details->getProjectId(); + } + else { + $non_billable += $duration; + $projects[$connector_id][$details->getProjectId()] += $duration; + $non_billable_projects[$connector_id][$details->getProjectId()] = $details->getProjectId(); + } } else { - $non_billable += $duration; - $projects[$details->getProjectId()] += $duration; - $non_billable_projects[$details->getProjectId()] = $details->getProjectId(); + $unknown += $duration; + $unknowns[] = $tid; } } - else { - $unknown += $duration; - $unknowns[] = $tid; - } } $table = new Table($output); if (!$project) { @@ -199,29 +204,43 @@ protected function execute(InputInterface $input, OutputInterface $output) { if ($project) { $project_names = $this->connector->projectNames(); $rows[] = ['Billable', '', '', '']; - foreach ($billable_projects as $project_id) { - $project_name = isset($project_names[$project_id]) ? $project_names[$project_id] : "Project ID $project_id"; - $rows[] = ['', $project_name, Formatter::formatDuration($projects[$project_id]), '']; + foreach ($billable_projects as $connector_id => $connector_projects) { + foreach ($connector_projects as $project_id) { + $project_name = isset($project_names[$connector_id][$project_id]) ? $project_names[$connector_id][$project_id] : "Project ID $project_id"; + $rows[] = [ + '', + $project_name, + Formatter::formatDuration($projects[$connector_id][$project_id]), + '', + ]; + } } $rows[] = new TableSeparator(); $rows[] = [ 'Billable', '', Formatter::formatDuration($billable), - "<$tag>" . ($total ? round(100 * $billable / $total, 2) : 0) . "%" + "<$tag>" . ($total ? round(100 * $billable / $total, 2) : 0) . "%", ]; $rows[] = new TableSeparator(); $rows[] = ['Non-Billable', '', '', '']; - foreach ($non_billable_projects as $project_id) { - $project_name = isset($project_names[$project_id]) ? $project_names[$project_id] : "Project ID $project_id"; - $rows[] = ['', $project_name, Formatter::formatDuration($projects[$project_id]), '']; + foreach ($non_billable_projects as $connector_id => $connector_projects) { + foreach ($connector_projects as $project_id) { + $project_name = isset($project_names[$connector_id][$project_id]) ? $project_names[$connector_id][$project_id] : "Project ID $project_id"; + $rows[] = [ + '', + $project_name, + Formatter::formatDuration($projects[$connector_id][$project_id]), + '', + ]; + } } $rows[] = new TableSeparator(); $rows[] = [ 'Non-billable', '', Formatter::formatDuration($non_billable), - round(100 * $non_billable / $total, 2) . '%' + round(100 * $non_billable / $total, 2) . '%', ]; if ($unknown) { $rows[] = new TableSeparator(); @@ -236,12 +255,12 @@ protected function execute(InputInterface $input, OutputInterface $output) { $rows[] = [ 'Billable', Formatter::formatDuration($billable), - "<$tag>" . ($total ? round(100 * $billable / $total, 2) : 0) . "%" + "<$tag>" . ($total ? round(100 * $billable / $total, 2) : 0) . "%", ]; $rows[] = [ 'Non-billable', Formatter::formatDuration($non_billable), - ($total ? round(100 * $non_billable / $total, 2) : 0) . '%' + ($total ? round(100 * $non_billable / $total, 2) : 0) . '%', ]; if ($unknown) { $rows[] = ['Unknown*', Formatter::formatDuration($unknown), round(100 * $unknown / $total, 2) . '%']; @@ -280,6 +299,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { $table->render(); } + /** + * + */ protected function getTotalMonthHours($m, $y) { $target_key = sprintf('%s_%s', $y, $m); // If we have no customisations it's just number of days times hours per @@ -340,10 +362,13 @@ protected function formatProgressRow($caption, $numerator, $denominator, $expect return [ $caption, "$numerator/$denominator ($difference)", - round(100 * $numerator / $denominator, 2) . '%' + round(100 * $numerator / $denominator, 2) . '%', ]; } + /** + * + */ protected function getWeekdaysInMonth($m, $y) { $target_key = sprintf('%s_%s', $y, $m); if (isset($this->targets[$target_key])) { @@ -364,6 +389,9 @@ protected function getWeekdaysInMonth($m, $y) { return $weekdays + 20; } + /** + * + */ protected function getWeekdaysPassedThisMonth($output, \DateTime $reference_point) { $days_passed = $reference_point->format('d'); if ($reference_point->format('Y-m-t') < date('Y-m-d')) { @@ -401,17 +429,17 @@ protected function getWeekdaysPassedThisMonth($output, \DateTime $reference_poin /** * {@inheritdoc} */ - public static function getConfiguration(NodeDefinition $root_node) { + public static function getConfiguration(NodeDefinition $root_node, ContainerBuilder $container) { $root_node->children() - ->scalarNode('billable_percentage') - ->defaultValue(0.8) - ->end() - ->scalarNode('hours_per_day') - ->defaultValue(8) - ->end() - ->arrayNode('days_per_month') - ->prototype('scalar') - ->end() + ->scalarNode('billable_percentage') + ->defaultValue(0.8) + ->end() + ->scalarNode('hours_per_day') + ->defaultValue(8) + ->end() + ->arrayNode('days_per_month') + ->prototype('scalar') + ->end() ->end(); return $root_node; } @@ -419,7 +447,7 @@ public static function getConfiguration(NodeDefinition $root_node) { /** * {@inheritdoc} */ - public static function askPreBootQuestions(QuestionHelper $helper, InputInterface $input, OutputInterface $output, array $config) { + public static function askPreBootQuestions(QuestionHelper $helper, InputInterface $input, OutputInterface $output, array $config, ContainerBuilder $container) { $default_percentage = isset($config['billable_percentage']) ? $config['billable_percentage'] : 0.8; $default_hours_per_day = isset($config['hours_per_day']) ? $config['hours_per_day'] : 8; $config = ['billable_percentage' => '', 'hours_per_day' => ''] + $config; @@ -441,7 +469,7 @@ public function askPostBootQuestions(QuestionHelper $helper, InputInterface $inp /** * {@inheritdoc} */ - public static function getDefaults($config) { + public static function getDefaults($config, ContainerBuilder $container) { return $config + [ 'billable_percentage' => 0.8, 'hours_per_day' => 8, diff --git a/src/Commands/Bitbar.php b/src/Commands/Bitbar.php index 4749ff1..acc8643 100644 --- a/src/Commands/Bitbar.php +++ b/src/Commands/Bitbar.php @@ -1,69 +1,65 @@ connector = $connector; - $this->repository = $repository; - parent::__construct(); - } + /** + * + */ + public function __construct(Connector $connector, Repository $repository) { + $this->connector = $connector; + $this->repository = $repository; + parent::__construct(); + } - /** - * {@inheritdoc} - */ - protected function configure() { - $this - ->setName('bitbar') - ->setDescription('Bitbar output') - ->setHelp('Bitbar outputUsage: tl bitbar') - ->addUsage('tl bitbar'); - } + /** + * {@inheritdoc} + */ + protected function configure() { + $this + ->setName('bitbar') + ->setDescription('Bitbar output') + ->setHelp('Bitbar outputUsage: tl bitbar') + ->addUsage('tl bitbar'); + } - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) { - if ($open = $this->repository->getActive()) { - $text = $open->tid . ': ' . Formatter::formatDuration(time() - $open->start) . ' '; - } - else { - $text = 'Inactive '; - } - $total = 0; - foreach ($this->repository->review(Total::ALL) as $data) { - $total += $data->duration; - } - $text .= '(' . $total . 'h)'; - $output->writeln($text); + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) { + if ($open = $this->repository->getActive()) { + $text = $open->tid . ': ' . Formatter::formatDuration(time() - $open->start) . ' '; + } + else { + $text = 'Inactive '; + } + $total = 0; + foreach ($this->repository->review(Total::ALL) as $data) { + $total += $data->duration; } + $text .= '(' . $total . 'h)'; + $output->writeln($text); + } } diff --git a/src/Commands/CacheClear.php b/src/Commands/CacheClear.php index 5a64d3c..b06a4aa 100644 --- a/src/Commands/CacheClear.php +++ b/src/Commands/CacheClear.php @@ -1,17 +1,11 @@ cache = $cache; parent::__construct(); diff --git a/src/Commands/Combine.php b/src/Commands/Combine.php index 0788d99..d2021ba 100644 --- a/src/Commands/Combine.php +++ b/src/Commands/Combine.php @@ -1,12 +1,9 @@ connector = $connector; $this->repository = $repository; @@ -56,13 +59,14 @@ protected function execute(InputInterface $input, OutputInterface $output) { // Create a new combined entry and then remove fields we don't want to keep // around in the new entry. $combined_entry = clone $entry1; - unset($combined_entry->id, $combined_entry->category, $combined_entry->comment, $combined_entry->teid); + unset($combined_entry->id, $combined_entry->category, $combined_entry->comment, $combined_entry->teid, $combined_entry->connector_id); + $combined_entry->connector_id = ':connector_id'; // Extend the entry date by the amount of time logged in the second entry. $combined_entry->end = $entry1->end + ($entry2->end - $entry2->start); // Insert the new entry, if all is well delete the two existing ones. - if ($new_slot = $this->repository->insert((array) $combined_entry)) { + if ($new_slot = $this->repository->insert((array) $combined_entry, [':connector_id' => $entry1->connector_id])) { $this->repository->delete($entry1->id); $this->repository->delete($entry2->id); @@ -70,6 +74,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { } } + /** + * + */ protected function validateSlots($slot1, $slot2, OutputInterface $output) { if ($slot1 === $slot2) { throw new \InvalidArgumentException('You cannot combine a slot with itself.'); @@ -85,6 +92,9 @@ protected function validateSlots($slot1, $slot2, OutputInterface $output) { if (!$entry2 = $this->repository->slot($slot2)) { throw new \InvalidArgumentException(sprintf('Invalid slot id %s', $slot2)); } + if ($entry1->connector_id !== $entry2->connector_id) { + throw new \InvalidArgumentException(sprintf('You cannot combine slots from %s backend with slots from %s backend', Manager::formatConnectorId($entry2->connector_id), Manager::formatConnectorId($entry1->connector_id))); + } // Ensure we've not already sent the slots. if (!empty($entry1->teid) || !empty($entry1->teid)) { throw new \InvalidArgumentException('You cannot combine entries that have already been sent.'); @@ -96,9 +106,12 @@ protected function validateSlots($slot1, $slot2, OutputInterface $output) { return [$entry1, $entry2]; } + /** + * + */ protected function stopTicket($slot_id, OutputInterface $output) { if ($stop = $this->repository->stop($slot_id)) { - $stopped = $this->connector->ticketDetails($stop->tid); + $stopped = $this->connector->ticketDetails($stop->tid, $stop->connector_id); $output->writeln(sprintf('Closed slot %d against ticket %d: %s, duration %s', $stop->id, $stop->tid, @@ -107,4 +120,5 @@ protected function stopTicket($slot_id, OutputInterface $output) { )); } } + } diff --git a/src/Commands/Comment.php b/src/Commands/Comment.php index 65d71b3..ec9d57a 100644 --- a/src/Commands/Comment.php +++ b/src/Commands/Comment.php @@ -1,23 +1,18 @@ connector = $connector; $this->repository = $repository; @@ -62,7 +60,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { if ($entry->comment && !$input->getOption('recomment')) { continue; } - $title = $this->connector->ticketDetails($entry->tid); + $title = $this->connector->ticketDetails($entry->tid, $entry->connector_id); $question = new Question( sprintf('Enter comment for slot %d [%d]: %s [%s h] [%s]', $entry->id, diff --git a/src/Commands/Configure.php b/src/Commands/Configure.php index 8457b2e..4cfc60f 100644 --- a/src/Commands/Configure.php +++ b/src/Commands/Configure.php @@ -1,12 +1,7 @@ directory = $directory; $this->configurableServiceIds = $configurable_service_ids; @@ -56,15 +54,15 @@ protected function execute(InputInterface $input, OutputInterface $output) { } foreach ($this->configurableServiceIds as $service_id) { $service_definition = $this->container->getDefinition($service_id); - /** @var ConfigurableService $service_class */ + /** @var \Larowlan\Tl\Configuration\ConfigurableService $service_class */ $service_class = $service_definition->getClass(); - $config = $service_class::getDefaults($config); - $config = $service_class::askPreBootQuestions($helper, $input, $output, $config); + $config = $service_class::getDefaults($config, $this->container); + $config = $service_class::askPreBootQuestions($helper, $input, $output, $config, $this->container); } $this->container->setParameter('config', $config); // Now we can attempt boot. foreach ($this->configurableServiceIds as $service_id) { - /** @var ConfigurableService $service */ + /** @var \Larowlan\Tl\Configuration\ConfigurableService $service */ $service = $this->container->get($service_id); $config = $service->askPostBootQuestions($helper, $input, $output, $config); } @@ -79,4 +77,5 @@ protected function execute(InputInterface $input, OutputInterface $output) { public function setContainerBuilder(ContainerBuilder $container) { $this->container = $container; } + } diff --git a/src/Commands/ContainerAwareCommand.php b/src/Commands/ContainerAwareCommand.php index eb5d907..1629ebf 100644 --- a/src/Commands/ContainerAwareCommand.php +++ b/src/Commands/ContainerAwareCommand.php @@ -1,8 +1,4 @@ connector = $connector; $this->repository = $repository; @@ -55,8 +57,8 @@ protected function execute(InputInterface $input, OutputInterface $output) { $slot = $this->repository->latest(); } if ($slot) { - $details = $this->connector->ticketDetails($slot->tid); - list($slot_id, $continued) = $this->repository->start($slot->tid, $slot->comment, $slot->id); + $details = $this->connector->ticketDetails($slot->tid, $slot->connector_id); + list($slot_id, $continued) = $this->repository->start($slot->tid, $slot->connector_id, $slot->comment, $slot->id); $output->writeln(sprintf('[%s] %s entry for %d: %s [slot:%d]', (new \DateTime())->format('h:i'), $continued ? 'Continued' : 'Started new', diff --git a/src/Commands/Delete.php b/src/Commands/Delete.php index ca3ff36..2f3cbb7 100644 --- a/src/Commands/Delete.php +++ b/src/Commands/Delete.php @@ -1,8 +1,4 @@ connector = $connector; $this->repository = $repository; @@ -54,11 +56,11 @@ protected function configure() { protected function execute(InputInterface $input, OutputInterface $output) { $slot_id = $input->getArgument('slot_id'); $helper = $this->getHelper('question'); - $question = new ConfirmationQuestion('Are you sure?', false); + $question = new ConfirmationQuestion('Are you sure?', FALSE); $confirm = NULL; if (($slot = $this->repository->slot($slot_id)) && ($confirm = ($input->getOption('confirm') || $helper->ask($input, $output, $question))) && $this->repository->delete($slot_id)) { - $deleted = $this->connector->ticketDetails($slot->tid); + $deleted = $this->connector->ticketDetails($slot->tid, $slot->connector_id); $output->writeln(sprintf('Deleted slot %d against ticket %d: %s, duration %s', $slot->id, $slot->tid, diff --git a/src/Commands/Edit.php b/src/Commands/Edit.php index 4edba35..a6c7181 100644 --- a/src/Commands/Edit.php +++ b/src/Commands/Edit.php @@ -1,20 +1,17 @@ connector = $connector; $this->repository = $repository; diff --git a/src/Commands/Install.php b/src/Commands/Install.php index 6fc1e24..e546205 100644 --- a/src/Commands/Install.php +++ b/src/Commands/Install.php @@ -1,12 +1,7 @@ connection = $connection; $this->directory = $directory; diff --git a/src/Commands/Log.php b/src/Commands/Log.php index c411f7d..403e5c6 100644 --- a/src/Commands/Log.php +++ b/src/Commands/Log.php @@ -1,8 +1,4 @@ directory = $directory; parent::__construct(); diff --git a/src/Commands/LogAwareCommand.php b/src/Commands/LogAwareCommand.php index 5ec5d8e..dda014e 100644 --- a/src/Commands/LogAwareCommand.php +++ b/src/Commands/LogAwareCommand.php @@ -1,8 +1,4 @@ connector = $connector; $this->repository = $repository; @@ -59,7 +54,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { $rows = []; foreach ($entries as $entry) { - $details = $this->connector->ticketDetails($entry->tid); + if (!$details = $this->connector->ticketDetails($entry->tid, $entry->connector_id)) { + continue; + } $rows[] = [$entry->tid, $details->getTitle()]; } $table->setRows($rows); diff --git a/src/Commands/Open.php b/src/Commands/Open.php index 276d6ba..e6f6144 100644 --- a/src/Commands/Open.php +++ b/src/Commands/Open.php @@ -1,20 +1,17 @@ connector = $connector; $this->repository = $repository; @@ -48,7 +48,7 @@ protected function configure() { */ protected function execute(InputInterface $input, OutputInterface $output) { if ($data = $this->repository->getActive()) { - $details = $this->connector->ticketDetails($data->tid); + $details = $this->connector->ticketDetails($data->tid, $data->connector_id); $output->writeLn(sprintf('%s [%d] - %s [slot: %d]', $details->getTitle(), $data->tid, diff --git a/src/Commands/PreinstallCommand.php b/src/Commands/PreinstallCommand.php index 468c155..d171ad7 100644 --- a/src/Commands/PreinstallCommand.php +++ b/src/Commands/PreinstallCommand.php @@ -1,8 +1,4 @@ reviewer = $reviewer; parent::__construct(); diff --git a/src/Commands/Send.php b/src/Commands/Send.php index 1fc0f5f..1e46542 100644 --- a/src/Commands/Send.php +++ b/src/Commands/Send.php @@ -1,8 +1,4 @@ connector = $connector; $this->repository = $repository; diff --git a/src/Commands/Start.php b/src/Commands/Start.php index 6bc1089..67143b6 100644 --- a/src/Commands/Start.php +++ b/src/Commands/Start.php @@ -1,13 +1,9 @@ connector = $connector; $this->repository = $repository; parent::__construct(); @@ -48,6 +50,7 @@ protected function configure() { ->addArgument('comment', InputArgument::OPTIONAL, 'Comment to start with') ->addOption('status', 's', InputOption::VALUE_NONE, 'Set issue to in progress') ->addOption('assign', 'a', InputOption::VALUE_NONE, 'Assign issue to you') + ->addOption('backend', 'b', InputOption::VALUE_OPTIONAL, 'Backend to use') ->addOption('redmine-comment', 'r', InputOption::VALUE_REQUIRED, 'Redmine comment') ->addUsage('tl start 12355') ->addUsage('tl start 12355 "Doin stuff"') @@ -70,9 +73,21 @@ protected function execute(InputInterface $input, OutputInterface $output) { if ($alias = $this->repository->loadAlias($ticket_id)) { $ticket_id = $alias; } - if ($title = $this->connector->ticketDetails($ticket_id)) { + if ($connector_id = $input->getOption('backend')) { + $connector_id = 'connector.' . $connector_id; + } + else { + $connector_id = $this->connector->spotConnector($ticket_id, $input, $output); + } + if (!$connector_id) { + throw new \InvalidArgumentException('No such ticket was found in any backends.'); + } + if ($alias = $this->connector->loadAlias($ticket_id, $connector_id)) { + $ticket_id = $alias; + } + if ($title = $this->connector->ticketDetails($ticket_id, $connector_id)) { if ($stop = $this->repository->stop()) { - $stopped = $this->connector->ticketDetails($stop->tid); + $stopped = $this->connector->ticketDetails($stop->tid, $stop->connector_id); $output->writeln(sprintf('Closed slot %d against ticket %d: %s, duration %s', $stop->id, $stop->tid, @@ -81,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { )); } try { - list($slot_id, $continued) = $this->repository->start($ticket_id, $input->getArgument('comment')); + list($slot_id, $continued) = $this->repository->start($ticket_id, $connector_id, $input->getArgument('comment')); $output->writeln(sprintf('[%s] %s entry for %d: %s [slot:%d]', (new \DateTime())->format('h:i'), $continued ? 'Continued' : 'Started new', @@ -90,7 +105,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { $slot_id )); if ($input->getOption('status')) { - if ($this->connector->setInProgress($ticket_id, $assign = $input->getOption('assign'), $input->getOption('redmine-comment') ?: 'Working on this')) { + if ($this->connector->setInProgress($ticket_id, $connector_id, $assign = $input->getOption('assign'), $input->getOption('redmine-comment') ?: 'Working on this')) { $output->writeln(sprintf('Ticket %s set to in-progress.', $ticket_id)); if ($assign) { $output->writeln(sprintf('Ticket %s assigned to you.', $ticket_id)); @@ -104,7 +119,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { } } elseif ($input->getOption('assign')) { - if ($this->connector->assign($ticket_id, $input->getOption('redmine-comment') ?: 'Working on this')) { + if ($this->connector->assign($ticket_id, $connector_id, $input->getOption('redmine-comment') ?: 'Working on this')) { $output->writeln(sprintf('Ticket %s assigned to you.', $ticket_id)); } @@ -118,7 +133,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { } } catch (\Exception $e) { - $output->writeln(sprintf('Error creating slot: %s', $e->getMessage())); + $output->writeln(sprintf('Error creating slot: %s', $e->getMessage())); } } else { diff --git a/src/Commands/Status.php b/src/Commands/Status.php index a74b174..21205e8 100644 --- a/src/Commands/Status.php +++ b/src/Commands/Status.php @@ -1,12 +1,7 @@ connector = $connector; $this->repository = $repository; @@ -64,7 +65,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { foreach ($data as $record) { $record->duration = ($record->end ?: time()) - $record->start; $total += $record->duration; - $details = $this->connector->ticketDetails($record->tid); + $details = $this->connector->ticketDetails($record->tid, $record->connector_id); if (empty($record->end)) { $record->tid .= ' *'; } diff --git a/src/Commands/Stop.php b/src/Commands/Stop.php index f16f5ae..a2575a0 100644 --- a/src/Commands/Stop.php +++ b/src/Commands/Stop.php @@ -1,21 +1,18 @@ connector = $connector; $this->repository = $repository; @@ -54,7 +54,7 @@ protected function configure() { */ protected function execute(InputInterface $input, OutputInterface $output) { if ($stop = $this->repository->stop()) { - $stopped = $this->connector->ticketDetails($stop->tid); + $stopped = $this->connector->ticketDetails($stop->tid, $stop->connector_id); $output->writeln(sprintf('[%s] Closed slot %d against ticket %d: %s, duration %s', (new \DateTime())->format('h:i'), $stop->id, @@ -63,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { Formatter::formatDuration($stop->duration) )); if (($comment = $input->getOption('comment')) || $input->getOption('pause')) { - if ($this->connector->pause($stop->tid, $comment)) { + if ($this->connector->pause($stop->tid, $comment, $stop->connector_id)) { $output->writeln(sprintf('Ticket %s set to paused.', $stop->tid)); } else { diff --git a/src/Commands/Tag.php b/src/Commands/Tag.php index 6d7be7c..0637e78 100644 --- a/src/Commands/Tag.php +++ b/src/Commands/Tag.php @@ -1,12 +1,7 @@ connector = $connector; $this->repository = $repository; @@ -78,28 +78,30 @@ protected function tagAll(InputInterface $input, OutputInterface $output) { $last = FALSE; try { $entries = $this->repository->review(Review::ALL, TRUE); - $categories = $this->connector->fetchCategories(); + $grouped_categories = $this->connector->fetchCategories(); } catch (ConnectException $e) { $output->writeln('You are offline, please try again later.'); return; } foreach ($entries as $entry) { + $categories = $grouped_categories[$entry->connector_id]; if ($entry->category && !$input->getOption('retag')) { continue; } - $title = $this->connector->ticketDetails($entry->tid); + $title = $this->connector->ticketDetails($entry->tid, $entry->connector_id); + $default = reset($categories); $question = new ChoiceQuestion( sprintf('Enter tag for slot %d [%d]: %s [%s h] [%s] %s', $entry->id, $entry->tid, $title->getTitle(), $entry->duration, - $last ?: static::DEFAULT_TAG, - $entry->comment ? '- "' . $entry->comment . '"': '' + $last ?: $default, + $entry->comment ? '- "' . $entry->comment . '"' : '' ), $categories, - $last ?: static::DEFAULT_TAG + $last ?: $default ); $tag_id = $helper->ask($input, $output, $question); $tag = $categories[$tag_id]; @@ -123,23 +125,26 @@ protected function tagOne(InputInterface $input, OutputInterface $output, $slot_ if ($entry = $this->repository->slot($slot_id)) { $helper = $this->getHelper('question'); try { - $title = $this->connector->ticketDetails($entry->tid); - $categories = $this->connector->fetchCategories(); - } catch (ConnectException $e) { + $title = $this->connector->ticketDetails($entry->tid, $entry->connector_id); + $grouped_categories = $this->connector->fetchCategories(); + $categories = $grouped_categories[$entry->connector_id]; + } + catch (ConnectException $e) { $output->writeln('You are offline, please try again later.'); return; } + $default = reset($categories); $question = new ChoiceQuestion( sprintf('Enter tag for slot %d [%d]: %s [%s h] [%s] %s', $entry->id, $entry->tid, $title->getTitle(), Formatter::formatDuration($entry->end - $entry->start), - static::DEFAULT_TAG, - $entry->comment ? '- "' . $entry->comment . '"': '' + $default, + $entry->comment ? '- "' . $entry->comment . '"' : '' ), $categories, - static::DEFAULT_TAG + $default ); $tag_id = $helper->ask($input, $output, $question); $tag = $categories[$tag_id]; diff --git a/src/Commands/TagAll.php b/src/Commands/TagAll.php index 6ac9296..2a0c34a 100644 --- a/src/Commands/TagAll.php +++ b/src/Commands/TagAll.php @@ -1,23 +1,18 @@ connector = $connector; $this->repository = $repository; @@ -52,27 +50,31 @@ protected function configure() { */ protected function execute(InputInterface $input, OutputInterface $output) { $entries = $this->repository->review(Review::ALL, TRUE); + $connector_ids = array_unique(array_map(function ($entry) { + return $entry->connector_id; + }, $entries)); $helper = $this->getHelper('question'); - $last = FALSE; $categories = $this->connector->fetchCategories(); - $question = new ChoiceQuestion( - sprintf('Select tag to use:[%s]', - $last ?: Tag::DEFAULT_TAG - ), - $categories, - $last ?: Tag::DEFAULT_TAG - ); - $tag_id = $helper->ask($input, $output, $question); - $tag = $categories[$tag_id]; - list(, $tag) = explode(':', $tag); + $tags = []; + foreach ($connector_ids as $connector_id) { + $question = new ChoiceQuestion( + sprintf('Select tag to use for %s tickets', Manager::formatConnectorId($connector_id)), + $categories[$connector_id] + ); + $tag_id = $helper->ask($input, $output, $question); + $tag = $categories[$connector_id][$tag_id]; + list(, $tag) = explode(':', $tag); + $tags[$connector_id] = $tag; + } + $tagged = FALSE; foreach ($entries as $entry) { if ($entry->category) { continue; } - $this->repository->tag($tag, $entry->id); - $last = $tag; + $this->repository->tag($tags[$entry->connector_id], $entry->id); + $tagged = TRUE; } - if (!$last) { + if (!$tagged) { $output->writeln('All items already tagged, use --retag to retag'); } } diff --git a/src/Commands/Total.php b/src/Commands/Total.php index 1e03103..7ff65c8 100644 --- a/src/Commands/Total.php +++ b/src/Commands/Total.php @@ -1,19 +1,15 @@ reviewer = $reviewer; parent::__construct(); diff --git a/src/Commands/Update.php b/src/Commands/Update.php index 26368c0..35263ce 100644 --- a/src/Commands/Update.php +++ b/src/Commands/Update.php @@ -1,8 +1,4 @@ setName('self-update') - ->setDescription('Updates tl to the latest version from larowlan.github.io') - ; + ->setDescription('Updates tl to the latest version from larowlan.github.io'); } - protected function execute(InputInterface $input, OutputInterface $output) - { + /** + * + */ + protected function execute(InputInterface $input, OutputInterface $output) { $manager = new Manager(Manifest::loadFile(self::MANIFEST_FILE)); - $manager->update($this->getApplication()->getVersion(), false, true); + $manager->update($this->getApplication()->getVersion(), FALSE, TRUE); } + } diff --git a/src/Commands/Visit.php b/src/Commands/Visit.php index 1fadbb4..a428057 100644 --- a/src/Commands/Visit.php +++ b/src/Commands/Visit.php @@ -1,20 +1,17 @@ connector = $connector; $this->repository = $repository; parent::__construct(); @@ -59,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln('No active ticket, please use tl visit {ticket_id} to specifiy a ticket.'); return; } - $url = $this->connector->ticketUrl($issue_number); + $url = $this->connector->ticketUrl($issue_number, isset($data) ? $data->connector_id : $this->getConnector($input, $output, $issue_number)); $this->open($url, $output); } @@ -94,4 +94,27 @@ protected function open($url, OutputInterface $output) { } } + /** + * Gets connector ID. + * @param \Symfony\Component\Console\Input\InputInterface $input + * Input. + * @param \Symfony\Component\Console\Output\OutputInterface $output + * Output. + * @param mixed $issue_number + * Issue number. + * + * @return string + * Connector ID. + * + * @throws \InvalidArgumentException + * When no such ticket exists. + */ + protected function getConnector(InputInterface $input, OutputInterface $output, $issue_number) { + $connector_id = $this->connector->spotConnector($issue_number, $input, $output); + if (!$connector_id) { + throw new \InvalidArgumentException('No such ticket was found in any backends.'); + } + return $connector_id; + } + } diff --git a/src/Configuration/ConfigurableService.php b/src/Configuration/ConfigurableService.php index 1104c99..a268911 100644 --- a/src/Configuration/ConfigurableService.php +++ b/src/Configuration/ConfigurableService.php @@ -1,8 +1,4 @@ root('tl'); foreach ($this->services as $service) { - $service::getConfiguration($root); + $service::getConfiguration($root, $this->container); } return $tree; } @@ -37,4 +45,11 @@ public function setConfigurableServices(array $class_names) { $this->services = $class_names; } + /** + * {@inheritdoc} + */ + public function setContainerBuilder(ContainerBuilder $container) { + $this->container = $container; + } + } diff --git a/src/Connector/Connector.php b/src/Connector/Connector.php index bba3764..e65e939 100644 --- a/src/Connector/Connector.php +++ b/src/Connector/Connector.php @@ -1,26 +1,32 @@ $configuration['jira_url'], + 'jiraUser' => $configuration['jira_username'], + 'jiraPassword' => $configuration['jira_api_token'], + ]); + $this->issueService = new IssueService($arrayConfiguration); + $this->projectService = new ProjectService($arrayConfiguration); + $this->cache = $cache; + $this->userName = $configuration['jira_username']; + $this->version = $version; + $this->httpClient = $httpClient; + $this->nonBillableProjects = array_map(function ($item) { + return (int) $item; + }, $config['jira_non_billable_projects'] ?? []); + } + + /** + * {@inheritdoc} + */ + public static function getConfiguration(NodeDefinition $root_node, ContainerBuilder $container) { + $root_node->children() + ->scalarNode('jira_username') + ->defaultValue('') + ->end() + ->scalarNode('jira_api_token') + ->defaultValue('') + ->end() + ->scalarNode('jira_url') + ->defaultValue(self::JIRA_URL) + ->end() + ->arrayNode('jira_non_billable_projects') + ->requiresAtLeastOneElement() + ->prototype('scalar') + ->end() + ->end() + ->end(); + } + + /** + * {@inheritdoc} + */ + public static function askPreBootQuestions(QuestionHelper $helper, InputInterface $input, OutputInterface $output, array $config, ContainerBuilder $container) { + $default_url = isset($config['jira_url']) ? $config['jira_url'] : self::JIRA_URL; + $default_key = isset($config['jira_api_token']) ? $config['jira_api_token'] : ''; + $default_username = isset($config['jira_username']) ? $config['jira_username'] : ''; + // Reset. + $config = [ + 'jira_url' => '', + 'jira_api_token' => '', + 'jira_username' => '', + ] + $config; + $question = new Question(sprintf('Enter your Jira URL: [%s]', $default_url), $default_url); + $config['jira_url'] = $helper->ask($input, $output, $question) ?: $default_url; + if (strpos($config['jira_url'], 'https') !== 0) { + $output->writeln('It is recommended to use https, POSTING over http is not supported'); + } + $question = new Question(sprintf('Enter your Jira username: [%s]', $default_username), $default_username); + $config['jira_username'] = $helper->ask($input, $output, $question) ?: $default_username; + $question = new Question(sprintf('Enter your Jira API token: [%s]', $default_key), $default_key); + $question->setValidator(function ($value) { + if (trim($value) == '') { + throw new \Exception('The token cannot be empty'); + } + + return $value; + }); + $question->setHidden(TRUE); + $config['jira_api_token'] = $helper->ask($input, $output, $question) ?: $default_key; + return $config; + } + + /** + * {@inheritdoc} + */ + public function askPostBootQuestions(QuestionHelper $helper, InputInterface $input, OutputInterface $output, array $config) { + $default_non_billable = isset($config['jira_non_billable_projects']) ? $config['jira_non_billable_projects'] : []; + // Reset. + $config = ['jira_non_billable_projects' => []] + $config; + try { + $output->writeln('Bear with us while we configure which Jira projects are non billable'); + $options = $this->projectNames(); + } + catch (JiraException $e) { + $output->writeln('Could not connect to backend, please check your API key and that you are online'); + return $config; + } + foreach ($options as $id => $project) { + $default = in_array($id, $default_non_billable); + $question = new ConfirmationQuestion(sprintf('Is the %s project non billable?[%s/%s]', $project, $default ? 'Y' : 'y', $default ? 'n' : 'N'), $default); + if ($helper->ask($input, $output, $question)) { + $config['jira_non_billable_projects'][] = $id; + } + } + return $config; + } + + /** + * {@inheritdoc} + */ + public static function getDefaults($config, ContainerBuilder $container) { + return $config = [ + 'jira_url' => static::JIRA_URL, + 'jira_api_token' => '', + 'jira_username' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function getName() { + return 'Jira'; + } + + /** + * {@inheritdoc} + */ + public function ticketDetails($id, $connectorId) { + try { + $issue = $this->issueService->get($id); + } + catch (JiraException $e) { + try { + // Check if we're offline. + $this->httpClient->request('GET', self::JIRA_URL); + } + catch (ConnectException $e) { + return new Ticket( + 'Offline: please try again later', + 'Offline', + TRUE + ); + } + return FALSE; + } + return new Ticket(sprintf('[%s] %s', $issue->key, $issue->fields->summary), $issue->fields->getProjectId(), !in_array((int) $issue->fields->getProjectId(), $this->nonBillableProjects, TRUE)); + } + + /** + * {@inheritdoc} + */ + public function loadAlias($ticket_id, $connectorId) { + if (($details = $this->cache->fetch($this->version . ':alias:' . $ticket_id))) { + return $details; + } + try { + $issue = $this->issueService->get($ticket_id); + } + catch (\Exception $e) { + return $ticket_id; + } + + $this->cache->save($this->version . ':alias:' . $ticket_id, $issue->id, Manager::LIFETIME); + return $issue->id; + } + + /** + * {@inheritdoc} + */ + public function fetchCategories() { + // Jira doesn't require time log entries to be classified. + return ['Work:1' => 'Work:1']; + } + + /** + * {@inheritdoc} + */ + public function sendEntry($entry) { + if ((float) $entry->duration == 0) { + // Zero time after rounding. + // Return 0 to ensure doesn't send again. + return 0; + } + $worklog = new Worklog(); + + $worklog->setComment($entry->comment) + ->setStarted(date('Y-m-d h:m:s', $entry->start)) + ->setTimeSpent(sprintf('%sh %sm', floor($entry->duration), ($entry->duration - floor($entry->duration)) * 60)); + $ret = $this->issueService->addWorklog($entry->tid, $worklog); + return $ret->id; + } + + /** + * {@inheritdoc} + */ + public function ticketUrl($id, $connectorId) { + $id = $this->loadAlias($id, $connectorId); + $issue = $this->issueService->get($id); + return sprintf('%s/browse/%s', self::JIRA_URL, $issue->key); + } + + /** + * {@inheritdoc} + */ + public function assigned($user) { + $search = $this->issueService->search('assignee = currentUser() and status not in (Resolved, closed, Done)', 0, 25); + $results = []; + foreach ($search->getIssues() as $issue) { + $results += [$issue->fields->project->name => []]; + $results[$issue->fields->project->name][$issue->id] = [ + 'title' => sprintf('[%s] %s', $issue->key, $issue->fields->summary), + 'status' => $issue->fields->status->name, + ]; + } + return $results; + } + + /** + * {@inheritdoc} + */ + public function setInProgress($ticket_id, $connectorId, $assign = FALSE, $comment = 'Working on this') { + $transition = new Transition(); + $transition->setTransitionName('In Progress'); + $transition->setCommentBody($comment); + $this->issueService->transition($ticket_id, $transition); + if ($assign) { + $this->issueService->changeAssignee($ticket_id, $this->userName); + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function assign($ticket_id, $connectorId, $comment = 'Working on this') { + $this->issueService->changeAssignee($ticket_id, $this->userName); + } + + /** + * {@inheritdoc} + */ + public function pause($ticket_id, $comment, $connectorId) { + // No paused status. + } + + /** + * {@inheritdoc} + */ + public function projectNames() { + $cid = 'jira-projects'; + if (($details = $this->cache->fetch($this->version . ':' . $cid))) { + return $details; + } + $projects = $this->projectService->getAllProjects(); + $return = []; + foreach ($projects as $project) { + $return[$project->id] = $project->name; + } + $this->cache->save($this->version . ':' . $cid, $return, Manager::LIFETIME * 4); + return $return; + } + +} diff --git a/src/Connector/Manager.php b/src/Connector/Manager.php new file mode 100644 index 0000000..2060e65 --- /dev/null +++ b/src/Connector/Manager.php @@ -0,0 +1,283 @@ +connectors[$id] = $container->get($id); + }; + } + $this->cache = $cache; + $this->version = $version; + } + + /** + * Loads the given connector. + * + * @param string $connector_id + * Connector ID. + * + * @return \Larowlan\Tl\Connector\Connector + * Connector. + */ + protected function connector($connector_id) { + if (!$this->connectors[$connector_id]) { + throw new \InvalidArgumentException('No such backend connector'); + } + return $this->connectors[$connector_id]; + } + + /** + * {@inheritdoc} + */ + public function spotConnector($id, InputInterface $input, OutputInterface $output) { + if ((!$backends = $this->cache->fetch($this->version . ':resolve_connector:' . $id))) { + // Ask each connector. + $backends = []; + foreach ($this->connectors as $connector_id => $connector) { + if ($connector->ticketDetails($id, $connector_id)) { + list(, $connector_id) = explode('.', $connector_id); + $backends[$connector_id] = call_user_func([get_class($connector), 'getName']); + } + } + // Cache result. + $this->cache->save($this->version . ':resolve_connector:' . $id, $backends, static::LIFETIME); + } + if (!$backends) { + return FALSE; + } + if (count($backends) == 1) { + return 'connector.' . key($backends); + } + // Ask for backend. + $question = new ChoiceQuestion( + 'The given ticket ID is found in more than one backend - which backend do you want to use', + $backends, + key($backends) + ); + $helper = new QuestionHelper(); + return 'connector.' . $helper->ask($input, $output, $question); + } + + /** + * {@inheritdoc} + */ + public function ticketDetails($id, $connectorId) { + if (($details = $this->cache->fetch($this->version . ':' . $connectorId . ':' . $id))) { + return $details; + } + $ticket = $this->connector($connectorId)->ticketDetails($id, $connectorId); + $this->cache->save($this->version . ':' . $connectorId . ':' . $id, $ticket, static::LIFETIME); + return $ticket; + } + + /** + * {@inheritdoc} + */ + public function fetchCategories() { + $categories = []; + foreach ($this->connectors as $id => $connector) { + $categories[$id] = $connector->fetchCategories(); + } + return $categories; + } + + /** + * {@inheritdoc} + */ + public function sendEntry($entry) { + return $this->connector($entry->connector_id)->sendEntry($entry); + } + + /** + * {@inheritdoc} + */ + public function ticketUrl($id, $connectorId) { + return $this->connector($connectorId)->ticketUrl($id, $connectorId); + } + + /** + * {@inheritdoc} + */ + public function assigned($user) { + $assigned = []; + foreach ($this->connectors as $id => $connector) { + $name = call_user_func([get_class($connector), 'getName']); + $assigned[$name] = $connector->assigned($user); + } + return $assigned; + } + + /** + * {@inheritdoc} + */ + public function setInProgress($ticket_id, $connectorId, $assign = FALSE, $comment = 'Working on this') { + return $this->connector($connectorId)->setInProgress($ticket_id, $connectorId, $assign, $comment); + } + + /** + * {@inheritdoc} + */ + public function assign($ticket_id, $connectorId, $comment = 'Working on this') { + return $this->connector($connectorId)->assign($ticket_id, $connectorId, $comment); + } + + /** + * {@inheritdoc} + */ + public function pause($ticket_id, $comment, $connectorId) { + return $this->connector($connectorId)->pause($ticket_id, $comment, $connectorId); + } + + /** + * {@inheritdoc} + */ + public function projectNames() { + $projectNames = []; + foreach ($this->connectors as $id => $connector) { + $projectNames[$id] = $connector->projectNames(); + } + return $projectNames; + } + + /** + * {@inheritdoc} + */ + public function loadAlias($ticket_id, $connectorId) { + return $this->connector($connectorId)->loadAlias($ticket_id, $connectorId); + } + + /** + * {@inheritdoc} + */ + public static function getConfiguration(NodeDefinition $root_node, ContainerBuilder $container) { + foreach (array_keys($container->findTaggedServiceIds('connector')) as $id) { + $definition = $container->getDefinition($id); + $class = $definition->getClass(); + $class::getConfiguration($root_node, $container); + } + $root_node->children() + ->arrayNode('connector_ids') + ->requiresAtLeastOneElement() + ->prototype('scalar') + ->end() + ->end() + ->end(); + return $root_node; + } + + /** + * {@inheritdoc} + */ + public static function askPreBootQuestions(QuestionHelper $helper, InputInterface $input, OutputInterface $output, array $config, ContainerBuilder $container) { + $connectorIds = array_keys($container->findTaggedServiceIds('connector')); + $activeIds = []; + foreach ($connectorIds as $id) { + $definition = $container->getDefinition($id); + $class = $definition->getClass(); + $name = call_user_func([$class, 'getName']); + $default = in_array($id, $config['connector_ids']); + $question = new ConfirmationQuestion(sprintf('Do you want to use the %s backend?[%s/%s]', $name, $default ? 'Y' : 'y', $default ? 'n' : 'N'), $default); + if ($helper->ask($input, $output, $question)) { + $activeIds[] = $id; + $config = $class::askPreBootQuestions($helper, $input, $output, $config, $container) + $config; + } + } + if (empty($activeIds)) { + throw new InvalidConfigurationException('You must select at least one backend'); + } + $config['connector_ids'] = $activeIds; + return $config; + } + + /** + * {@inheritdoc} + */ + public function askPostBootQuestions(QuestionHelper $helper, InputInterface $input, OutputInterface $output, array $config) { + foreach ($this->connectors as $connector) { + $config = $connector->askPostBootQuestions($helper, $input, $output, $config); + } + return $config; + } + + /** + * {@inheritdoc} + */ + public static function getDefaults($config, ContainerBuilder $container) { + foreach (array_keys($container->findTaggedServiceIds('connector')) as $id) { + $definition = $container->getDefinition($id); + $class = $definition->getClass(); + $class::getDefaults($config, $container); + } + $config['connector_ids'] = []; + return $config; + } + + /** + * Format conenctor id. + * + * @param string $connector_id + * Connector ID in connector.{id} format. + * + * @return string + * Connector ID with 'connector.' prefix stripped. + */ + public static function formatConnectorId($connector_id) { + if (strpos($connector_id, 'connector.') !== 0) { + return $connector_id; + } + return explode('.', $connector_id)[1]; + } + +} diff --git a/src/Connector/RedmineConnector.php b/src/Connector/RedmineConnector.php index 2a4ce8f..d1c0fb2 100644 --- a/src/Connector/RedmineConnector.php +++ b/src/Connector/RedmineConnector.php @@ -1,10 +1,5 @@ httpClient = $httpClient; - $this->cache = $cache; $this->url = $config['url']; $this->apiKey = $config['api_key']; - $this->nonBillableProjects = isset($config['non_billable_projects']) ? $config['non_billable_projects'] : []; + $this->nonBillableProjects = array_map(function ($item) { + return (int) $item; + }, $config['non_billable_projects'] ?? []); + $this->cache = $cache; $this->version = $version; } /** * {@inheritdoc} */ - public function ticketDetails($id) { - if (($details = $this->cache->fetch($this->version . ':' . $id))) { - return $details; - } - // We need to fetch it. + public function loadAlias($ticket_id, $connectorId) { + // Not supported. + return $ticket_id; + } + + /** + * {@inheritdoc} + */ + public function ticketDetails($id, $connectorId) { $url = $this->url . '/issues/' . $id . '.xml'; try { if ($xml = $this->fetch($url, $this->apiKey)) { @@ -68,8 +72,6 @@ public function ticketDetails($id) { (string) $xml->project['id'], $this->isBillable((string) $xml->project['id']) ); - $this->cache->save($this->version . ':' . $id, $entry, - static::LIFETIME); return $entry; } } @@ -83,6 +85,13 @@ public function ticketDetails($id) { return FALSE; } + /** + * {@inheritdoc} + */ + public static function getName() { + return 'Redmine'; + } + /** * {@inheritdoc} */ @@ -115,7 +124,7 @@ public function sendEntry($entry) { return 0; } $url = $this->url . '/time_entries.xml'; - $details = $this->ticketDetails($entry->tid); + $details = $this->ticketDetails($entry->tid, $entry->connector_id); $data = [ 'issue_id' => $entry->tid, 'project_id' => $details->getProjectId(), @@ -194,11 +203,14 @@ protected function fetch($url, $redmine_key) { /** * {@inheritdoc} */ - public function ticketUrl($id) { + public function ticketUrl($id, $connectorId) { return $this->url . '/issues/' . $id; } - public function assigned($user = 'me') { + /** + * {@inheritdoc} + */ + public function assigned($user) { $url = $this->url . '/issues.xml?assigned_to_id=' . $user; $tickets = []; if ($xml = $this->fetch($url, $this->apiKey)) { @@ -215,7 +227,7 @@ public function assigned($user = 'me') { // Sort by status. foreach ($tickets as $project => &$project_issues) { - uasort($project_issues, function($a, $b) { + uasort($project_issues, function ($a, $b) { return strcmp($a['status'], $b['status']); }); } @@ -224,21 +236,23 @@ public function assigned($user = 'me') { if ((int) $xml['total_count'] > (int) $xml['limit']) { $tickets['...']['...'] = [ 'title' => sprintf('Showing %s of %s', $xml['limit'], $xml['total_count']), - 'status' => 'Too many Issues!' + 'status' => 'Too many Issues!', ]; } else { $tickets['...'][''] = [ 'title' => sprintf('Showing %s issues', $xml['total_count']), - 'status' => '' + 'status' => '', ]; } - return $tickets; } - public function setInProgress($ticket_id, $assign = FALSE, $comment = 'Working on this') { + /** + * {@inheritdoc} + */ + public function setInProgress($ticket_id, $connectorId, $assign = FALSE, $comment = 'Working on this') { $states = $this->getStates(); if (!isset($states['In progress'])) { throw new \Exception('There is no "In progress" status'); @@ -250,10 +264,16 @@ public function setInProgress($ticket_id, $assign = FALSE, $comment = 'Working o return $this->putUpdate($ticket_id, $updates, $comment); } - public function assign($ticket_id, $comment = 'Working on this') { + /** + * {@inheritdoc} + */ + public function assign($ticket_id, $connectorId, $comment = 'Working on this') { return $this->putUpdate($ticket_id, ['assigned_to_id' => $this->getUserId()], $comment); } + /** + * {@inheritdoc} + */ protected function putUpdate($ticket_id, array $updates, $comment = 'Working on this') { $url = $this->url . '/issues/' . $ticket_id . '.xml'; $xml = new \SimpleXMLElement(''); @@ -280,6 +300,9 @@ protected function putUpdate($ticket_id, array $updates, $comment = 'Working on return FALSE; } + /** + * {@inheritdoc} + */ protected function getStates() { $cid = 'redmine-states'; if (($details = $this->cache->fetch($this->version . ':' . $cid))) { @@ -299,6 +322,9 @@ protected function getStates() { } + /** + * {@inheritdoc} + */ protected function getUserId() { $url = $this->url . '/users/current.xml'; $cid = 'userid'; @@ -307,14 +333,17 @@ protected function getUserId() { } if ($xml = $this->fetch($url, $this->apiKey)) { // Cache permanent. - $uid = (int)$xml->id; + $uid = (int) $xml->id; $this->cache->save($this->version . ':' . $cid, $uid, 0); return $uid; } throw new \Exception('Could not determine your user ID'); } - public function pause($ticket_id, $comment) { + /** + * {@inheritdoc} + */ + public function pause($ticket_id, $comment, $connectorId) { $states = $this->getStates(); if (!isset($states['Paused'])) { throw new \Exception('There is no "Paused" status'); @@ -333,34 +362,34 @@ public function pause($ticket_id, $comment) { * TRUE if billable. */ protected function isBillable($project_id) { - return !in_array($project_id, $this->nonBillableProjects, TRUE); + return !in_array((int) $project_id, $this->nonBillableProjects, TRUE); } /** * {@inheritdoc} */ - public static function getConfiguration(NodeDefinition $root_node) { + public static function getConfiguration(NodeDefinition $root_node, ContainerBuilder $container) { $root_node->children() - ->arrayNode('non_billable_projects') - ->requiresAtLeastOneElement() - ->prototype('scalar') - ->end() - ->end() - ->scalarNode('api_key') - ->isRequired() - ->defaultValue('') - ->end() - ->scalarNode('url') - ->defaultValue('https://redmine.previousnext.com.au') - ->isRequired() - ->end() + ->arrayNode('non_billable_projects') + ->requiresAtLeastOneElement() + ->prototype('scalar') + ->end() + ->end() + ->scalarNode('api_key') + ->isRequired() + ->defaultValue('') + ->end() + ->scalarNode('url') + ->defaultValue('https://redmine.previousnext.com.au') + ->isRequired() + ->end() ->end(); } /** * {@inheritdoc} */ - public static function askPreBootQuestions(QuestionHelper $helper, InputInterface $input, OutputInterface $output, array $config) { + public static function askPreBootQuestions(QuestionHelper $helper, InputInterface $input, OutputInterface $output, array $config, ContainerBuilder $container) { $default_url = isset($config['url']) ? $config['url'] : 'https://redmine.previousnext.com.au'; $default_key = isset($config['api_key']) ? $config['api_key'] : ''; // Reset. @@ -384,6 +413,7 @@ public function askPostBootQuestions(QuestionHelper $helper, InputInterface $inp $config = ['non_billable_projects' => []] + $config; try { $options = $this->projectNames(); + $output->writeln('Bear with us while we configure which Redmine projects are non billable'); } catch (ConnectException $e) { $output->writeln('Could not connect to backend, please check your API key and that you are online'); @@ -393,20 +423,20 @@ public function askPostBootQuestions(QuestionHelper $helper, InputInterface $inp $output->writeln('An error occured trying to connect to the backend, please check your API key and that you are online'); throw $e; } - - $question = new ChoiceQuestion(sprintf('Select non billable project IDs (separated by ,):'), $options, implode(',', $default_non_billable)); - $question->setMultiselect(TRUE); - $config['non_billable_projects'] = $helper->ask($input, $output, $question) ?: $default_non_billable; - $config['non_billable_projects'] = array_map(function($item) { - return explode('::', $item, 2)[1]; - }, $config['non_billable_projects']); + foreach ($options as $id => $project) { + $default = in_array($id, $default_non_billable); + $question = new ConfirmationQuestion(sprintf('Is the %s project non billable?[%s/%s]', $project, $default ? 'Y' : 'y', $default ? 'n' : 'N'), $default); + if ($helper->ask($input, $output, $question)) { + $config['non_billable_projects'][] = $id; + } + } return $config; } /** * {@inheritdoc} */ - public static function getDefaults($config) { + public static function getDefaults($config, ContainerBuilder $container) { if (!$config) { $config = []; } @@ -420,7 +450,7 @@ public static function getDefaults($config) { public function projectNames() { $cid = 'redmine-projects'; if (($details = $this->cache->fetch($this->version . ':' . $cid))) { - return $details; + return $details; } $options = []; diff --git a/src/DateHelper.php b/src/DateHelper.php index 019a3b7..38a6384 100644 --- a/src/DateHelper.php +++ b/src/DateHelper.php @@ -1,8 +1,4 @@ output = $output; $this->log = $log; diff --git a/src/LogHelper.php b/src/LogHelper.php index 6fde10d..f43b25e 100644 --- a/src/LogHelper.php +++ b/src/LogHelper.php @@ -1,8 +1,4 @@ connection = $connection; } + /** + * {@inheritdoc} + */ protected function qb() { return $this->connection()->createQueryBuilder(); } + /** * {@inheritdoc} */ @@ -50,6 +55,9 @@ public function stop($slot_id = NULL) { return FALSE; } + /** + * {@inheritdoc} + */ public function getActive($slot_id = NULL) { $q = $this->qb()->select('*') ->from('slots', 's') @@ -66,6 +74,9 @@ public function getActive($slot_id = NULL) { return FALSE; } + /** + * {@inheritdoc} + */ public function latest() { $q = $this->qb()->select('*') ->from('slots', 's') @@ -78,9 +89,13 @@ public function latest() { return FALSE; } - public function start($ticket_id, $comment = '', $force_continue = FALSE) { + /** + * {@inheritdoc} + */ + public function start($ticket_id, $connectorId, $comment = '', $force_continue = FALSE) { $continue_query = $continue = $this->qb()->select('*') ->from('slots', 's') + ->where('s.connector_id = :connector_id') ->where('s.tid = :tid'); if (!$force_continue) { $continue_query->andWhere('s.comment IS NULL') @@ -91,6 +106,7 @@ public function start($ticket_id, $comment = '', $force_continue = FALSE) { ->setParameter('id', $force_continue); } $continue = $continue_query->setParameter('tid', $ticket_id) + ->setParameter('connector_id', $connectorId) ->execute() ->fetch(\PDO::FETCH_OBJ); if ((!$comment || $force_continue) && $continue) { @@ -101,21 +117,25 @@ public function start($ticket_id, $comment = '', $force_continue = FALSE) { ->set('end', ':end') ->setParameter(':end', NULL) ->execute(); - return array($continue->id, TRUE); + return [$continue->id, TRUE]; } - $record = array( + $record = [ 'tid' => $ticket_id, 'start' => $this::requestTime(), - ); - $params = []; + 'connector_id' => ':connector_id', + ]; + $params = [':connector_id' => $connectorId]; if ($comment) { $record['comment'] = ':comment'; $params[':comment'] = $comment; } - return array($this->insert($record, $params), FALSE); + return [$this->insert($record, $params), FALSE]; } + /** + * {@inheritdoc} + */ public function insert($slot, $params = []) { $query = $this->qb()->insert('slots') ->values($slot); @@ -126,6 +146,9 @@ public function insert($slot, $params = []) { return $this->connection()->lastInsertId(); } + /** + * {@inheritdoc} + */ public function status($date = NULL) { if (!$date) { $stamp = mktime('0', '0'); @@ -133,7 +156,7 @@ public function status($date = NULL) { else { $stamp = strtotime($date); } - $return = $this->qb()->select('id', 'tid', 'end', 'start') + $return = $this->qb()->select('id', 'tid', 'end', 'start', 'connector_id') ->from('slots') ->where('start > :start AND start < :end') ->setParameter(':start', $stamp) @@ -143,6 +166,9 @@ public function status($date = NULL) { return $return; } + /** + * {@inheritdoc} + */ public function review($date = NULL, $check = FALSE) { if (!$date) { $stamp = mktime('0', '0'); @@ -150,8 +176,9 @@ public function review($date = NULL, $check = FALSE) { else { $stamp = strtotime($date); } - $query = $this->qb()->select('end', 'tid', 'category', 'comment', 'id', 'start') - ->from('slots'); + $query = $this->qb() + ->select('end', 'tid', 'category', 'comment', 'id', 'start', 'connector_id') + ->from('slots'); $where = $this->qb()->expr()->andX( $this->qb()->expr()->isNull('teid'), $this->qb()->expr()->gt('start', ':stamp') @@ -172,10 +199,16 @@ public function review($date = NULL, $check = FALSE) { return $return; } + /** + * {@inheritdoc} + */ public function send() { return $this->review('19780101'); } + /** + * {@inheritdoc} + */ public function store($entries) { foreach ($entries as $tid => $entry_id) { $this->qb()->update('slots') @@ -187,6 +220,9 @@ public function store($entries) { } } + /** + * {@inheritdoc} + */ public function edit($slot_id, $duration) { $request_time = $this::requestTime(); return $this->qb()->update('slots') @@ -197,7 +233,7 @@ public function edit($slot_id, $duration) { } /** - * Wraps REQUEST_TIME constant + * Wraps REQUEST_TIME constant. * * @return int * Current request time. @@ -206,6 +242,9 @@ protected static function requestTime() { return time(); } + /** + * {@inheritdoc} + */ public function tag($tag_id, $slot_id = NULL) { $query = $this->qb()->update('slots') ->set('category', ':tag') @@ -217,6 +256,9 @@ public function tag($tag_id, $slot_id = NULL) { return $query->execute(); } + /** + * {@inheritdoc} + */ public function comment($slot_id, $comment) { return $this->qb()->update('slots') ->set('comment', ':comment') @@ -227,9 +269,12 @@ public function comment($slot_id, $comment) { ->execute(); } + /** + * {@inheritdoc} + */ public function frequent() { return $this->qb() - ->select('tid') + ->select('tid', 'connector_id') ->from('slots', 's') ->groupBy('tid') ->orderBy('COUNT(*)', 'DESC') @@ -238,6 +283,9 @@ public function frequent() { ->fetchAll(\PDO::FETCH_OBJ); } + /** + * {@inheritdoc} + */ public function slot($slot_id) { return $this->qb()->select('*') ->from('slots') @@ -247,6 +295,9 @@ public function slot($slot_id) { ->fetch(\PDO::FETCH_OBJ); } + /** + * {@inheritdoc} + */ public function delete($slot_id) { return $this->qb()->delete('slots') ->where('id = :id') @@ -256,10 +307,16 @@ public function delete($slot_id) { ->execute(); } + /** + * {@inheritdoc} + */ protected function connection() { return $this->connection; } + /** + * {@inheritdoc} + */ public function addAlias($ticket_id, $alias) { return $this->qb()->insert('aliases') ->values([ @@ -271,6 +328,9 @@ public function addAlias($ticket_id, $alias) { ->execute(); } + /** + * {@inheritdoc} + */ public function removeAlias($ticket_id, $alias) { return $this->qb()->delete('aliases') ->where('tid = :ticket_id') @@ -280,6 +340,9 @@ public function removeAlias($ticket_id, $alias) { ->execute(); } + /** + * {@inheritdoc} + */ public function loadAlias($alias) { return $this->qb()->select('tid') ->from('aliases') @@ -289,6 +352,9 @@ public function loadAlias($alias) { ->fetchColumn(); } + /** + * {@inheritdoc} + */ public function listAliases($filter = '') { $query = $this->qb()->select('alias', 'tid') ->from('aliases'); @@ -302,12 +368,15 @@ public function listAliases($filter = '') { ->fetchAll(\PDO::FETCH_OBJ); } + /** + * {@inheritdoc} + */ public function totalByTicket($start, $end = NULL) { if (!$end) { // Some time in the future. $end = time() + 86400; } - $return = $this->qb()->select('tid', 'end', 'start') + $return = $this->qb()->select('tid', 'end', 'start', 'connector_id') ->from('slots') ->where('start > :start AND start < :end') ->setParameter(':start', $start) @@ -317,10 +386,10 @@ public function totalByTicket($start, $end = NULL) { $totals = []; foreach ($return as $row) { $row->duration = round((($row->end ?: time()) - $row->start) / 900) * 900; - if (!isset($totals[$row->tid])) { - $totals[$row->tid] = 0; + if (!isset($totals[$row->connector_id][$row->tid])) { + $totals[$row->connector_id][$row->tid] = 0; } - $totals[$row->tid] += $row->duration; + $totals[$row->connector_id][$row->tid] += $row->duration; } return $totals; } diff --git a/src/Repository/Repository.php b/src/Repository/Repository.php index 92f607e..12f60a6 100644 --- a/src/Repository/Repository.php +++ b/src/Repository/Repository.php @@ -1,12 +1,10 @@ addColumn('teid', 'bigint', ['unsigned' => TRUE])->setNotnull(FALSE); $slots->addColumn('comment', 'string', ['length' => 255])->setNotnull(FALSE); $slots->addColumn('category', 'string', ['length' => 255])->setNotnull(FALSE); + $slots->addColumn('connector_id', 'string', ['length' => 50])->setDefault('connector.redmine')->setNotnull(FALSE); $slots->setPrimaryKey(['id']); $slots->addIndex(['start']); $slots->addIndex(['end']); @@ -39,4 +45,5 @@ public function getSchema() { $aliases->addIndex(['tid']); return $schema; } + } diff --git a/src/Reviewer.php b/src/Reviewer.php index fa04fb2..29de822 100644 --- a/src/Reviewer.php +++ b/src/Reviewer.php @@ -1,34 +1,53 @@ connector = $connector; $this->repository = $repository; } + /** + * Gets table headers. + * + * @param bool $exact + * TRUE to include exact column. + * + * @return array + * Headers. + */ public static function headers($exact = FALSE) { $headers = [ 'SlotID', @@ -46,6 +65,22 @@ public static function headers($exact = FALSE) { return $headers; } + /** + * Gets summary of tickets. + * + * @param int $date + * Start date. + * @param bool $check + * Check if stored remotely already. + * @param bool $exact + * TRUE to include exact column. + * + * @return array + * Rows. + * + * @throws \Exception + * When all entries already stored and check is TRUE. + */ public function getSummary($date = 19780101, $check = FALSE, $exact = FALSE) { $data = $this->repository->review($date, $check); if (count($data) == 0 && !$check) { @@ -63,15 +98,15 @@ public function getSummary($date = 19780101, $check = FALSE, $exact = FALSE) { $exact_total = 0; foreach ($data as $record) { $total += $record->duration; - $details = $this->connector->ticketDetails($record->tid); + $details = $this->connector->ticketDetails($record->tid, $record->connector_id); $category_id = str_pad($record->category, 3, 0, STR_PAD_LEFT); $category = ''; if ($record->category) { if ($offline) { $category = 'Offline'; } - elseif (isset($categories[$category_id])) { - $category = $categories[$category_id]; + elseif (isset($categories[$record->connector_id][$category_id])) { + $category = $categories[$record->connector_id][$category_id]; } else { $category = 'Unknown'; @@ -103,7 +138,7 @@ public function getSummary($date = 19780101, $check = FALSE, $exact = FALSE) { '' . Formatter::formatDuration($exact_total) . '', '', '', - '' + '', ]; } else { @@ -113,9 +148,10 @@ public function getSummary($date = 19780101, $check = FALSE, $exact = FALSE) { '' . $total . ' h', '', '', - '' + '', ]; } return $rows; } + } diff --git a/src/Ticket.php b/src/Ticket.php index cdf3e95..9a66cfa 100644 --- a/src/Ticket.php +++ b/src/Ticket.php @@ -1,25 +1,29 @@ getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); + $this->setupConnector(); $output = $this->executeCommand('alias', [ 'ticket_id' => 1234, 'alias' => 'pony', @@ -41,10 +32,7 @@ public function testCreate() { * @covers ::execute */ public function testDelete() { - $this->getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); + $this->setupConnector(); $output = $this->executeCommand('alias', [ 'ticket_id' => 1234, 'alias' => 'pony', @@ -62,10 +50,7 @@ public function testDelete() { * @covers ::execute */ public function testList() { - $this->getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); + $this->setupConnector(); $aliases = [ 'some', 'drunk', diff --git a/tests/Commands/ContinueTest.php b/tests/Commands/ContinueTest.php index 5bf8ce1..4b77cbe 100644 --- a/tests/Commands/ContinueTest.php +++ b/tests/Commands/ContinueTest.php @@ -1,8 +1,4 @@ getMockConnector()->expects($this->any()) ->method('ticketDetails') ->willReturnMap([ - ['1', new Ticket('Do something', 1)], - ['2', new Ticket('Do something else', 2)], - ['3', new Ticket('Do something more', 3)], + ['1', 'connector.redmine', new Ticket('Do something', 1)], + ['2', 'connector.redmine', new Ticket('Do something else', 2)], + ['3', 'connector.redmine', new Ticket('Do something more', 3)], ]); + $this->getMockConnector()->expects($this->any()) + ->method('spotConnector') + ->willReturn('connector.redmine'); $repository = $this->getRepository(); // Five entries for today. $start = time(); @@ -37,26 +37,30 @@ protected function setUp() { $this->slotId1 = $repository->insert([ 'tid' => 1, 'start' => $start, - 'end' => $start + 3588 * 7 - ]); + 'end' => $start + 3588 * 7, + 'connector_id' => ':connector_id', + ], [':connector_id' => 'connector.redmine']); // 1 hr. $this->slotId2 = $repository->insert([ 'tid' => 2, 'start' => $start, - 'end' => $start + 3600 - ]); + 'end' => $start + 3600, + 'connector_id' => ':connector_id', + ], [':connector_id' => 'connector.redmine']); // 9 hrs. $this->slotId3 = $repository->insert([ 'tid' => 3, 'start' => $start, - 'end' => $start + 3600 * 9 - ]); + 'end' => $start + 3600 * 9, + 'connector_id' => ':connector_id', + ], [':connector_id' => 'connector.redmine']); // Yesterday. $repository->insert([ 'tid' => 3, 'start' => $start - 86400, - 'end' => $start - 86400 + 3600 * 9 - ]); + 'end' => $start - 86400 + 3600 * 9, + 'connector_id' => ':connector_id', + ], [':connector_id' => 'connector.redmine']); } /** @@ -78,7 +82,7 @@ public function testContinueCommand() { */ public function testContinueCommandWithSlotId() { $this->setUp(); - $result = $this->executeCommand('continue' ,['slot_id' => $this->slotId2]); + $result = $this->executeCommand('continue', ['slot_id' => $this->slotId2]); $this->assertContains('Continued entry for 2: Do something else [slot:' . $this->slotId2 . ']', $result->getDisplay()); $this->assertTicketIsOpen(2); $this->getRepository()->stop(); diff --git a/tests/Commands/FormatterTest.php b/tests/Commands/FormatterTest.php index 1d84c15..563dd01 100644 --- a/tests/Commands/FormatterTest.php +++ b/tests/Commands/FormatterTest.php @@ -1,17 +1,14 @@ getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); - $output = new StreamOutput(fopen('php://memory', 'w', false)); + $this->setupConnector(); + $output = new StreamOutput(fopen('php://memory', 'w', FALSE)); $command = $this->container->get('app.command.start'); $command->setApplication($this->application); $this->application->setAutoExit(FALSE); diff --git a/tests/Commands/ReviewTest.php b/tests/Commands/ReviewTest.php index a8197c3..3b8e12b 100644 --- a/tests/Commands/ReviewTest.php +++ b/tests/Commands/ReviewTest.php @@ -1,8 +1,4 @@ getMockConnector()->expects($this->any()) ->method('ticketDetails') ->willReturnMap([ - ['1', new Ticket('Do something', 1)], - ['2', new Ticket('Do something else ', 2)], - ['3', new Ticket('Do something more', 3)], + ['1', 'connector.redmine', new Ticket('Do something', 1)], + ['2', 'connector.redmine', new Ticket('Do something else ', 2)], + ['3', 'connector.redmine', new Ticket('Do something more', 3)], ]); + $this->getMockConnector()->expects($this->any()) + ->method('spotConnector') + ->willReturn('connector.redmine'); $repository = $this->getRepository(); // Five entries for today. $start = time(); @@ -34,20 +33,23 @@ protected function setUp() { $repository->insert([ 'tid' => 1, 'start' => $start, - 'end' => $start + 3588 * 7 - ]); + 'end' => $start + 3588 * 7, + 'connector_id' => ':connector_id', + ], [':connector_id' => 'connector.redmine']); // 1 hr. $repository->insert([ 'tid' => 2, 'start' => $start, - 'end' => $start + 3600 - ]); + 'end' => $start + 3600, + 'connector_id' => ':connector_id', + ], [':connector_id' => 'connector.redmine']); // 3 hrs. $repository->insert([ 'tid' => 3, 'start' => $start, - 'end' => $start + 3600 * 3 - ]); + 'end' => $start + 3600 * 3, + 'connector_id' => ':connector_id', + ], [':connector_id' => 'connector.redmine']); } /** @@ -67,7 +69,7 @@ public function testReviewCommand() { */ public function testReviewExact() { $this->setUp(); - $result = $this->executeCommand('review' ,['--exact' => TRUE]); + $result = $this->executeCommand('review', ['--exact' => TRUE]); $this->assertContains('10:58:36', $result->getDisplay()); $this->assertContains('6:58:36', $result->getDisplay()); $this->assertContains('3:00:00', $result->getDisplay()); diff --git a/tests/Commands/StartTest.php b/tests/Commands/StartTest.php index efadf4e..fe1d088 100644 --- a/tests/Commands/StartTest.php +++ b/tests/Commands/StartTest.php @@ -1,13 +1,7 @@ getMockConnector()->expects($this->any()) ->method('ticketDetails') - ->with(1234) + ->with(1234, 'connector.redmine') ->willReturn(new Ticket('Running tests', 123)); + $this->getMockConnector()->expects($this->any()) + ->method('spotConnector') + ->willReturn('connector.redmine'); $output = $this->executeCommand('start', ['issue_number' => 1234]); $this->assertRegExp('/Started new entry for 1234: Running tests/', $output->getDisplay()); $this->assertTicketIsOpen(1234); @@ -36,11 +33,8 @@ public function testStart() { * @covers \Larowlan\Tl\Application::doRun */ public function testStartLogging() { - $this->getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); - $output = new StreamOutput(fopen('php://memory', 'w', false)); + $this->setupConnector(); + $output = new StreamOutput(fopen('php://memory', 'w', FALSE)); $command = $this->container->get('app.command.start'); $command->setApplication($this->application); $this->application->setAutoExit(FALSE); @@ -63,10 +57,13 @@ public function testStopStart() { $this->getMockConnector()->expects($this->any()) ->method('ticketDetails') ->willReturnMap([ - [1234, new Ticket('Running tests', 123)], - ["1234", new Ticket('Running tests', 123)], - [4567, new Ticket('Running more tests', 123)], + [1234, 'connector.redmine', new Ticket('Running tests', 123)], + ["1234", 'connector.redmine', new Ticket('Running tests', 123)], + [4567, 'connector.redmine', new Ticket('Running more tests', 123)], ]); + $this->getMockConnector()->expects($this->any()) + ->method('spotConnector') + ->willReturn('connector.redmine'); $output = $this->executeCommand('start', ['issue_number' => 1234]); $this->assertRegExp('/Started new entry for 1234: Running tests/', $output->getDisplay()); $active = $this->assertTicketIsOpen(1234); @@ -84,10 +81,7 @@ public function testStopStart() { * @covers ::execute */ public function testStartWithComment() { - $this->getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); + $this->setupConnector(); $output = $this->executeCommand('start', [ 'issue_number' => 1234, 'comment' => 'Doing stuff', @@ -100,10 +94,7 @@ public function testStartWithComment() { * @covers ::execute */ public function testAssign() { - $this->getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); + $this->setupConnector(); $this->getMockConnector()->expects($this->once()) ->method('assign') ->with(1234) @@ -121,10 +112,7 @@ public function testAssign() { * @covers ::execute */ public function testAssignShortSyntax() { - $this->getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); + $this->setupConnector(); $this->getMockConnector()->expects($this->once()) ->method('assign') ->with(1234) @@ -142,10 +130,7 @@ public function testAssignShortSyntax() { * @covers ::execute */ public function testAssignAlreadyAssigned() { - $this->getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); + $this->setupConnector(); $this->getMockConnector()->expects($this->once()) ->method('assign') ->with(1234) @@ -163,17 +148,14 @@ public function testAssignAlreadyAssigned() { * @covers ::execute */ public function testStatus() { - $this->getMockConnector()->expects($this->any()) - ->method('ticketDetails') - ->with(1234) - ->willReturn(new Ticket('Running tests', 123)); + $this->setupConnector(); $this->getMockConnector()->expects($this->once()) ->method('setInProgress') ->with(1234) ->willReturn(TRUE); $output = $this->executeCommand('start', [ 'issue_number' => 1234, - '--status' => TRUE + '--status' => TRUE, ]); $this->assertRegExp('/Started new entry for 1234: Running tests/', $output->getDisplay()); $this->assertRegExp('/Ticket 1234 set to in-progress/', $output->getDisplay()); @@ -186,11 +168,14 @@ public function testStatus() { public function testStatusAndAssign() { $this->getMockConnector()->expects($this->any()) ->method('ticketDetails') - ->with(1234) + ->with(1234, 'connector.redmine') ->willReturn(new Ticket('Running tests', 123)); + $this->getMockConnector()->expects($this->any()) + ->method('spotConnector') + ->willReturn('connector.redmine'); $this->getMockConnector()->expects($this->once()) ->method('setInProgress') - ->with(1234, TRUE) + ->with(1234, 'connector.redmine', TRUE) ->willReturn(TRUE); $output = $this->executeCommand('start', [ 'issue_number' => 1234, @@ -206,20 +191,20 @@ public function testStatusAndAssign() { /** * @covers ::execute */ - public function testStatusAssignAndComment() { + public function testStatusAndAssignWithBackend() { $this->getMockConnector()->expects($this->any()) ->method('ticketDetails') - ->with(1234) + ->with(1234, 'connector.redmine') ->willReturn(new Ticket('Running tests', 123)); $this->getMockConnector()->expects($this->once()) ->method('setInProgress') - ->with(1234, TRUE, "I will look on friday") + ->with(1234, 'connector.redmine', TRUE) ->willReturn(TRUE); $output = $this->executeCommand('start', [ 'issue_number' => 1234, '--status' => TRUE, '-a' => TRUE, - '-r' => "I will look on friday" + '-b' => 'redmine', ]); $this->assertRegExp('/Started new entry for 1234: Running tests/', $output->getDisplay()); $this->assertRegExp('/Ticket 1234 set to in-progress/', $output->getDisplay()); @@ -230,18 +215,42 @@ public function testStatusAssignAndComment() { /** * @covers ::execute */ - public function testStatusAlreadyInProgress() { + public function testStatusAssignAndComment() { $this->getMockConnector()->expects($this->any()) ->method('ticketDetails') - ->with(1234) + ->with(1234, 'connector.redmine') ->willReturn(new Ticket('Running tests', 123)); + $this->getMockConnector()->expects($this->any()) + ->method('spotConnector') + ->willReturn('connector.redmine'); + $this->getMockConnector()->expects($this->once()) + ->method('setInProgress') + ->with(1234, 'connector.redmine', TRUE, "I will look on friday") + ->willReturn(TRUE); + $output = $this->executeCommand('start', [ + 'issue_number' => 1234, + '--status' => TRUE, + '-a' => TRUE, + '-r' => "I will look on friday", + ]); + $this->assertRegExp('/Started new entry for 1234: Running tests/', $output->getDisplay()); + $this->assertRegExp('/Ticket 1234 set to in-progress/', $output->getDisplay()); + $this->assertRegExp('/Ticket 1234 assigned to you/', $output->getDisplay()); + $this->assertTicketIsOpen(1234); + } + + /** + * @covers ::execute + */ + public function testStatusAlreadyInProgress() { + $this->setupConnector(); $this->getMockConnector()->expects($this->once()) ->method('setInProgress') ->with(1234) ->willReturn(FALSE); $output = $this->executeCommand('start', [ 'issue_number' => 1234, - '-s' => TRUE + '-s' => TRUE, ]); $this->assertRegExp('/Started new entry for 1234: Running tests/', $output->getDisplay()); $this->assertRegExp('/Could not update ticket status/', $output->getDisplay()); diff --git a/tests/Commands/StatusTest.php b/tests/Commands/StatusTest.php index 3c1ad55..840f718 100644 --- a/tests/Commands/StatusTest.php +++ b/tests/Commands/StatusTest.php @@ -1,13 +1,7 @@ 'http://example.com'])); $container->setParameter('directory', $test_directory); - $mock_connector = $this->getMock(Connector::class); + $mock_connector = $this->createMock(ConnectorManager::class); $container->set('connector', $mock_connector); - $configuration_processor = $this->getMock(Processor::class); + $configuration_processor = $this->createMock(Processor::class); $configuration_processor->expects($this->any()) ->method('processConfiguration') ->willReturn([]); @@ -70,20 +67,23 @@ protected function setUp() { $install = $container->get('app.command.install'); $install->setApplication($this->application); $input = new ArrayInput(['command' => 'install']); - $output = $this->getMock(OutputInterface::class); + $output = $this->createMock(OutputInterface::class); $install->run($input, $output); } } + /** + * + */ protected function tearDown() { - parent::tearDown(); // TODO: Change the autogenerated stub + // TODO: Change the autogenerated stub. + parent::tearDown(); } - /** * Gets the mock connector. * - * @return \Larowlan\Tl\Connector\Connector|\PHPUnit_Framework_MockObject_MockObject $connector + * @return \Larowlan\Tl\Connector\Connector|\PHPUnit_Framework_MockObject_MockObject * The Mock connector. */ protected function getMockConnector() { @@ -123,7 +123,7 @@ protected function executeCommand($name, array $input = []) { $command = $this->container->get('app.command.' . $name); $command->setApplication($this->application); $command_tester = new CommandTester($command); - $command_tester->execute(['command' => $command->getName()] + $input); + $command_tester->execute(['command' => $command->getName()] + $input); return $command_tester; } @@ -131,7 +131,7 @@ protected function executeCommand($name, array $input = []) { * @return mixed */ protected function assertTicketIsOpen($ticket_id, $comment = NULL) { - /** @var Repository $repository */ + /** @var \Larowlan\Tl\Repository\Repository $repository */ $repository = $this->getRepository(); $active = $repository->getActive(); $this->assertEquals($ticket_id, $active->tid); @@ -142,4 +142,17 @@ protected function assertTicketIsOpen($ticket_id, $comment = NULL) { return $active; } + /** + * Sets up connector. + */ + protected function setupConnector() { + $this->getMockConnector()->expects($this->any()) + ->method('ticketDetails') + ->with(1234, 'connector.redmine') + ->willReturn(new Ticket('Running tests', 123)); + $this->getMockConnector()->expects($this->any()) + ->method('spotConnector') + ->willReturn('connector.redmine'); + } + } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5395b02..09851f6 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,5 @@