* initial commit
This commit is contained in:
commit
863e6e502b
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.idea
|
||||||
|
composer.phar
|
||||||
|
|
||||||
|
clover.xml
|
||||||
|
coveralls-upload.json
|
||||||
|
phpunit.xml
|
||||||
|
vendor/
|
||||||
46
bin/clear-config-cache.php
Normal file
46
bin/clear-config-cache.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Script for clearing the configuration cache.
|
||||||
|
*
|
||||||
|
* Can also be invoked as `composer clear-config-cache`.
|
||||||
|
*
|
||||||
|
* @see https://github.com/zendframework/zend-expressive-skeleton for the canonical source repository
|
||||||
|
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
|
||||||
|
* @license https://github.com/zendframework/zend-expressive-skeleton/blob/master/LICENSE.md New BSD License
|
||||||
|
*/
|
||||||
|
|
||||||
|
chdir(__DIR__ . '/../');
|
||||||
|
|
||||||
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
|
$config = include 'config/config.php';
|
||||||
|
|
||||||
|
if (! isset($config['config_cache_path'])) {
|
||||||
|
echo "No configuration cache path found" . PHP_EOL;
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! file_exists($config['config_cache_path'])) {
|
||||||
|
printf(
|
||||||
|
"Configured config cache file '%s' not found%s",
|
||||||
|
$config['config_cache_path'],
|
||||||
|
PHP_EOL
|
||||||
|
);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === unlink($config['config_cache_path'])) {
|
||||||
|
printf(
|
||||||
|
"Error removing config cache file '%s'%s",
|
||||||
|
$config['config_cache_path'],
|
||||||
|
PHP_EOL
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
"Removed configured config cache file '%s'%s",
|
||||||
|
$config['config_cache_path'],
|
||||||
|
PHP_EOL
|
||||||
|
);
|
||||||
|
exit(0);
|
||||||
60
composer.json
Normal file
60
composer.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "zendframework/zend-expressive-skeleton",
|
||||||
|
"description": "Zend expressive skeleton. Begin developing PSR-7 middleware applications in seconds!",
|
||||||
|
"type": "project",
|
||||||
|
"homepage": "https://github.com/zendframework/zend-expressive-skeleton",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.1",
|
||||||
|
"roave/security-advisories": "dev-master",
|
||||||
|
"zendframework/zend-component-installer": "^1.0",
|
||||||
|
"zendframework/zend-config": "^3.1",
|
||||||
|
"zendframework/zend-config-aggregator": "^1.0",
|
||||||
|
"zendframework/zend-expressive": "^2.0.2",
|
||||||
|
"zendframework/zend-expressive-fastroute": "^2.0",
|
||||||
|
"zendframework/zend-expressive-helpers": "^4.0",
|
||||||
|
"zendframework/zend-expressive-platesrenderer": "^1.3.1",
|
||||||
|
"zendframework/zend-http": "^2.6",
|
||||||
|
"zendframework/zend-json": "^3.0",
|
||||||
|
"zendframework/zend-servicemanager": "^3.3",
|
||||||
|
"zendframework/zend-stdlib": "^3.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^6.0.8 || ^5.7.15",
|
||||||
|
"squizlabs/php_codesniffer": "^2.8.1",
|
||||||
|
"zfcampus/zf-development-mode": "^3.1",
|
||||||
|
"filp/whoops": "^2.1.7"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/App/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"AppTest\\": "test/AppTest/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"post-create-project-cmd": [
|
||||||
|
"@development-enable"
|
||||||
|
],
|
||||||
|
"development-disable": "zf-development-mode disable",
|
||||||
|
"development-enable": "zf-development-mode enable",
|
||||||
|
"development-status": "zf-development-mode status",
|
||||||
|
"check": [
|
||||||
|
"@cs-check",
|
||||||
|
"@test"
|
||||||
|
],
|
||||||
|
"clear-config-cache": "php bin/clear-config-cache.php",
|
||||||
|
"cs-check": "phpcs",
|
||||||
|
"cs-fix": "phpcbf",
|
||||||
|
"serve": "php -S 0.0.0.0:8080 -t public public/index.php",
|
||||||
|
"test": "phpunit --colors=always",
|
||||||
|
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
|
||||||
|
"upload-coverage": "coveralls -v"
|
||||||
|
}
|
||||||
|
}
|
||||||
3208
composer.lock
generated
Normal file
3208
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
config/.gitignore
vendored
Normal file
1
config/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
development.config.php
|
||||||
2
config/autoload/.gitignore
vendored
Normal file
2
config/autoload/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
local.php
|
||||||
|
*.local.php
|
||||||
39
config/autoload/dependencies.global.php
Normal file
39
config/autoload/dependencies.global.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Zend\Expressive\Application;
|
||||||
|
use Zend\Expressive\Container;
|
||||||
|
use Zend\Expressive\Delegate;
|
||||||
|
use Zend\Expressive\Helper;
|
||||||
|
use Zend\Expressive\Middleware;
|
||||||
|
|
||||||
|
return [
|
||||||
|
// Provides application-wide services.
|
||||||
|
// We recommend using fully-qualified class names whenever possible as
|
||||||
|
// service names.
|
||||||
|
'dependencies' => [
|
||||||
|
// Use 'aliases' to alias a service name to another service. The
|
||||||
|
// key is the alias name, the value is the service to which it points.
|
||||||
|
'aliases' => [
|
||||||
|
'Zend\Expressive\Delegate\DefaultDelegate' => Delegate\NotFoundDelegate::class,
|
||||||
|
],
|
||||||
|
// Use 'invokables' for constructor-less services, or services that do
|
||||||
|
// not require arguments to the constructor. Map a service name to the
|
||||||
|
// class name.
|
||||||
|
'invokables' => [
|
||||||
|
// Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class,
|
||||||
|
Helper\ServerUrlHelper::class => Helper\ServerUrlHelper::class,
|
||||||
|
],
|
||||||
|
// Use 'factories' for services provided by callbacks/factory classes.
|
||||||
|
'factories' => [
|
||||||
|
Application::class => Container\ApplicationFactory::class,
|
||||||
|
Delegate\NotFoundDelegate::class => Container\NotFoundDelegateFactory::class,
|
||||||
|
Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class,
|
||||||
|
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
|
||||||
|
Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class,
|
||||||
|
|
||||||
|
Zend\Stratigility\Middleware\ErrorHandler::class => Container\ErrorHandlerFactory::class,
|
||||||
|
Middleware\ErrorResponseGenerator::class => Container\ErrorResponseGeneratorFactory::class,
|
||||||
|
Middleware\NotFoundHandler::class => Container\NotFoundHandlerFactory::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
34
config/autoload/development.local.php.dist
Normal file
34
config/autoload/development.local.php.dist
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Development-only configuration.
|
||||||
|
*
|
||||||
|
* Put settings you want enabled when under development mode in this file, and
|
||||||
|
* check it into your repository.
|
||||||
|
*
|
||||||
|
* Developers on your team will then automatically enable them by calling on
|
||||||
|
* `composer development-enable`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Zend\Expressive\Container;
|
||||||
|
use Zend\Expressive\Middleware\ErrorResponseGenerator;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'dependencies' => [
|
||||||
|
'invokables' => [
|
||||||
|
],
|
||||||
|
'factories' => [
|
||||||
|
ErrorResponseGenerator::class => Container\WhoopsErrorResponseGeneratorFactory::class,
|
||||||
|
'Zend\Expressive\Whoops' => Container\WhoopsFactory::class,
|
||||||
|
'Zend\Expressive\WhoopsPageHandler' => Container\WhoopsPageHandlerFactory::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'whoops' => [
|
||||||
|
'json_exceptions' => [
|
||||||
|
'display' => true,
|
||||||
|
'show_trace' => true,
|
||||||
|
'ajax_only' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
11
config/autoload/local.php.dist
Normal file
11
config/autoload/local.php.dist
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local configuration.
|
||||||
|
*
|
||||||
|
* Copy this file to `local.php` and change its settings as required.
|
||||||
|
* `local.php` is ignored by git and safe to use for local and sensitive data like usernames and passwords.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [
|
||||||
|
];
|
||||||
12
config/autoload/router.global.php
Normal file
12
config/autoload/router.global.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Zend\Expressive\Router\FastRouteRouter;
|
||||||
|
use Zend\Expressive\Router\RouterInterface;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'dependencies' => [
|
||||||
|
'invokables' => [
|
||||||
|
RouterInterface::class => FastRouteRouter::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
16
config/autoload/templates.global.php
Normal file
16
config/autoload/templates.global.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Zend\Expressive\Plates\PlatesRendererFactory;
|
||||||
|
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'dependencies' => [
|
||||||
|
'factories' => [
|
||||||
|
TemplateRendererInterface::class => PlatesRendererFactory::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'templates' => [
|
||||||
|
'extension' => 'phtml',
|
||||||
|
],
|
||||||
|
];
|
||||||
27
config/autoload/zend-expressive.global.php
Normal file
27
config/autoload/zend-expressive.global.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Zend\ConfigAggregator\ConfigAggregator;
|
||||||
|
|
||||||
|
return [
|
||||||
|
// Toggle the configuration cache. Set this to boolean false, or remove the
|
||||||
|
// directive, to disable configuration caching. Toggling development mode
|
||||||
|
// will also disable it by default; clear the configuration cache using
|
||||||
|
// `composer clear-config-cache`.
|
||||||
|
ConfigAggregator::ENABLE_CACHE => true,
|
||||||
|
|
||||||
|
// Enable debugging; typically used to provide debugging information within templates.
|
||||||
|
'debug' => false,
|
||||||
|
|
||||||
|
'zend-expressive' => [
|
||||||
|
// Enable programmatic pipeline: Any `middleware_pipeline` or `routes`
|
||||||
|
// configuration will be ignored when creating the `Application` instance.
|
||||||
|
'programmatic_pipeline' => true,
|
||||||
|
|
||||||
|
// Provide templates for the error handling middleware to use when
|
||||||
|
// generating responses.
|
||||||
|
'error_handler' => [
|
||||||
|
'template_404' => 'error::404',
|
||||||
|
'template_error' => 'error::error',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
33
config/config.php
Normal file
33
config/config.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Zend\ConfigAggregator\ArrayProvider;
|
||||||
|
use Zend\ConfigAggregator\ConfigAggregator;
|
||||||
|
use Zend\ConfigAggregator\PhpFileProvider;
|
||||||
|
|
||||||
|
// To enable or disable caching, set the `ConfigAggregator::ENABLE_CACHE` boolean in
|
||||||
|
// `config/autoload/local.php`.
|
||||||
|
$cacheConfig = [
|
||||||
|
'config_cache_path' => 'data/config-cache.php',
|
||||||
|
];
|
||||||
|
|
||||||
|
$aggregator = new ConfigAggregator([
|
||||||
|
\Zend\Validator\ConfigProvider::class,
|
||||||
|
// Include cache configuration
|
||||||
|
new ArrayProvider($cacheConfig),
|
||||||
|
|
||||||
|
// Default App module config
|
||||||
|
App\ConfigProvider::class,
|
||||||
|
|
||||||
|
// Load application config in a pre-defined order in such a way that local settings
|
||||||
|
// overwrite global settings. (Loaded as first to last):
|
||||||
|
// - `global.php`
|
||||||
|
// - `*.global.php`
|
||||||
|
// - `local.php`
|
||||||
|
// - `*.local.php`
|
||||||
|
new PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||||
|
|
||||||
|
// Load development config if it exists
|
||||||
|
new PhpFileProvider('config/development.config.php'),
|
||||||
|
], $cacheConfig['config_cache_path']);
|
||||||
|
|
||||||
|
return $aggregator->getMergedConfig();
|
||||||
16
config/container.php
Normal file
16
config/container.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Zend\ServiceManager\Config;
|
||||||
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
|
// Load configuration
|
||||||
|
$config = require __DIR__ . '/config.php';
|
||||||
|
|
||||||
|
// Build container
|
||||||
|
$container = new ServiceManager();
|
||||||
|
(new Config($config['dependencies']))->configureServiceManager($container);
|
||||||
|
|
||||||
|
// Inject config
|
||||||
|
$container->setService('config', $config);
|
||||||
|
|
||||||
|
return $container;
|
||||||
29
config/development.config.php.dist
Normal file
29
config/development.config.php.dist
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File required to allow enablement of development mode.
|
||||||
|
*
|
||||||
|
* For use with the zf-development-mode tool.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* $ composer development-disable
|
||||||
|
* $ composer development-enable
|
||||||
|
* $ composer development-status
|
||||||
|
*
|
||||||
|
* DO NOT MODIFY THIS FILE.
|
||||||
|
*
|
||||||
|
* Provide your own development-mode settings by editing the file
|
||||||
|
* `config/autoload/development.local.php.dist`.
|
||||||
|
*
|
||||||
|
* Because this file is aggregated last, it simply ensures:
|
||||||
|
*
|
||||||
|
* - The `debug` flag is _enabled_.
|
||||||
|
* - Configuration caching is _disabled_.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Zend\ConfigAggregator\ConfigAggregator;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'debug' => true,
|
||||||
|
ConfigAggregator::ENABLE_CACHE => false,
|
||||||
|
];
|
||||||
55
config/pipeline.php
Normal file
55
config/pipeline.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Zend\Expressive\Helper\ServerUrlMiddleware;
|
||||||
|
use Zend\Expressive\Helper\UrlHelperMiddleware;
|
||||||
|
use Zend\Expressive\Middleware\ImplicitHeadMiddleware;
|
||||||
|
use Zend\Expressive\Middleware\ImplicitOptionsMiddleware;
|
||||||
|
use Zend\Expressive\Middleware\NotFoundHandler;
|
||||||
|
use Zend\Stratigility\Middleware\ErrorHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup middleware pipeline:
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The error handler should be the first (most outer) middleware to catch
|
||||||
|
// all Exceptions.
|
||||||
|
$app->pipe(ErrorHandler::class);
|
||||||
|
$app->pipe(ServerUrlMiddleware::class);
|
||||||
|
|
||||||
|
// Pipe more middleware here that you want to execute on every request:
|
||||||
|
// - bootstrapping
|
||||||
|
// - pre-conditions
|
||||||
|
// - modifications to outgoing responses
|
||||||
|
//
|
||||||
|
// Piped Middleware may be either callables or service names. Middleware may
|
||||||
|
// also be passed as an array; each item in the array must resolve to
|
||||||
|
// middleware eventually (i.e., callable or service name).
|
||||||
|
//
|
||||||
|
// Middleware can be attached to specific paths, allowing you to mix and match
|
||||||
|
// applications under a common domain. The handlers in each middleware
|
||||||
|
// attached this way will see a URI with the MATCHED PATH SEGMENT REMOVED!!!
|
||||||
|
//
|
||||||
|
// - $app->pipe('/api', $apiMiddleware);
|
||||||
|
// - $app->pipe('/docs', $apiDocMiddleware);
|
||||||
|
// - $app->pipe('/files', $filesMiddleware);
|
||||||
|
|
||||||
|
// Register the routing middleware in the middleware pipeline
|
||||||
|
$app->pipeRoutingMiddleware();
|
||||||
|
$app->pipe(ImplicitHeadMiddleware::class);
|
||||||
|
$app->pipe(ImplicitOptionsMiddleware::class);
|
||||||
|
$app->pipe(UrlHelperMiddleware::class);
|
||||||
|
|
||||||
|
// Add more middleware here that needs to introspect the routing results; this
|
||||||
|
// might include:
|
||||||
|
//
|
||||||
|
// - route-based authentication
|
||||||
|
// - route-based validation
|
||||||
|
// - etc.
|
||||||
|
|
||||||
|
// Register the dispatch middleware in the middleware pipeline
|
||||||
|
$app->pipeDispatchMiddleware();
|
||||||
|
|
||||||
|
// At this point, if no Response is return by any middleware, the
|
||||||
|
// NotFoundHandler kicks in; alternately, you can provide other fallback
|
||||||
|
// middleware to execute.
|
||||||
|
$app->pipe(NotFoundHandler::class);
|
||||||
31
config/routes.php
Normal file
31
config/routes.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Setup routes with a single request method:
|
||||||
|
*
|
||||||
|
* $app->get('/', App\Action\HomePageAction::class, 'home');
|
||||||
|
* $app->post('/album', App\Action\AlbumCreateAction::class, 'album.create');
|
||||||
|
* $app->put('/album/:id', App\Action\AlbumUpdateAction::class, 'album.put');
|
||||||
|
* $app->patch('/album/:id', App\Action\AlbumUpdateAction::class, 'album.patch');
|
||||||
|
* $app->delete('/album/:id', App\Action\AlbumDeleteAction::class, 'album.delete');
|
||||||
|
*
|
||||||
|
* Or with multiple request methods:
|
||||||
|
*
|
||||||
|
* $app->route('/contact', App\Action\ContactAction::class, ['GET', 'POST', ...], 'contact');
|
||||||
|
*
|
||||||
|
* Or handling all request methods:
|
||||||
|
*
|
||||||
|
* $app->route('/contact', App\Action\ContactAction::class)->setName('contact');
|
||||||
|
*
|
||||||
|
* or:
|
||||||
|
*
|
||||||
|
* $app->route(
|
||||||
|
* '/contact',
|
||||||
|
* App\Action\ContactAction::class,
|
||||||
|
* Zend\Expressive\Router\Route::HTTP_METHOD_ANY,
|
||||||
|
* 'contact'
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
|
||||||
|
$app->get('/', App\Action\HomePageAction::class, 'home');
|
||||||
|
$app->get('/api/ping', App\Action\PingAction::class, 'api.ping');
|
||||||
|
$app->get('/api/dbg', App\Action\DbgAction::class, 'api.dbg');
|
||||||
2
data/.gitignore
vendored
Normal file
2
data/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
20
phpcs.xml.dist
Normal file
20
phpcs.xml.dist
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<ruleset name="Expressive Skeleton coding standard">
|
||||||
|
<description>Expressive Skeleton coding standard</description>
|
||||||
|
|
||||||
|
<!-- display progress -->
|
||||||
|
<arg value="p"/>
|
||||||
|
<arg name="colors"/>
|
||||||
|
|
||||||
|
<!-- inherit rules from: -->
|
||||||
|
<rule ref="PSR2"/>
|
||||||
|
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
|
||||||
|
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
|
||||||
|
<properties>
|
||||||
|
<property name="ignoreBlankLines" value="false"/>
|
||||||
|
</properties>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Paths to check -->
|
||||||
|
<file>src</file>
|
||||||
|
</ruleset>
|
||||||
17
phpunit.xml.dist
Normal file
17
phpunit.xml.dist
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="App\\Tests">
|
||||||
|
<directory>./test</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory suffix=".php">./src</directory>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
||||||
17
public/.htaccess
Normal file
17
public/.htaccess
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
# The following rule tells Apache that if the requested filename
|
||||||
|
# exists, simply serve it.
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -s [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -l [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -d
|
||||||
|
RewriteRule ^.*$ - [NC,L]
|
||||||
|
|
||||||
|
# The following rewrites all other queries to index.php. The
|
||||||
|
# condition ensures that if you are using Apache aliases to do
|
||||||
|
# mass virtual hosting, the base path will be prepended to
|
||||||
|
# allow proper resolution of the index.php file; it will work
|
||||||
|
# in non-aliased environments as well, providing a safe, one-size
|
||||||
|
# fits all solution.
|
||||||
|
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
|
||||||
|
RewriteRule ^(.*) - [E=BASE:%1]
|
||||||
|
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
29
public/index.php
Normal file
29
public/index.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Delegate static file requests back to the PHP built-in webserver
|
||||||
|
if (php_sapi_name() === 'cli-server'
|
||||||
|
&& is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
chdir(dirname(__DIR__));
|
||||||
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Self-called anonymous function that creates its own scope and keep the global namespace clean.
|
||||||
|
*/
|
||||||
|
call_user_func(function () {
|
||||||
|
/** @var \Interop\Container\ContainerInterface $container */
|
||||||
|
$container = require 'config/container.php';
|
||||||
|
|
||||||
|
/** @var \Zend\Expressive\Application $app */
|
||||||
|
$app = $container->get(\Zend\Expressive\Application::class);
|
||||||
|
|
||||||
|
// Import programmatic/declarative middleware pipeline and routing
|
||||||
|
// configuration statements
|
||||||
|
require 'config/pipeline.php';
|
||||||
|
require 'config/routes.php';
|
||||||
|
|
||||||
|
$app->run();
|
||||||
|
});
|
||||||
BIN
public/zf-logo.png
Normal file
BIN
public/zf-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 600 B |
29
src/App/Action/DbgAction.php
Normal file
29
src/App/Action/DbgAction.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Action;
|
||||||
|
|
||||||
|
use App\Service\JiraCollectorService;
|
||||||
|
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||||
|
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
|
||||||
|
|
||||||
|
class DbgAction implements ServerMiddlewareInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var JiraCollectorService
|
||||||
|
*/
|
||||||
|
private $jiraCollectorService;
|
||||||
|
|
||||||
|
public function __construct(JiraCollectorService $jiraCollectorService)
|
||||||
|
{
|
||||||
|
$this->jiraCollectorService = $jiraCollectorService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
|
||||||
|
{
|
||||||
|
$chartData = $this->jiraCollectorService->getChartData();
|
||||||
|
return new JsonResponse($chartData);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/App/Action/DbgFactory.php
Normal file
17
src/App/Action/DbgFactory.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Action;
|
||||||
|
|
||||||
|
use App\Service\JiraCollectorService;
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
|
||||||
|
class DbgFactory
|
||||||
|
{
|
||||||
|
public function __invoke(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
/** @var JiraCollectorService $jiraCollectorService */
|
||||||
|
$jiraCollectorService = $container->get(JiraCollectorService::class);
|
||||||
|
|
||||||
|
return new DbgAction($jiraCollectorService);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/App/Action/HomePageAction.php
Normal file
32
src/App/Action/HomePageAction.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Action;
|
||||||
|
|
||||||
|
use App\Service\JiraCollectorService;
|
||||||
|
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||||
|
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Zend\Diactoros\Response\HtmlResponse;
|
||||||
|
use Zend\Expressive\Template;
|
||||||
|
|
||||||
|
class HomePageAction implements ServerMiddlewareInterface
|
||||||
|
{
|
||||||
|
private $template;
|
||||||
|
private $jiraService;
|
||||||
|
|
||||||
|
public function __construct(Template\TemplateRendererInterface $template,
|
||||||
|
JiraCollectorService $jiraService)
|
||||||
|
{
|
||||||
|
$this->template = $template;
|
||||||
|
$this->jiraService = $jiraService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'chartRows' => $this->jiraService->getChartData(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return new HtmlResponse($this->template->render('app::home-page', $data));
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/App/Action/HomePageFactory.php
Normal file
17
src/App/Action/HomePageFactory.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Action;
|
||||||
|
|
||||||
|
use App\Service\JiraCollectorService;
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||||
|
|
||||||
|
class HomePageFactory
|
||||||
|
{
|
||||||
|
public function __invoke(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$jiraCollector = $container->get(JiraCollectorService::class);
|
||||||
|
$template = $container->get(TemplateRendererInterface::class);
|
||||||
|
return new HomePageAction($template, $jiraCollector);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/App/Action/PingAction.php
Normal file
16
src/App/Action/PingAction.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Action;
|
||||||
|
|
||||||
|
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||||
|
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
|
||||||
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
class PingAction implements ServerMiddlewareInterface
|
||||||
|
{
|
||||||
|
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
|
||||||
|
{
|
||||||
|
return new JsonResponse(['ack' => time()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/App/ConfigProvider.php
Normal file
87
src/App/ConfigProvider.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Zend\Config\Config;
|
||||||
|
use Zend\Http\Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration provider for the App module
|
||||||
|
*
|
||||||
|
* @see https://docs.zendframework.com/zend-component-installer/
|
||||||
|
*/
|
||||||
|
class ConfigProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the configuration array
|
||||||
|
*
|
||||||
|
* To add a bit of a structure, each section is defined in a separate
|
||||||
|
* method which returns an array with its configuration.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function __invoke()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'dependencies' => $this->getDependencies(),
|
||||||
|
'templates' => $this->getTemplates(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the container dependencies
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDependencies()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'invokables' => [
|
||||||
|
Action\PingAction::class => Action\PingAction::class,
|
||||||
|
],
|
||||||
|
'factories' => [
|
||||||
|
Action\HomePageAction::class => Action\HomePageFactory::class,
|
||||||
|
Action\DbgAction::class => Action\DbgFactory::class,
|
||||||
|
|
||||||
|
Service\JiraCollectorService::class => Service\JiraCollectorServiceFactory::class,
|
||||||
|
Client::class => function(ContainerInterface $container): Client {
|
||||||
|
$configArray = $container->get('config');
|
||||||
|
$config = new Config($configArray['app.config']);
|
||||||
|
|
||||||
|
$httpClient = new Client();
|
||||||
|
$httpClient->setAdapter($curlAdapter = new Client\Adapter\Curl());
|
||||||
|
$curlAdapter->setOptions([
|
||||||
|
'timeout' => 300,
|
||||||
|
]);
|
||||||
|
$curlAdapter
|
||||||
|
->setCurlOption(CURLOPT_SSL_VERIFYPEER, false)
|
||||||
|
->setCurlOption(CURLOPT_SSL_VERIFYHOST, false)
|
||||||
|
;
|
||||||
|
if($config->get('http.proxy.enabled', false)) {
|
||||||
|
$curlAdapter
|
||||||
|
->setCurlOption(CURLOPT_PROXYTYPE, $config->get('http.proxy.type'))
|
||||||
|
->setCurlOption(CURLOPT_PROXY, $config->get('http.proxy.url'))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
return $httpClient;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the templates configuration
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getTemplates()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'paths' => [
|
||||||
|
'app' => ['templates/app'],
|
||||||
|
'error' => ['templates/error'],
|
||||||
|
'layout' => ['templates/layout'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/App/Entity/JiraIssue.php
Normal file
111
src/App/Entity/JiraIssue.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
class JiraIssue implements \JsonSerializable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \DateTime
|
||||||
|
*/
|
||||||
|
private $createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \DateTime
|
||||||
|
*/
|
||||||
|
private $resolvedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $id
|
||||||
|
* @return JiraIssue
|
||||||
|
*/
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getKey()
|
||||||
|
{
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return JiraIssue
|
||||||
|
*/
|
||||||
|
public function setKey($key)
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \DateTime
|
||||||
|
*/
|
||||||
|
public function getCreatedAt()
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \DateTime $createdAt
|
||||||
|
* @return JiraIssue
|
||||||
|
*/
|
||||||
|
public function setCreatedAt($createdAt)
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \DateTime
|
||||||
|
*/
|
||||||
|
public function getResolvedAt()
|
||||||
|
{
|
||||||
|
return $this->resolvedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \DateTime $resolvedAt
|
||||||
|
* @return JiraIssue
|
||||||
|
*/
|
||||||
|
public function setResolvedAt($resolvedAt)
|
||||||
|
{
|
||||||
|
$this->resolvedAt = $resolvedAt;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
function jsonSerialize()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->getId(),
|
||||||
|
'key' => $this->getKey(),
|
||||||
|
'createdAt' => $this->getCreatedAt(),
|
||||||
|
'resolvedAt' => $this->getResolvedAt(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/App/Entity/WeekData.php
Normal file
103
src/App/Entity/WeekData.php
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
class WeekData implements \JsonSerializable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $in = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $out = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $open = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getIn(): int
|
||||||
|
{
|
||||||
|
return $this->in;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $in
|
||||||
|
* @return WeekData
|
||||||
|
*/
|
||||||
|
public function setIn(int $in): WeekData
|
||||||
|
{
|
||||||
|
$this->in = $in;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $step
|
||||||
|
*/
|
||||||
|
public function incrementIn(int $step = 1)
|
||||||
|
{
|
||||||
|
$this->in += $step;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getOut(): int
|
||||||
|
{
|
||||||
|
return $this->out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $out
|
||||||
|
* @return WeekData
|
||||||
|
*/
|
||||||
|
public function setOut(int $out): WeekData
|
||||||
|
{
|
||||||
|
$this->out = $out;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $step
|
||||||
|
*/
|
||||||
|
public function incrementOut(int $step = 1)
|
||||||
|
{
|
||||||
|
$this->out += $step;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getOpen(): int
|
||||||
|
{
|
||||||
|
return $this->open;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $open
|
||||||
|
* @return WeekData
|
||||||
|
*/
|
||||||
|
public function setOpen(int $open): WeekData
|
||||||
|
{
|
||||||
|
$this->open = $open;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
function jsonSerialize()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'in' => $this->getIn(),
|
||||||
|
'out' => $this->getOut(),
|
||||||
|
'open' => $this->getOpen(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/App/Service/JiraCollectorService.php
Normal file
141
src/App/Service/JiraCollectorService.php
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Entity\JiraIssue;
|
||||||
|
use App\Entity\WeekData;
|
||||||
|
use Zend\Config\Config;
|
||||||
|
use Zend\Http\Client;
|
||||||
|
use Zend\Json\Decoder;
|
||||||
|
use Zend\Json\Json;
|
||||||
|
|
||||||
|
class JiraCollectorService
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Config
|
||||||
|
*/
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Client
|
||||||
|
*/
|
||||||
|
private $httpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JiraClientService constructor.
|
||||||
|
* @param Client $client
|
||||||
|
* @param Config $config
|
||||||
|
*/
|
||||||
|
public function __construct(Client $client, Config $config)
|
||||||
|
{
|
||||||
|
$this->httpClient = $client;
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChartData()
|
||||||
|
{
|
||||||
|
$supportTickets = $this->getSupportJiraTickets();
|
||||||
|
$openTickets = array_values(array_filter($supportTickets, function(JiraIssue $ticket){
|
||||||
|
return $ticket->getResolvedAt() === null;
|
||||||
|
}));
|
||||||
|
$openTicketCount = count($openTickets);
|
||||||
|
$weekHash = $this->prepareWeekHash();
|
||||||
|
|
||||||
|
$weekHash[date("Y_W")]->setOpen($openTicketCount);
|
||||||
|
array_map(function (JiraIssue $jiraTicket) use ($weekHash) {
|
||||||
|
$createdAtIdx = $jiraTicket->getCreatedAt()->format("Y_W");
|
||||||
|
|
||||||
|
if(array_key_exists($createdAtIdx, $weekHash)) {
|
||||||
|
$weekHash[$createdAtIdx]->incrementIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($jiraTicket->getResolvedAt()) {
|
||||||
|
$resolvedAtIdx = $jiraTicket->getResolvedAt()->format("Y_W");
|
||||||
|
if(array_key_exists($resolvedAtIdx, $weekHash)) {
|
||||||
|
$weekHash[$resolvedAtIdx]->incrementOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, $supportTickets);
|
||||||
|
|
||||||
|
$this->calculateOpenTickets($weekHash);
|
||||||
|
return array_reverse($weekHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getSupportJiraTickets(): ?array
|
||||||
|
{
|
||||||
|
$user = $this->config->get('jira.user');
|
||||||
|
$password = $this->config->get('jira.password');
|
||||||
|
$jiraResultUrl = $this->config->get('jira.query');
|
||||||
|
|
||||||
|
$response = $this->httpClient
|
||||||
|
->setUri($jiraResultUrl)
|
||||||
|
->setAuth($user, $password)
|
||||||
|
->send();
|
||||||
|
|
||||||
|
if (!$response->isSuccess()) {
|
||||||
|
throw new \UnexpectedValueException("Bad JIRA result", $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsedJsonData = Decoder::decode($response->getBody(), Json::TYPE_ARRAY);
|
||||||
|
|
||||||
|
$kanbanBoard = $this->hydrateKanbanBoard($parsedJsonData);
|
||||||
|
|
||||||
|
return $kanbanBoard;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $parsedJsonData
|
||||||
|
* @return JiraIssue[]
|
||||||
|
*/
|
||||||
|
private function hydrateKanbanBoard($parsedJsonData): array
|
||||||
|
{
|
||||||
|
$jiraIssues = [];
|
||||||
|
|
||||||
|
foreach ($parsedJsonData['issues'] as $jsonIssue) {
|
||||||
|
$jiraIssue = new JiraIssue();
|
||||||
|
$jiraIssue->setId(intval($jsonIssue['id']))
|
||||||
|
->setKey($jsonIssue['key'])
|
||||||
|
->setCreatedAt(new \DateTime($jsonIssue['fields']['created']));
|
||||||
|
|
||||||
|
if ($jsonIssue['fields']['resolutiondate']) {
|
||||||
|
$jiraIssue->setResolvedAt(new \DateTime($jsonIssue['fields']['resolutiondate']));
|
||||||
|
}
|
||||||
|
$jiraIssues[] = $jiraIssue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $jiraIssues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return WeekData[]
|
||||||
|
*/
|
||||||
|
private function prepareWeekHash(): array
|
||||||
|
{
|
||||||
|
$hash = [];
|
||||||
|
for ($i = 0; $i > -30; $i-- ) {
|
||||||
|
$hashIndex = date("Y_W", strtotime("${i} week"));
|
||||||
|
$hash[$hashIndex] = new WeekData();
|
||||||
|
}
|
||||||
|
return $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param WeekData[] $hash
|
||||||
|
* @return WeekData[]
|
||||||
|
*/
|
||||||
|
private function calculateOpenTickets(array &$hash)
|
||||||
|
{
|
||||||
|
$thisWeek = reset($hash);
|
||||||
|
$openCount = $thisWeek->getOpen() - $thisWeek->getIn() + $thisWeek->getOut();
|
||||||
|
while($weekData = next($hash)) {
|
||||||
|
// going in reverse direction, so logic is also reversed here
|
||||||
|
$weekData->setOpen($openCount);
|
||||||
|
$openCount = $openCount - $weekData->getIn() + $weekData->getOut();
|
||||||
|
}
|
||||||
|
return $hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/App/Service/JiraCollectorServiceFactory.php
Normal file
18
src/App/Service/JiraCollectorServiceFactory.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Zend\Config\Config;
|
||||||
|
use Zend\Http\Client;
|
||||||
|
|
||||||
|
class JiraCollectorServiceFactory
|
||||||
|
{
|
||||||
|
public function __invoke(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$configArray = $container->get('config');
|
||||||
|
$httpClient = $container->get(Client::class);
|
||||||
|
$config = new Config($configArray['app.config']);
|
||||||
|
return new JiraCollectorService($httpClient, $config);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
templates/app/home-page.phtml
Normal file
14
templates/app/home-page.phtml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php $this->layout('layout::default', ['title' => 'Support Cases (based on JIRA data)']) ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id='chart-container' style='width: 1258px; height: 755px; margin: 0 auto'></div>
|
||||||
|
<div style='display:none'>
|
||||||
|
<?php foreach ($chartRows as $rowDate => $chartRow): ?>
|
||||||
|
Week: <span class='week'><?php
|
||||||
|
list($year,$week) = explode('_', $rowDate);
|
||||||
|
echo $week; ?></span> Created: <span class='inflow'><?=$chartRow->getIn()?></span>, Resolved: <span class='outflow'>-<?=$chartRow->getOut()?></span>, Sum: <span class='sum'><?=$chartRow->getOpen()?></span><br/>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
9
templates/error/404.phtml
Normal file
9
templates/error/404.phtml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php $this->layout('layout::default', ['title' => '404 Not Found']) ?>
|
||||||
|
|
||||||
|
<h1>Oops!</h1>
|
||||||
|
<h2>This is awkward.</h2>
|
||||||
|
<p>We encountered a 404 Not Found error.</p>
|
||||||
|
<p>
|
||||||
|
You are looking for something that doesn't exist or may have moved. Check out one of the links on this page
|
||||||
|
or head back to <a href="/">Home</a>.
|
||||||
|
</p>
|
||||||
11
templates/error/error.phtml
Normal file
11
templates/error/error.phtml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php $this->layout('layout::default', ['title' => sprintf('%d %s', $status, $reason)]) ?>
|
||||||
|
|
||||||
|
<h1>Oops!</h1>
|
||||||
|
<h2>This is awkward.</h2>
|
||||||
|
<p>We encountered a <?=$this->e($status)?> <?=$this->e($reason)?> error.</p>
|
||||||
|
<?php if ($status == 404) : ?>
|
||||||
|
<p>
|
||||||
|
You are looking for something that doesn't exist or may have moved. Check out one of the links on this page
|
||||||
|
or head back to <a href="/">Home</a>.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
138
templates/layout/default.phtml
Normal file
138
templates/layout/default.phtml
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<title><?=$this->e($title)?> - zend-expressive</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" />
|
||||||
|
<style>
|
||||||
|
body { padding-top: 60px; }
|
||||||
|
.app { display: flex; min-height: 100vh; flex-direction: column; }
|
||||||
|
.app-content { flex: 1; }
|
||||||
|
.app-footer { padding-bottom: 1em; }
|
||||||
|
.zf-green, h2 a { color: #68b604; }
|
||||||
|
</style>
|
||||||
|
<?=$this->section('stylesheets')?>
|
||||||
|
</head>
|
||||||
|
<body class="app">
|
||||||
|
<header class="app-header">
|
||||||
|
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="/">
|
||||||
|
<img src="/zf-logo.png" alt="Zend Expressive" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="collapse navbar-collapse">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li>
|
||||||
|
<a href="https://docs.zendframework.com/zend-expressive/" target="_blank">
|
||||||
|
<i class="fa fa-book"></i> Docs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/zendframework/zend-expressive" target="_blank">
|
||||||
|
<i class="fa fa-wrench"></i> Contribute
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="app-content">
|
||||||
|
<main class="container">
|
||||||
|
<?=$this->section('content')?>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="app-footer">
|
||||||
|
<div class="container">
|
||||||
|
<hr />
|
||||||
|
<?php if ($this->section('footer')): ?>
|
||||||
|
<?=$this->section('footer')?>
|
||||||
|
<?php else: ?>
|
||||||
|
<p>
|
||||||
|
© 2005 - <?=date('Y')?> by Zend Technologies Ltd. All rights reserved.
|
||||||
|
</p>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||||
|
<script src="https://code.highcharts.com/highcharts.js"></script>
|
||||||
|
<script src="https://code.highcharts.com/modules/exporting.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('#chart-container').highcharts({
|
||||||
|
title: {
|
||||||
|
text: 'Support Cases'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
categories: get('.week')
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
column: {
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
style: {
|
||||||
|
fontSize: '16px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
spline: {
|
||||||
|
dataLabels: {
|
||||||
|
enabled: true,
|
||||||
|
style: {
|
||||||
|
fontSize: '16px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
type: 'column',
|
||||||
|
name: 'Inflow',
|
||||||
|
data: get('.inflow'),
|
||||||
|
color: '#993300'
|
||||||
|
}, {
|
||||||
|
type: 'column',
|
||||||
|
name: 'Outflow',
|
||||||
|
data: get('.outflow'),
|
||||||
|
color: '#0070c0'
|
||||||
|
}, {
|
||||||
|
type: 'spline',
|
||||||
|
name: 'Open',
|
||||||
|
data: get('.sum'),
|
||||||
|
color: '#7030a0'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function get(type) {
|
||||||
|
var data_arr = [];
|
||||||
|
$(type).each(function (i) {
|
||||||
|
if(type == 'week') {
|
||||||
|
data_arr.push($(this).text());
|
||||||
|
} else {
|
||||||
|
data_arr.push(parseInt($(this).text()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data_arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<?=$this->section('javascript')?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
52
test/AppTest/Action/HomePageActionTest.php
Normal file
52
test/AppTest/Action/HomePageActionTest.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace AppTest\Action;
|
||||||
|
|
||||||
|
use App\Action\HomePageAction;
|
||||||
|
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Zend\Diactoros\Response\HtmlResponse;
|
||||||
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
use Zend\Expressive\Router\RouterInterface;
|
||||||
|
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||||
|
|
||||||
|
class HomePageActionTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @var RouterInterface */
|
||||||
|
protected $router;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
$this->router = $this->prophesize(RouterInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReturnsJsonResponseWhenNoTemplateRendererProvided()
|
||||||
|
{
|
||||||
|
$homePage = new HomePageAction($this->router->reveal(), null);
|
||||||
|
$response = $homePage->process(
|
||||||
|
$this->prophesize(ServerRequestInterface::class)->reveal(),
|
||||||
|
$this->prophesize(DelegateInterface::class)->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReturnsHtmlResponseWhenTemplateRendererProvided()
|
||||||
|
{
|
||||||
|
$renderer = $this->prophesize(TemplateRendererInterface::class);
|
||||||
|
$renderer
|
||||||
|
->render('app::home-page', Argument::type('array'))
|
||||||
|
->willReturn('');
|
||||||
|
|
||||||
|
$homePage = new HomePageAction($this->router->reveal(), $renderer->reveal());
|
||||||
|
|
||||||
|
$response = $homePage->process(
|
||||||
|
$this->prophesize(ServerRequestInterface::class)->reveal(),
|
||||||
|
$this->prophesize(DelegateInterface::class)->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(HtmlResponse::class, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
test/AppTest/Action/HomePageFactoryTest.php
Normal file
51
test/AppTest/Action/HomePageFactoryTest.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace AppTest\Action;
|
||||||
|
|
||||||
|
use App\Action\HomePageAction;
|
||||||
|
use App\Action\HomePageFactory;
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Zend\Expressive\Router\RouterInterface;
|
||||||
|
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||||
|
|
||||||
|
class HomePageFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @var ContainerInterface */
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
$this->container = $this->prophesize(ContainerInterface::class);
|
||||||
|
$router = $this->prophesize(RouterInterface::class);
|
||||||
|
|
||||||
|
$this->container->get(RouterInterface::class)->willReturn($router);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFactoryWithoutTemplate()
|
||||||
|
{
|
||||||
|
$factory = new HomePageFactory();
|
||||||
|
$this->container->has(TemplateRendererInterface::class)->willReturn(false);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(HomePageFactory::class, $factory);
|
||||||
|
|
||||||
|
$homePage = $factory($this->container->reveal());
|
||||||
|
|
||||||
|
$this->assertInstanceOf(HomePageAction::class, $homePage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFactoryWithTemplate()
|
||||||
|
{
|
||||||
|
$factory = new HomePageFactory();
|
||||||
|
$this->container->has(TemplateRendererInterface::class)->willReturn(true);
|
||||||
|
$this->container
|
||||||
|
->get(TemplateRendererInterface::class)
|
||||||
|
->willReturn($this->prophesize(TemplateRendererInterface::class));
|
||||||
|
|
||||||
|
$this->assertInstanceOf(HomePageFactory::class, $factory);
|
||||||
|
|
||||||
|
$homePage = $factory($this->container->reveal());
|
||||||
|
|
||||||
|
$this->assertInstanceOf(HomePageAction::class, $homePage);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
test/AppTest/Action/PingActionTest.php
Normal file
26
test/AppTest/Action/PingActionTest.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace AppTest\Action;
|
||||||
|
|
||||||
|
use App\Action\PingAction;
|
||||||
|
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Zend\Diactoros\Response\JsonResponse;
|
||||||
|
|
||||||
|
class PingActionTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testResponse()
|
||||||
|
{
|
||||||
|
$pingAction = new PingAction();
|
||||||
|
$response = $pingAction->process(
|
||||||
|
$this->prophesize(ServerRequestInterface::class)->reveal(),
|
||||||
|
$this->prophesize(DelegateInterface::class)->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$json = json_decode((string) $response->getBody());
|
||||||
|
|
||||||
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||||
|
$this->assertTrue(isset($json->ack));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user