Реализация выгрузки задач по командам
commit
fe6255deb1
|
|
@ -0,0 +1,3 @@
|
|||
/vendor/
|
||||
/.idea/
|
||||
/local/
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "nechaev/reports",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"guzzlehttp/guzzle": "^7.10",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nechaev\\Reports\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,621 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b8dad7f6207ecd5e6111703a662d2426",
|
||||
"packages": [
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.10.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "47ba23c7a55247e2e1b7407aca90e9bbed0d9d86"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/47ba23c7a55247e2e1b7407aca90e9bbed0d9d86",
|
||||
"reference": "47ba23c7a55247e2e1b7407aca90e9bbed0d9d86",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^2.3",
|
||||
"guzzlehttp/psr7": "^2.8",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-client-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-curl": "*",
|
||||
"guzzle/client-integration-tests": "3.0.2",
|
||||
"guzzlehttp/test-server": "^0.3.2",
|
||||
"php-http/message-factory": "^1.1",
|
||||
"phpunit/phpunit": "^8.5.52 || ^9.6.34",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Required for CURL handler support",
|
||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": [
|
||||
"client",
|
||||
"curl",
|
||||
"framework",
|
||||
"http",
|
||||
"http client",
|
||||
"psr-18",
|
||||
"psr-7",
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.10.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-20T22:59:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
"version": "2.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/promises.git",
|
||||
"reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/09e8a212562fb1fb6a512c4156ed71525969d6c2",
|
||||
"reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.52 || ^9.6.34"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Promise\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle promises library",
|
||||
"keywords": [
|
||||
"promise"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/promises/issues",
|
||||
"source": "https://github.com/guzzle/promises/tree/2.4.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-20T22:57:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.10.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "73ab136360b5dfd858006eae9795e8fe43c80361"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/73ab136360b5dfd858006eae9795e8fe43c80361",
|
||||
"reference": "73ab136360b5dfd858006eae9795e8fe43c80361",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.1 || ^2.0",
|
||||
"ralouphie/getallheaders": "^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-factory-implementation": "1.0",
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"http-interop/http-factory-tests": "1.1.0",
|
||||
"jshttp/mime-db": "1.54.0.1",
|
||||
"phpunit/phpunit": "^8.5.52 || ^9.6.34"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Psr7\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://sagikazarmark.hu"
|
||||
}
|
||||
],
|
||||
"description": "PSR-7 message implementation that also provides common utility methods",
|
||||
"keywords": [
|
||||
"http",
|
||||
"message",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"stream",
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.10.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-20T09:27:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-client.git",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP clients",
|
||||
"homepage": "https://github.com/php-fig/http-client",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-client",
|
||||
"psr",
|
||||
"psr-18"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-client"
|
||||
},
|
||||
"time": "2023-09-23T14:17:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-factory.git",
|
||||
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
|
||||
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
|
||||
"keywords": [
|
||||
"factory",
|
||||
"http",
|
||||
"message",
|
||||
"psr",
|
||||
"psr-17",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-factory"
|
||||
},
|
||||
"time": "2024-04-15T12:06:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP messages",
|
||||
"homepage": "https://github.com/php-fig/http-message",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-message",
|
||||
"psr",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-message/tree/2.0"
|
||||
},
|
||||
"time": "2023-04-04T09:54:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "3.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^5 || ^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/getallheaders.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ralph Khattar",
|
||||
"email": "ralph.khattar@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A polyfill for getallheaders.",
|
||||
"support": {
|
||||
"issues": "https://github.com/ralouphie/getallheaders/issues",
|
||||
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
|
||||
},
|
||||
"time": "2019-03-08T08:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b",
|
||||
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/contracts",
|
||||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"function.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-13T15:52:40+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// config.php
|
||||
|
||||
return [
|
||||
'jira' => [
|
||||
'url' => 'https://your-domain.atlassian.net', // Ваш домен Jira
|
||||
'email' => 'your-email@example.com', // Ваш email
|
||||
'apiToken' => 'your-api-token', // Ваш API токен
|
||||
],
|
||||
'export' => [
|
||||
'maxResults' => 1000, // Максимальное количество задач за один запрос
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
use Nechaev\Reports\RolesEnum;
|
||||
|
||||
// Подключаем автозагрузчик Composer и файл конфигурации
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// готовим конфиги
|
||||
$baseConfig = require __DIR__ . '/configs/config.php';
|
||||
$config = require __DIR__ . '/local/configs/config.php';
|
||||
|
||||
$config = array_replace_recursive($baseConfig, $config);
|
||||
|
||||
// готовим команды
|
||||
|
||||
$teams = [
|
||||
'ts' => [
|
||||
'projects' => 'TS',
|
||||
'executors' => [
|
||||
't.vafin' => RolesEnum::BACKEND,
|
||||
'v.kulikov' => RolesEnum::BACKEND,
|
||||
'v.yuzkiv' => RolesEnum::BACKEND,
|
||||
'e.kozyrev' => RolesEnum::BACKEND,
|
||||
's.kudryavcev' => RolesEnum::BACKEND,
|
||||
'a.khokhryakov' => RolesEnum::BACKEND,
|
||||
'v.kozachenko' => RolesEnum::BACKEND,
|
||||
'k.benner' => RolesEnum::FRONTEND,
|
||||
'p.aksenov' => RolesEnum::FRONTEND,
|
||||
'ar.markov' => RolesEnum::FRONTEND,
|
||||
'i.zhurkov' => RolesEnum::QA,
|
||||
'e.zyumkina' => RolesEnum::QA,
|
||||
'a.nelyubov' => RolesEnum::QA,
|
||||
'n.merkulov' => RolesEnum::QA,
|
||||
'o.kirichik' => RolesEnum::ANALYTIC,
|
||||
't.baskakova' => RolesEnum::ANALYTIC,
|
||||
'm.larin' => RolesEnum::ANALYTIC,
|
||||
'k.goshko' => RolesEnum::DESIGNER,
|
||||
'e.kazakova' => RolesEnum::DESIGNER,
|
||||
],
|
||||
],
|
||||
'altis' => [
|
||||
'projects' => 'b2b-center',
|
||||
'executors' => [
|
||||
'd.ivashov' => RolesEnum::BACKEND,
|
||||
'm.boytsova' => RolesEnum::ANALYTIC,
|
||||
'm.shabanova' => RolesEnum::ANALYTIC,
|
||||
'm.azarenkova' => RolesEnum::DESIGNER,
|
||||
'a.taranenko' => RolesEnum::BACKEND,
|
||||
'd.zhugel' => RolesEnum::BACKEND,
|
||||
'e.podmarev' => RolesEnum::BACKEND,
|
||||
'e.sokolova' => RolesEnum::BACKEND,
|
||||
'd.pavlov' => RolesEnum::BACKEND,
|
||||
'e.paranyuk' => RolesEnum::FRONTEND,
|
||||
'a.golubev' => RolesEnum::FRONTEND,
|
||||
'a.donnik' => RolesEnum::QA,
|
||||
'e.borkunov' => RolesEnum::QA,
|
||||
'd.androsova' => RolesEnum::QA,
|
||||
'e.plotinskaya' => RolesEnum::QA,
|
||||
|
||||
'a.kirin' => RolesEnum::BACKEND,
|
||||
'v.lushnikov' => RolesEnum::ANALYTIC,
|
||||
'a.dozhun' => RolesEnum::ANALYTIC,
|
||||
'd.borisov' => RolesEnum::BACKEND,
|
||||
'r.schekin' => RolesEnum::BACKEND,
|
||||
'a.fatov' => RolesEnum::BACKEND,
|
||||
'm.arzamastsev' => RolesEnum::BACKEND,
|
||||
'a.kuzin' => RolesEnum::BACKEND,
|
||||
'i.grigorev' => RolesEnum::FRONTEND,
|
||||
'a.drozdov' => RolesEnum::FRONTEND,
|
||||
'n.pilyavets' => RolesEnum::FRONTEND,
|
||||
'r.novikov' => RolesEnum::QA,
|
||||
'k.khayrullina' => RolesEnum::QA,
|
||||
'o.rudakov' => RolesEnum::QA,
|
||||
'e.motygullin' => RolesEnum::QA,
|
||||
]
|
||||
],
|
||||
'srm' => [
|
||||
'projects' => '"SRM (Мои поставщики)", CRM, Bryansk',
|
||||
'executors' => [
|
||||
'a.abramchuk' => RolesEnum::DESIGNER,
|
||||
'i.chikurov' => RolesEnum::ANALYTIC,
|
||||
'a.belyavcova' => RolesEnum::ANALYTIC,
|
||||
'a.afanasenkov' => RolesEnum::BACKEND,
|
||||
'v.evstegneev' => RolesEnum::BACKEND,
|
||||
'm.epikhin' => RolesEnum::BACKEND,
|
||||
'k.suvorov' => RolesEnum::BACKEND,
|
||||
'v.utyuganov' => RolesEnum::BACKEND,
|
||||
'a.shitov' => RolesEnum::BACKEND,
|
||||
'a.filatov' => RolesEnum::BACKEND,
|
||||
'i.monastyrskiy' => RolesEnum::FRONTEND,
|
||||
'd.nasyrov' => RolesEnum::FRONTEND,
|
||||
'm.miklushov' => RolesEnum::QA,
|
||||
'd.mikhaylov' => RolesEnum::QA,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nechaev\Reports;
|
||||
|
||||
use DateInterval;
|
||||
use DatePeriod;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Class DateHelper
|
||||
* @package Nechaev\Reports
|
||||
*/
|
||||
class DateHelper
|
||||
{
|
||||
/**
|
||||
* Подсчитывает количество рабочих дней между двумя датами.
|
||||
*
|
||||
* @param string $startDate Дата начала периода (формат 'Y-m-d').
|
||||
* @param string $endDate Дата окончания периода (формат 'Y-m-d').
|
||||
* @throws \DateMalformedStringException
|
||||
*/
|
||||
public static function countWorkingDays(string $startDate, string $endDate): int {
|
||||
// Преобразуем строки дат в объекты DateTime
|
||||
$start = new DateTime($startDate);
|
||||
$end = new DateTime($endDate);
|
||||
|
||||
$interval = new DateInterval('P1D'); // Интервал — 1 день
|
||||
$period = new DatePeriod($start, $interval, $end);
|
||||
|
||||
$workingDays = 0;
|
||||
|
||||
foreach ($period as $day) {
|
||||
$dayStr = $day->format('Y-m-d');
|
||||
// Проверяем, не является ли день субботой или воскресеньем
|
||||
$isWeekend = in_array($dayStr, self::getHolidays()) || in_array($day->format('N'), [6, 7]);
|
||||
if (!$isWeekend) {
|
||||
$workingDays++;
|
||||
}
|
||||
}
|
||||
|
||||
return $workingDays;
|
||||
}
|
||||
|
||||
public static function getHolidays(): array {
|
||||
return [
|
||||
'2026-01-01', // Новый год
|
||||
'2026-01-02', // Новогодние каникулы
|
||||
'2026-01-03', // Новогодние каникулы
|
||||
'2026-01-04', // Новогодние каникулы
|
||||
'2026-01-05', // Новогодние каникулы
|
||||
'2026-01-06', // Новогодние каникулы
|
||||
'2026-01-07', // Рождество Христово
|
||||
'2026-01-08', // Новогодние каникулы
|
||||
'2026-01-09', // Перенос выходного с 3 января
|
||||
'2026-01-11', // Перенос выходного с 4 января
|
||||
'2026-02-23', // День защитника Отечества
|
||||
'2026-03-09', // Международный женский день
|
||||
'2026-05-01', // Праздник Весны и Труда
|
||||
'2026-05-11', // День Победы
|
||||
'2026-06-12', // День России
|
||||
'2026-11-04', // День народного единства
|
||||
'2026-12-31' // Перенос выходного с 4 января
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nechaev\Reports;
|
||||
|
||||
/**
|
||||
* Class RolesEnum
|
||||
* @package Nechaev\Reports
|
||||
*/
|
||||
class RolesEnum
|
||||
{
|
||||
public const ANALYTIC = 'analytic';
|
||||
public const BACKEND = 'backend';
|
||||
public const DESIGNER = 'designer';
|
||||
public const FRONTEND = 'frontend';
|
||||
public const QA = 'qa';
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nechaev\Reports;
|
||||
|
||||
/**
|
||||
* Class StatusHelper
|
||||
* @package Nechaev\Reports
|
||||
* @author Bryansk_team <bryansk_team@b2b-center.ru>
|
||||
*/
|
||||
class StatusHelper
|
||||
{
|
||||
public static function getInWorkStatuses(): array {
|
||||
return [
|
||||
'Разработка',
|
||||
"В работе",
|
||||
];
|
||||
}
|
||||
|
||||
public static function getInReviewStatuses(): array {
|
||||
return [
|
||||
'On code-review',
|
||||
"Review",
|
||||
];
|
||||
}
|
||||
|
||||
public static function getInTestingStatuses(): array {
|
||||
return [
|
||||
"Тестирование",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data ["status" => "data"]
|
||||
* @param string[] $statuses
|
||||
* @return mixed|null
|
||||
*/
|
||||
public static function getDataByStatus(array $data, array $statuses) {
|
||||
foreach ($statuses as $status) {
|
||||
if (isset($data[$status])) {
|
||||
return $data[$status];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nechaev\Reports;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Class Task
|
||||
* @package Nechaev\Reports
|
||||
*/
|
||||
class Task
|
||||
{
|
||||
private string $type; // Тип таски: bug, backend...
|
||||
private string $key; // Номер задачи
|
||||
private string $description; // Заголовок задачи
|
||||
private string $assignee; // Исполнитель
|
||||
private int $estimate; // Время оценки в секундах
|
||||
private int $totalTime; // суммарное время потраченное на работу в секундах
|
||||
private string $dateTakeInWork; // дата-время взятия в работу
|
||||
private string $resolutionDate; // Дата-время завершения задачи
|
||||
|
||||
private int $leadTime; // System Lead Time в рабочих днях
|
||||
private float $recast; // Переработка
|
||||
|
||||
private float $efficiency; // КПД
|
||||
|
||||
private int $countReview; // Количество заходов на ревью
|
||||
private int $countTest; // Количество заходов на тест
|
||||
|
||||
public function __construct(
|
||||
string $type,
|
||||
string $key,
|
||||
string $description,
|
||||
string $assignee,
|
||||
?int $estimate,
|
||||
int $totalTime,
|
||||
string $dateTakeInWork,
|
||||
string $resolutionDate,
|
||||
?int $countReview,
|
||||
?int $countTest,
|
||||
) {
|
||||
$this->type = $type;
|
||||
$this->key = $key;
|
||||
$this->description = $description;
|
||||
$this->assignee = $assignee;
|
||||
$this->estimate = $estimate ?? 0;
|
||||
$this->totalTime = $totalTime;
|
||||
$this->dateTakeInWork = $dateTakeInWork;
|
||||
$this->resolutionDate = $resolutionDate;
|
||||
|
||||
$this->countReview = $countReview ?? 0;
|
||||
$this->countTest = $countTest ?? 0;
|
||||
|
||||
$this->leadTime = DateHelper::countWorkingDays($dateTakeInWork, $resolutionDate);
|
||||
$this->recast = ($this->estimate == 0) ? 0: round($totalTime/$estimate,4)-1;
|
||||
// totalTime в секундах, leadTime в днях
|
||||
$this->efficiency = round(($totalTime/3600)/$this->leadTime*8, 2);
|
||||
}
|
||||
|
||||
public function toCSV(): string {
|
||||
return implode(";", [
|
||||
$this->type,
|
||||
$this->key,
|
||||
$this->description,
|
||||
$this->assignee,
|
||||
new DateTime($this->dateTakeInWork)->format('Y-m-d'),
|
||||
new DateTime($this->resolutionDate)->format('Y-m-d'),
|
||||
$this->leadTime,
|
||||
round($this->estimate/3600,2), // переводим в часы
|
||||
round($this->totalTime/3600, 2), // переводим в часы
|
||||
$this->recast,
|
||||
$this->efficiency,
|
||||
$this->countReview,
|
||||
$this->countTest,
|
||||
]);
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->toCSV()."\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
/**
|
||||
* Получает количество раз нахождения задачи в каждом статусе.
|
||||
*
|
||||
* @param string $issueKey Ключ задачи (например, PROJ-123)
|
||||
* @param Client $client Экземпляр клиента Guzzle с авторизацией
|
||||
* @return array Ассоциативный массив ['In Progress' => [3,"2026-04-15T17:00:49.000+0300"], 'Done' => [1,"2026-04-15T17:00:49.000+0300"]]
|
||||
* @throws Exception
|
||||
*/
|
||||
function getStatusForIssue(string $issueKey, Client $client): array {
|
||||
$statusCounts = [];
|
||||
$statusDates = [];
|
||||
|
||||
try {
|
||||
$response = $client->get("rest/api/2/issue/{$issueKey}", [
|
||||
'query' => [
|
||||
'expand' => 'changelog',
|
||||
],
|
||||
]);
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception("Ошибка декодирования JSON для задачи {$issueKey}.");
|
||||
}
|
||||
|
||||
// Обрабатываем текущую "пачку" записей истории
|
||||
foreach ($data['changelog']['histories'] as $historyItem) {
|
||||
foreach ($historyItem['items'] as $item) {
|
||||
// Ищем только изменения поля 'status'
|
||||
//var_dump($item); die();
|
||||
if ($item['field'] === 'status') {
|
||||
$dateStr = $historyItem['created'];
|
||||
|
||||
$statusName = $item['toString'];
|
||||
if (!isset($statusCounts[$statusName])) {
|
||||
$statusCounts[$statusName] = 0;
|
||||
}
|
||||
$statusCounts[$statusName]++;
|
||||
if (!isset($statusDates[$statusName])) {
|
||||
$statusDates[$statusName] = $dateStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если вернули меньше, чем запрошено, или дошли до конца - выходим
|
||||
} catch (GuzzleException $e) {
|
||||
echo "⚠️ Ошибка при получении истории для {$issueKey}: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
return array_merge_recursive($statusCounts, $statusDates);
|
||||
}
|
||||
|
||||
function filterByResolution(array $data): array {
|
||||
$result = [];
|
||||
|
||||
foreach($data as $issue) {
|
||||
if ($issue['fields']['resolution']['id'] == "1") {
|
||||
$result[] = $issue;
|
||||
} else {
|
||||
echo "Выкинуто из отчета ".$issue["key"]." из-за статуса ".$issue['fields']['resolution']['id']."\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string[] $executors
|
||||
*/
|
||||
function filterByExecutors(array $data, array $executors): array {
|
||||
$result = [];
|
||||
foreach($data as $issue) {
|
||||
if (in_array($issue["fields"]["assignee"]["name"], $executors)) {
|
||||
$result[] = $issue;
|
||||
} else {
|
||||
echo "Выкинуто из отчета ".$issue["key"]." из-за исполнителя ".$issue["fields"]["assignee"]["name"]."\n";
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
global$config; #!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @var array $config
|
||||
* @var array $teams
|
||||
* @var string[] $holidays
|
||||
*/
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Nechaev\Reports\StatusHelper;
|
||||
|
||||
require __DIR__ . '/../init.php';
|
||||
require __DIR__ . '/functions.php';
|
||||
|
||||
// --- 1. Обработка параметров командной строки ---
|
||||
$options = getopt('', ['start-date:', 'end-date:']);
|
||||
|
||||
if (empty($options['start-date']) || empty($options['end-date'])) {
|
||||
echo "Использование: php jira_export.php --start-date=ГГГГ-ММ-ДД --end-date=ГГГГ-ММ-ДД\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$startDate = $options['start-date'];
|
||||
$endDate = $options['end-date'];
|
||||
|
||||
// Валидация формата дат
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
|
||||
die("Ошибка: даты должны быть в формате ГГГГ-ММ-ДД\n");
|
||||
}
|
||||
|
||||
// Извлекаем настройки из конфига
|
||||
$jiraUrl = $config['jira']['url'];
|
||||
$apiToken = $config['jira']['apiToken'];
|
||||
$maxResults = $config['export']['maxResults'];
|
||||
|
||||
// --- 2. Инициализация клиента Guzzle ---
|
||||
$client = new Client([
|
||||
'base_uri' => $jiraUrl,
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $apiToken,
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
foreach ($teams as $team => $teamData) {
|
||||
echo "Расчет для команды {$team}\n";
|
||||
$filename = "{$team}_tasks_{$startDate}_to_{$endDate}.csv";
|
||||
|
||||
// --- 3. Подготовка JQL-запроса ---
|
||||
// Запрос ищет задачи, у которых статус изменился на "Done" в указанный период.
|
||||
$jql = "project in (".$teamData['projects'].") AND issuetype in standardIssueTypes() AND issuetype not in (Epic, \"Admin work\") AND resolutiondate > \"{$startDate}\" AND resolutiondate < \"{$endDate}\"";
|
||||
|
||||
$params = [
|
||||
'jql' => $jql,
|
||||
'maxResults' => $maxResults,
|
||||
'fields' => 'issuetype,key,summary,assignee,resolution,aggregatetimeoriginalestimate,aggregateprogress,resolutiondate',
|
||||
];
|
||||
|
||||
try {
|
||||
// --- 4. Обработка пагинации ---
|
||||
// Если задач больше, чем maxResults, Jira вернет только часть.
|
||||
// Мы будем запрашивать страницы до тех пор, пока не получим все.
|
||||
$allIssues = [];
|
||||
$startAt = 0;
|
||||
$totalFetched = 0;
|
||||
|
||||
do {
|
||||
$response = $client->get('rest/api/2/search', [
|
||||
'query' => $params + ['startAt' => $startAt]
|
||||
]);
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception("Ошибка декодирования JSON-ответа от Jira.");
|
||||
}
|
||||
|
||||
$issues = $data['issues'] ?? [];
|
||||
$allIssues = array_merge($allIssues, $issues);
|
||||
|
||||
// Обновляем счетчики для следующей итерации
|
||||
$totalFetched += count($issues);
|
||||
$startAt += count($issues);
|
||||
|
||||
echo "Получено задач: {$totalFetched} из {$data['total']}\n";
|
||||
|
||||
} while ($startAt < ($data['total'] ?? 0));
|
||||
|
||||
if (!empty($allIssues)) {
|
||||
// Убираем дубликаты, там где изменения не требуются
|
||||
$allIssues = filterByResolution($allIssues);
|
||||
|
||||
// Убираем задачи, исполнители на которых не исполнители
|
||||
$allIssues = filterByExecutors($allIssues, array_keys($teamData['executors']));
|
||||
|
||||
//file_put_contents($filename.".json", json_encode($allIssues, JSON_PRETTY_PRINT));die();
|
||||
|
||||
echo "Убрали лишнее, осталось ".count($allIssues)."\n";
|
||||
|
||||
$task = null;
|
||||
foreach ($allIssues as $i => $issue) {
|
||||
$statuses = getStatusForIssue($issue['key'], $client);
|
||||
//$allIssues[$i]['countStatuses'] = $statuses;
|
||||
$firstDateInStatus = array_map(function($value) {
|
||||
return is_array($value) && count($value) > 1 ? $value[1] : null;
|
||||
}, $statuses);
|
||||
|
||||
$countStatus = array_map(function($value) {
|
||||
return is_array($value) && count($value) > 1 ? $value[0] : null;
|
||||
}, $statuses);
|
||||
|
||||
$task = new \Nechaev\Reports\Task(
|
||||
$issue["fields"]["issuetype"]["name"],
|
||||
$issue['key'],
|
||||
$issue["fields"]["summary"],
|
||||
$issue["fields"]["assignee"]["name"],
|
||||
$issue["fields"]["aggregatetimeoriginalestimate"],
|
||||
$issue["fields"]["aggregateprogress"]["progress"],
|
||||
StatusHelper::getDataByStatus($firstDateInStatus, StatusHelper::getInWorkStatuses()),
|
||||
$issue["fields"]["resolutiondate"],
|
||||
StatusHelper::getDataByStatus($countStatus, StatusHelper::getInReviewStatuses()),
|
||||
StatusHelper::getDataByStatus($countStatus, StatusHelper::getInTestingStatuses()),
|
||||
);
|
||||
file_put_contents($filename, $task, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- 5. Сохранение в файл ---
|
||||
|
||||
|
||||
echo "Данные успешно сохранены в файл: {$filename}\n";
|
||||
} else {
|
||||
echo "За указанный период завершенных задач не найдено.\n";
|
||||
}
|
||||
|
||||
} catch (GuzzleException | Exception $e) {
|
||||
echo "Ошибка: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue