Merge pull request #1132 from cachethq/api-template-incidents

Incident API can now use incident templates with Twig templating
This commit is contained in:
James Brooks
2015-11-15 11:57:09 +00:00
31 changed files with 363 additions and 22 deletions

View File

@@ -69,6 +69,20 @@ final class ReportIncidentCommand
*/
public $incident_date;
/**
* A given incident template.
*
* @var string|null
*/
public $template;
/**
* Variables for the incident template.
*
* @var string[]|null
*/
public $template_vars;
/**
* The validation rules.
*
@@ -83,6 +97,7 @@ final class ReportIncidentCommand
'component_status' => 'int|min:1|max:4|required_with:component_id',
'notify' => 'bool',
'incident_date' => 'string',
'template' => 'string',
];
/**
@@ -96,10 +111,12 @@ final class ReportIncidentCommand
* @param int $component_status
* @param bool $notify
* @param string|null $incident_date
* @param string|null $template
* @param array|null $template_vars
*
* @return void
*/
public function __construct($name, $status, $message, $visible, $component_id, $component_status, $notify, $incident_date)
public function __construct($name, $status, $message, $visible, $component_id, $component_status, $notify, $incident_date, $template, $template_vars)
{
$this->name = $name;
$this->status = $status;
@@ -109,5 +126,7 @@ final class ReportIncidentCommand
$this->component_status = $component_status;
$this->notify = $notify;
$this->incident_date = $incident_date;
$this->template = $template;
$this->template_vars = $template_vars;
}
}

View File

@@ -78,6 +78,20 @@ final class UpdateIncidentCommand
*/
public $incident_date;
/**
* A given incident template.
*
* @var string|null
*/
public $template;
/**
* Variables for the incident template.
*
* @var string[]|null
*/
public $template_vars;
/**
* The validation rules.
*
@@ -91,6 +105,7 @@ final class UpdateIncidentCommand
'component_id' => 'int',
'component_status' => 'int|min:1|max:4|required_with:component_id',
'notify' => 'bool',
'template' => 'string',
];
/**
@@ -105,10 +120,12 @@ final class UpdateIncidentCommand
* @param int $component_status
* @param bool $notify
* @param string|null $incident_date
* @param string|null $template
* @param array|null $template_vars
*
* @return void
*/
public function __construct(Incident $incident, $name, $status, $message, $visible, $component_id, $component_status, $notify, $incident_date = null)
public function __construct(Incident $incident, $name, $status, $message, $visible, $component_id, $component_status, $notify, $incident_date, $template, $template_vars)
{
$this->incident = $incident;
$this->name = $name;
@@ -119,5 +136,7 @@ final class UpdateIncidentCommand
$this->component_status = $component_status;
$this->notify = $notify;
$this->incident_date = $incident_date;
$this->template = $template;
$this->template_vars = $template_vars;
}
}

View File

@@ -16,6 +16,9 @@ use CachetHQ\Cachet\Dates\DateFactory;
use CachetHQ\Cachet\Events\Incident\IncidentWasReportedEvent;
use CachetHQ\Cachet\Models\Component;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\IncidentTemplate;
use Twig_Loader_String;
use TwigBridge\Facade\Twig;
class ReportIncidentCommandHandler
{
@@ -47,6 +50,10 @@ class ReportIncidentCommandHandler
*/
public function handle(ReportIncidentCommand $command)
{
if ($command->template) {
$command->message = $this->parseIncidentTemplate($command->template, $command->template_vars);
}
$data = [
'name' => $command->name,
'status' => $command->status,
@@ -81,4 +88,20 @@ class ReportIncidentCommandHandler
return $incident;
}
/**
* Compiles an incident template into an incident message.
*
* @param string $templateSlug
* @param array $vars
*
* @return string
*/
protected function parseIncidentTemplate($templateSlug, $vars)
{
Twig::setLoader(new Twig_Loader_String());
$template = IncidentTemplate::forSlug($templateSlug)->first();
return Twig::render($template->template, $vars);
}
}

View File

@@ -16,6 +16,9 @@ use CachetHQ\Cachet\Dates\DateFactory;
use CachetHQ\Cachet\Events\Incident\IncidentWasUpdatedEvent;
use CachetHQ\Cachet\Models\Component;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\IncidentTemplate;
use Twig_Loader_String;
use TwigBridge\Facade\Twig;
class UpdateIncidentCommandHandler
{
@@ -47,6 +50,10 @@ class UpdateIncidentCommandHandler
*/
public function handle(UpdateIncidentCommand $command)
{
if ($command->template) {
$command->message = $this->parseIncidentTemplate($command->template, $command->template_vars);
}
$incident = $command->incident;
$incident->update($this->filterIncidentData($command));
@@ -91,4 +98,20 @@ class UpdateIncidentCommandHandler
'notify' => $command->notify,
]);
}
/**
* Compiles an incident template into an incident message.
*
* @param string $templateSlug
* @param array $vars
*
* @return string
*/
protected function parseIncidentTemplate($templateSlug, $vars)
{
Twig::setLoader(new Twig_Loader_String());
$template = IncidentTemplate::forSlug($templateSlug)->first();
return Twig::render($template->template, $vars);
}
}

View File

@@ -73,7 +73,9 @@ class IncidentController extends AbstractApiController
Binput::get('component_id'),
Binput::get('component_status'),
Binput::get('notify', true),
Binput::get('created_at')
Binput::get('created_at'),
Binput::get('template'),
Binput::get('vars')
));
} catch (Exception $e) {
throw new BadRequestHttpException();
@@ -101,7 +103,9 @@ class IncidentController extends AbstractApiController
Binput::get('component_id'),
Binput::get('component_status'),
Binput::get('notify', true),
Binput::get('created_at')
Binput::get('created_at'),
Binput::get('template'),
Binput::get('vars')
));
} catch (Exception $e) {
throw new BadRequestHttpException();

View File

@@ -58,4 +58,17 @@ class IncidentTemplate extends Model
$template->slug = Str::slug($template->name);
});
}
/**
* Finds a template by the slug.
*
* @param \Illuminate\Database\Query\Builder $query
* @param string $slug
*
* @return \Illuminate\Database\Query\Builder
*/
public function scopeForSlug($query, $slug)
{
return $query->where('slug', $slug);
}
}

View File

@@ -35,7 +35,8 @@
"jenssegers/date": "^3.0",
"mccool/laravel-auto-presenter": "^4.2",
"pragmarx/google2fa": "^0.7",
"roumen/feed": "^2.9"
"roumen/feed": "^2.9",
"rcrowe/twigbridge": "^0.9.0"
},
"require-dev": {
"fzaninotto/faker": "^1.5",

129
composer.lock generated
View File

@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "f139419cc0b4579b34749d181cf9c15b",
"content-hash": "cb98697488db5c325f3fb86c98dddef8",
"hash": "ff4fd4c7d8c30ce6da667c81caa67372",
"content-hash": "dd7e113fcc687c33e7d47bdc27b87f75",
"packages": [
{
"name": "alt-three/emoji",
@@ -2593,6 +2593,70 @@
],
"time": "2015-11-12 16:18:56"
},
{
"name": "rcrowe/twigbridge",
"version": "v0.9.0",
"source": {
"type": "git",
"url": "https://github.com/rcrowe/TwigBridge.git",
"reference": "f875fa9457ebadf8f24b683b226848b660407f8f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/f875fa9457ebadf8f24b683b226848b660407f8f",
"reference": "f875fa9457ebadf8f24b683b226848b660407f8f",
"shasum": ""
},
"require": {
"illuminate/support": "5.0.*|5.1.*",
"illuminate/view": "5.0.*|5.1.*",
"php": ">=5.4.0",
"twig/twig": "~1.15|~2.0"
},
"require-dev": {
"laravel/framework": "5.0.*",
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "~0.6",
"squizlabs/php_codesniffer": "~1.5"
},
"suggest": {
"laravelcollective/html": "For bringing back html/form in Laravel 5.x",
"twig/extensions": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.8-dev"
}
},
"autoload": {
"psr-4": {
"TwigBridge\\": "src",
"TwigBridge\\Tests\\": "tests"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
},
{
"name": "Rob Crowe",
"email": "hello@vivalacrowe.com"
}
],
"description": "Adds the power of Twig to Laravel",
"keywords": [
"laravel",
"twig"
],
"time": "2015-11-02 17:37:16"
},
{
"name": "roumen/feed",
"version": "v2.9.7",
@@ -3426,6 +3490,67 @@
],
"time": "2015-10-25 17:17:38"
},
{
"name": "twig/twig",
"version": "v1.23.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/d9b6333ae8dd2c8e3fd256e127548def0bc614c6",
"reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6",
"shasum": ""
},
"require": {
"php": ">=5.2.7"
},
"require-dev": {
"symfony/debug": "~2.7",
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.23-dev"
}
},
"autoload": {
"psr-0": {
"Twig_": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
},
{
"name": "Twig Team",
"homepage": "http://twig.sensiolabs.org/contributors",
"role": "Contributors"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "http://twig.sensiolabs.org",
"keywords": [
"templating"
],
"time": "2015-11-05 12:49:06"
},
{
"name": "vlucas/phpdotenv",
"version": "v1.1.1",

View File

@@ -162,6 +162,7 @@ return [
'McCool\LaravelAutoPresenter\AutoPresenterServiceProvider',
'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider',
'Roumen\Feed\FeedServiceProvider',
'TwigBridge\ServiceProvider',
/*
* Application Service Providers...

View File

@@ -74,3 +74,14 @@ $factory->define('CachetHQ\Cachet\Models\Subscriber', function ($faker) {
'verified_at' => Carbon::now(),
];
});
$factory->define('CachetHQ\Cachet\Models\IncidentTemplate', function ($faker) {
return [
'name' => 'Test Template',
'slug' => 'test-template',
'template' => <<<ETEMPLATE
Name: {{ name }},
Message: {{ message }}
ETEMPLATE
];
});

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Navn',
'template' => 'Skabelon',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Name',
'template' => 'Vorlage',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Name',
'template' => 'Template',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -44,6 +44,7 @@ return [
'templates' => [
'name' => 'Nombre',
'template' => 'Plantilla',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Nom',
'template' => 'Modéle',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Nama',
'template' => 'Template',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Nome',
'template' => 'Modello',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => '이름',
'template' => '템플릿',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Naam',
'template' => 'Sjabloon',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -45,6 +45,7 @@ return [
'templates' => [
'name' => 'Nazwa',
'template' => 'Szablon',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Nome',
'template' => 'Template',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => 'Название',
'template' => 'Шаблон',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => '事件模板名',
'template' => '模板',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -51,6 +51,7 @@ return [
'templates' => [
'name' => '事件模板名',
'template' => '模板',
'twig' => 'Incident Templates can make use of the <a href="http://twig.sensiolabs.org/" target="_blank">Twig</a> templating language.',
],
],

View File

@@ -22,13 +22,7 @@
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,700" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="{{ elixir('dist/css/all.css') }}">
@yield('css')
@include('partials.crowdin')
<script type="text/javascript">
var Global = {};
Global.locale = '{{ Setting::get('app_locale') }}';
</script>
<script src="{{ elixir('dist/js/all.js') }}"></script>
</head>

View File

@@ -1,5 +1,25 @@
@extends('layout.dashboard')
@section('css')
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.8.0/codemirror.css">
@stop
@section('js')
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.8.0/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.8.0/mode/twig/twig.min.js"></script>
<script>
(function() {
console.log(document.getElementById('cm-editor'));
var editor = CodeMirror.fromTextArea(document.getElementById('cm-editor'), {
lineNumbers: true,
mode: 'twig',
lineWrapping: true
});
}());
</script>
@stop
@section('content')
<div class="header">
<div class="sidebar-toggler visible-xs">
@@ -23,9 +43,8 @@
</div>
<div class="form-group">
<label>{{ trans('forms.incidents.templates.template') }}</label>
<div class='markdown-control'>
<textarea name="template[template]" class="form-control" rows="5" required></textarea>
</div>
<textarea name="template[template]" id="cm-editor" class="form-control" rows="8"></textarea>
<span class="help-block">{!! trans('forms.incidents.templates.twig') !!}</span>
</div>
</fieldset>

View File

@@ -1,5 +1,25 @@
@extends('layout.dashboard')
@section('css')
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.8.0/codemirror.css">
@stop
@section('js')
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.8.0/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.8.0/mode/twig/twig.min.js"></script>
<script>
(function() {
console.log(document.getElementById('cm-editor'));
var editor = CodeMirror.fromTextArea(document.getElementById('cm-editor'), {
lineNumbers: true,
mode: 'twig',
lineWrapping: true
});
}());
</script>
@stop
@section('content')
<div class="header">
<div class="sidebar-toggler visible-xs">
@@ -32,9 +52,8 @@
</div>
<div class="form-group">
<label>{{ trans('forms.incidents.templates.template') }}</label>
<div class='markdown-control'>
<textarea name="template[template]" class="form-control" rows="5" required>{{ $template->template }}</textarea>
</div>
<textarea name="template[template]" id="cm-editor" class="form-control" rows="8">{{ $template->template }}</textarea>
<span class="help-block">{!! trans('forms.incidents.templates.twig') !!}</span>
</div>
</fieldset>

View File

@@ -9,5 +9,11 @@
@yield('content')
</div>
</div>
<script type="text/javascript">
var Global = {};
Global.locale = '{{ Setting::get('app_locale') }}';
</script>
@yield('js')
<script src="{{ elixir('dist/js/all.js') }}"></script>
</body>
</html>

View File

@@ -63,6 +63,27 @@ class IncidentTest extends AbstractTestCase
$this->assertResponseOk();
}
public function testCreateIncidentWithTemplate()
{
$template = factory('CachetHQ\Cachet\Models\IncidentTemplate')->create();
$this->beUser();
$this->post('/api/v1/incidents', [
'name' => 'Foo',
'status' => 1,
'visible' => 1,
'template' => $template->slug,
'vars' => [
'name' => 'Foo',
'message' => 'Hello there this is a foo!',
],
]);
$this->seeJson([
'name' => 'Foo',
'message' => "Name: Foo,\nMessage: Hello there this is a foo!",
]);
}
public function testGetNewIncident()
{
$incident = factory('CachetHQ\Cachet\Models\Incident')->create();
@@ -84,6 +105,27 @@ class IncidentTest extends AbstractTestCase
$this->assertResponseOk();
}
public function testPutIncidentWithTemplate()
{
$this->beUser();
$template = factory('CachetHQ\Cachet\Models\IncidentTemplate')->create();
$component = factory('CachetHQ\Cachet\Models\Incident')->create();
$this->put('/api/v1/incidents/1', [
'name' => 'Foo',
'template' => $template->slug,
'vars' => [
'name' => 'Foo',
'message' => 'Hello there this is a foo!',
],
]);
$this->seeJson([
'name' => 'Foo',
'message' => "Name: Foo,\nMessage: Hello there this is a foo!",
]);
$this->assertResponseOk();
}
public function testDeleteIncident()
{
$this->beUser();

View File

@@ -33,6 +33,8 @@ class ReportIncidentCommandTest extends AbstractCommandTestCase
'component_status' => 1,
'notify' => false,
'incident_date' => null,
'template' => null,
'template_vars' => null,
];
$object = new ReportIncidentCommand(
$params['name'],
@@ -42,7 +44,9 @@ class ReportIncidentCommandTest extends AbstractCommandTestCase
$params['component_id'],
$params['component_status'],
$params['notify'],
$params['incident_date']
$params['incident_date'],
$params['template'],
$params['template_vars']
);
return compact('params', 'object');

View File

@@ -35,6 +35,8 @@ class UpdateIncidentCommandTest extends AbstractCommandTestCase
'component_status' => 1,
'notify' => false,
'incident_date' => null,
'template' => null,
'template_vars' => null,
];
$object = new UpdateIncidentCommand(
$params['incident'],
@@ -45,7 +47,9 @@ class UpdateIncidentCommandTest extends AbstractCommandTestCase
$params['component_id'],
$params['component_status'],
$params['notify'],
$params['incident_date']
$params['incident_date'],
$params['template'],
$params['template_vars']
);
return compact('params', 'object');