Backport Incident Updates from v3.0.0

This commit is contained in:
James Brooks 2016-10-06 17:21:18 +01:00
parent c778ce91c3
commit c94919f1b9
43 changed files with 1834 additions and 85 deletions

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Commands\IncidentUpdate;
use CachetHQ\Cachet\Models\IncidentUpdate;
/**
* This is the remove incident update command.
*
* @author James Brooks <james@alt-three.com>
*/
final class RemoveIncidentUpdateCommand
{
/**
* The incident update to remove.
*
* @var \CachetHQ\Cachet\Models\IncidentUpdate
*/
public $incidentUpdate;
/**
* Create a new remove incident update command instance.
*
* @param \CachetHQ\Cachet\Models\IncidentUpdate $incidentUpdate
*
* @return void
*/
public function __construct(IncidentUpdate $incidentUpdate)
{
$this->incidentUpdate = $incidentUpdate;
}
}

View File

@ -0,0 +1,81 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Commands\IncidentUpdate;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\User;
/**
* This is the report incident update command.
*
* @author James Brooks <james@alt-three.com>
*/
final class ReportIncidentUpdateCommand
{
/**
* The incident.
*
* @var \CachetHQ\Cachet\Models\Incident
*/
public $incident;
/**
* The incident status.
*
* @var int
*/
public $status;
/**
* The incident message.
*
* @var string
*/
public $message;
/**
* The user.
*
* @var \CachetHQ\Cachet\Models\User
*/
public $user;
/**
* The validation rules.
*
* @var string[]
*/
public $rules = [
'incident' => 'required',
'status' => 'required|int|min:1|max:4',
'message' => 'required|string',
'user' => 'required',
];
/**
* Create a new report incident update command instance.
*
* @param \CachetHQ\Cachet\Models\Incident $incident
* @param string $status
* @param string $message
* @param \CachetHQ\Cachet\Models\User $user
*
* @return void
*/
public function __construct(Incident $incident, $status, $message, User $user)
{
$this->incident = $incident;
$this->status = $status;
$this->message = $message;
$this->user = $user;
}
}

View File

@ -0,0 +1,81 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Commands\IncidentUpdate;
use CachetHQ\Cachet\Models\IncidentUpdate;
use CachetHQ\Cachet\Models\User;
/**
* This is the update incident update command.
*
* @author James Brooks <james@alt-three.com>
*/
final class UpdateIncidentUpdateCommand
{
/**
* The incident update.
*
* @var \CachetHQ\Cachet\Models\IncidentUpdate
*/
public $update;
/**
* The incident status.
*
* @var int
*/
public $status;
/**
* The incident message.
*
* @var string
*/
public $message;
/**
* The user.
*
* @var \CachetHQ\Cachet\Models\User
*/
public $user;
/**
* The validation rules.
*
* @var string[]
*/
public $rules = [
'update' => 'required',
'status' => 'int|min:1|max:4',
'message' => 'string',
'user' => 'required',
];
/**
* Create a new update incident update command instance.
*
* @param \CachetHQ\Cachet\Models\IncidentUpdate $update
* @param string $status
* @param string $message
* @param \CachetHQ\Cachet\Models\User $user
*
* @return void
*/
public function __construct(IncidentUpdate $update, $status, $message, User $user)
{
$this->update = $update;
$this->status = $status;
$this->message = $message;
$this->user = $user;
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Events\IncidentUpdate;
use CachetHQ\Cachet\Bus\Events\EventInterface;
/**
* This is the incident update event interface.
*
* @author James Brooks <james@alt-three.com>
*/
interface IncidentUpdateEventInterface extends EventInterface
{
//
}

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Events\IncidentUpdate;
use CachetHQ\Cachet\Models\IncidentUpdate;
/**
* This is the incident update was removed event.
*
* @author James Brooks <james@alt-three.com>
*/
final class IncidentUpdateWasRemovedEvent implements IncidentUpdateEventInterface
{
/**
* The incident update that has been removed.
*
* @var \CachetHQ\Cachet\Models\IncidentUpdate
*/
public $update;
/**
* Create a new incident update was removed event instance.
*
* @param \CachetHQ\Cachet\Models\IncidentUpdate $update
*
* @return void
*/
public function __construct(IncidentUpdate $update)
{
$this->update = $update;
}
}

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Events\IncidentUpdate;
use CachetHQ\Cachet\Models\IncidentUpdate;
/**
* This is the incident update was reported event.
*
* @author James Brooks <james@alt-three.com>
*/
final class IncidentUpdateWasReportedEvent implements IncidentUpdateEventInterface
{
/**
* The incident update that has been reported.
*
* @var \CachetHQ\Cachet\Models\IncidentUpdate
*/
public $update;
/**
* Create a new incident update was reported event instance.
*
* @param \CachetHQ\Cachet\Models\IncidentUpdate $update
*
* @return void
*/
public function __construct(IncidentUpdate $update)
{
$this->update = $update;
}
}

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Events\IncidentUpdate;
use CachetHQ\Cachet\Models\IncidentUpdate;
/**
* This is the incident update was updated event.
*
* @author James Brooks <james@alt-three.com>
*/
final class IncidentUpdateWasUpdatedEvent implements IncidentUpdateEventInterface
{
/**
* The incident update that has been updated.
*
* @var \CachetHQ\Cachet\Models\IncidentUpdate
*/
public $update;
/**
* Create a new incident update was updated event instance.
*
* @param \CachetHQ\Cachet\Models\IncidentUpdate $update
*
* @return void
*/
public function __construct(IncidentUpdate $update)
{
$this->update = $update;
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Handlers\Commands\IncidentUpdate;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\RemoveIncidentUpdateCommand;
use CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasRemovedEvent;
/**
* This is the remove incident update command handler.
*
* @author James Brooks <james@alt-three.com>
*/
class RemoveIncidentUpdateCommandHandler
{
/**
* Handle the remove incident update command.
*
* @param \CachetHQ\Cachet\Bus\Commands\IncidentUpdate\RemoveIncidentUpdateCommand $command
*
* @return void
*/
public function handle(RemoveIncidentUpdateCommand $command)
{
$update = $command->incidentUpdate;
event(new IncidentUpdateWasRemovedEvent($update));
$update->delete();
}
}

View File

@ -0,0 +1,65 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Handlers\Commands\IncidentUpdate;
use CachetHQ\Cachet\Bus\Commands\Incident\UpdateIncidentCommand;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\ReportIncidentUpdateCommand;
use CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasReportedEvent;
use CachetHQ\Cachet\Models\IncidentUpdate;
/**
* This is the report incident update command handler.
*
* @author James Brooks <james@alt-three.com>
*/
class ReportIncidentUpdateCommandHandler
{
/**
* Handle the report incident command.
*
* @param \CachetHQ\Cachet\Bus\Commands\IncidentUpdate\ReportIncidentUpdateCommand $command
*
* @return \CachetHQ\Cachet\Models\IncidentUpdate
*/
public function handle(ReportIncidentUpdateCommand $command)
{
$data = [
'incident_id' => $command->incident->id,
'status' => $command->status,
'message' => $command->message,
'user_id' => $command->user->id,
];
// Create the incident update.
$update = IncidentUpdate::create($data);
// Update the original incident with the new status.
dispatch(new UpdateIncidentCommand(
$incident,
null,
$command->status,
null,
null,
null,
null,
null,
null,
null,
null,
null
));
event(new IncidentUpdateWasReportedEvent($update));
return $update;
}
}

View File

@ -0,0 +1,59 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Bus\Handlers\Commands\IncidentUpdate;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\UpdateIncidentUpdateCommand;
use CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasUpdatedEvent;
/**
* This is the update incident update command handler.
*
* @author James Brooks <james@alt-three.com>
*/
class UpdateIncidentUpdateCommandHandler
{
/**
* Handle the update incident update command.
*
* @param \CachetHQ\Cachet\Bus\Commands\IncidentUpdate\UpdateIncidentUpdateCommand $command
*
* @return \CachetHQ\Cachet\Models\IncidentUpdate
*/
public function handle(UpdateIncidentUpdateCommand $command)
{
$command->update->update($this->filter($command));
event(new IncidentUpdateWasUpdatedEvent($command->update));
return $command->update;
}
/**
* Filter the command data.
*
* @param \CachetHQ\Cachet\Bus\Commands\IncidentUpdate\UpdateIncidentUpdateCommand $command
*
* @return array
*/
protected function filter(UpdateIncidentUpdateCommand $command)
{
$params = [
'status' => $command->status,
'message' => $command->message,
'user_id' => $command->user->id,
];
return array_filter($params, function ($val) {
return $val !== null;
});
}
}

View File

@ -15,6 +15,7 @@ use CachetHQ\Cachet\Models\Component;
use CachetHQ\Cachet\Models\ComponentGroup;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\IncidentTemplate;
use CachetHQ\Cachet\Models\IncidentUpdate;
use CachetHQ\Cachet\Models\Metric;
use CachetHQ\Cachet\Models\MetricPoint;
use CachetHQ\Cachet\Models\Subscriber;
@ -201,74 +202,31 @@ EINCIDENT;
$defaultIncidents = [
[
'name' => 'Cachet supports Markdown!',
'message' => $incidentMessage,
'status' => 4,
'name' => 'Our monkeys aren\'t performing',
'message' => 'We\'re investigating an issue with our monkeys not performing as they should be.',
'status' => Incident::INVESTIGATING,
'component_id' => 0,
'scheduled_at' => null,
'visible' => 1,
'stickied' => false,
],
[
'name' => 'Awesome',
'message' => ':+1: We totally nailed the fix.',
'status' => 4,
'name' => 'This is an unresolved incident',
'message' => 'Unresolved incidents are left without a **Fixed** update.',
'status' => Incident::INVESTIGATING,
'component_id' => 0,
'scheduled_at' => null,
'visible' => 1,
'stickied' => false,
],
[
'name' => 'Monitoring the fix',
'message' => ":ship: We've deployed a fix.",
'status' => 3,
'component_id' => 0,
'scheduled_at' => null,
'visible' => 1,
'stickied' => false,
],
[
'name' => 'Update',
'message' => "We've identified the problem. Our engineers are currently looking at it.",
'status' => 2,
'component_id' => 0,
'scheduled_at' => null,
'visible' => 1,
'stickied' => false,
],
[
'name' => 'Test Incident',
'message' => 'Something went wrong, with something or another.',
'status' => 1,
'component_id' => 0,
'scheduled_at' => null,
'visible' => 1,
'stickied' => false,
],
[
'name' => 'Investigating the API',
'message' => ':zap: We\'ve seen high response times from our API. It looks to be fixing itself as time goes on.',
'status' => 1,
'component_id' => 1,
'scheduled_at' => null,
'visible' => 1,
'stickied' => false,
],
[
'name' => 'Sticked incidents!',
'message' => 'Need to continually notify your customers of an incident? You can stick incidents to the top!',
'status' => 1,
'component_id' => 1,
'scheduled_at' => null,
'visible' => 1,
'stickied' => true,
],
];
Incident::truncate();
foreach ($defaultIncidents as $incident) {
Incident::create($incident);
foreach ($defaultIncidents as $defaultIncident) {
$incident = Incident::create($defaultIncident);
$this->seedIncidentUpdates($incident);
}
}
@ -282,6 +240,47 @@ EINCIDENT;
IncidentTemplate::truncate();
}
/**
* Seed the incident updates table for a given incident.
*
* @return void
*/
protected function seedIncidentUpdates($incident)
{
$defaultUpdates = [
1 => [
[
'status' => Incident::FIXED,
'message' => 'The monkeys are back and rested!',
'user_id' => 1,
], [
'status' => Incident::WATCHED,
'message' => 'Our monkeys need a break from performing. They\'ll be back after a good rest.',
'user_id' => 1,
], [
'status' => Incident::IDENTIFIED,
'message' => 'We have identified the issue with our lovely performing monkeys.',
'user_id' => 1,
],
],
2 => [
[
'status' => Incident::WATCHED,
'message' => 'We\'re actively watching this issue, so it remains unresolved.',
'user_id' => 1,
],
],
];
$updates = $defaultUpdates[$incident->id];
foreach ($updates as $updateId => $update) {
$update['incident_id'] = $incident->id;
IncidentUpdate::create($update);
}
}
/**
* Seed the metric points table.
*

View File

@ -48,6 +48,15 @@ class EventServiceProvider extends ServiceProvider
'CachetHQ\Cachet\Bus\Events\Component\ComponentWasUpdatedEvent' => [
//
],
'CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasRemovedEvent' => [
//
],
'CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasReportedEvent' => [
//
],
'CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasUpdatedEvent' => [
//
],
'CachetHQ\Cachet\Bus\Events\Incident\IncidentWasRemovedEvent' => [
//
],

View File

@ -0,0 +1,132 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Http\Controllers\Api;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\RemoveIncidentUpdateCommand;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\ReportIncidentUpdateCommand;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\UpdateIncidentUpdateCommand;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\IncidentUpdate;
use GrahamCampbell\Binput\Facades\Binput;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* This is the incident update controller.
*
* @author James Brooks <james@alt-three.com>
*/
class IncidentUpdateController extends AbstractApiController
{
/**
* Return all updates on the incident.
*
* @param \CachetHQ\Cachet\Models\Incident $incident
*
* @return \Illuminate\Http\JsonResponse
*/
public function getIncidentUpdates(Incident $incident)
{
$updates = IncidentUpdate::orderBy('created_at', 'desc');
if ($sortBy = Binput::get('sort')) {
$direction = Binput::has('order') && Binput::get('order') == 'desc';
$updates->sort($sortBy, $direction);
}
$updates = $updates->paginate(Binput::get('per_page', 20));
return $this->paginator($updates, Request::instance());
}
/**
* Return a single incident update.
*
* @param \CachetHQ\Cachet\Models\Incident $incident
* @param \CachetHQ\Cachet\Models\IncidentUpdate $update
*
* @return \Illuminate\Http\JsonResponse
*/
public function getIncidentUpdate(Incident $incident, IncidentUpdate $update)
{
return $this->item($update);
}
/**
* Create a new incident update.
*
* @param \CachetHQ\Cachet\Models\Incident $incident
*
* @return \Illuminate\Http\JsonResponse
*/
public function postIncidentUpdate(Incident $incident)
{
try {
$update = dispatch(new ReportIncidentUpdateCommand(
$incident,
Binput::get('status'),
Binput::get('message'),
Auth::user()
));
} catch (QueryException $e) {
throw new BadRequestHttpException();
}
return $this->item($update);
}
/**
* Update an incident update.
*
* @param \CachetHQ\Cachet\Models\Incident $incident
* @param \CachetHQ\Cachet\Models\IncidentUpdate $update
*
* @return \Illuminate\Http\JsonResponse
*/
public function putIncidentUpdate(Incident $incident, IncidentUpdate $update)
{
try {
$update = dispatch(new UpdateIncidentUpdateCommand(
$update,
Binput::get('status'),
Binput::get('message'),
Auth::user()
));
} catch (QueryException $e) {
throw new BadRequestHttpException();
}
return $this->item($update);
}
/**
* Create a new incident update.
*
* @param \CachetHQ\Cachet\Models\Incident $incident
* @param \CachetHQ\Cachet\Models\IncidentUpdate $update
*
* @return \Illuminate\Http\JsonResponse
*/
public function deleteIncidentUpdate(Incident $incident, IncidentUpdate $update)
{
try {
dispatch(new RemoveIncidentUpdateCommand($update));
} catch (QueryException $e) {
throw new BadRequestHttpException();
}
return $this->noContent();
}
}

View File

@ -15,15 +15,22 @@ use AltThree\Validator\ValidationException;
use CachetHQ\Cachet\Bus\Commands\Incident\RemoveIncidentCommand;
use CachetHQ\Cachet\Bus\Commands\Incident\ReportIncidentCommand;
use CachetHQ\Cachet\Bus\Commands\Incident\UpdateIncidentCommand;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\ReportIncidentUpdateCommand;
use CachetHQ\Cachet\Models\Component;
use CachetHQ\Cachet\Models\ComponentGroup;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\IncidentTemplate;
use GrahamCampbell\Binput\Facades\Binput;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View;
/**
* This is the incident controller.
*
* @author James Brooks <james@alt-three.com>
*/
class IncidentController extends Controller
{
/**
@ -33,13 +40,24 @@ class IncidentController extends Controller
*/
protected $subMenu = [];
/**
* The guard instance.
*
* @var \Illuminate\Contracts\Auth\Guard
*/
protected $auth;
/**
* Creates a new incident controller instance.
*
* @param \Illuminate\Contracts\Auth\Guard $auth
*
* @return void
*/
public function __construct()
public function __construct(Guard $auth)
{
$this->auth = $auth;
$this->subMenu = [
'incidents' => [
'title' => trans('dashboard.incidents.incidents'),
@ -281,4 +299,43 @@ class IncidentController extends Controller
return Redirect::route('dashboard.templates.edit', ['id' => $template->id])
->withUpdatedTemplate($template);
}
/**
* Shows the incident update form.
*
* @param \CachetHQ\Cachet\Models\Incident $incident
*
* @return \Illuminate\View\View
*/
public function showIncidentUpdateAction(Incident $incident)
{
return View::make('dashboard.incidents.update')->withIncident($incident);
}
/**
* Creates a new incident update.
*
* @param \CachetHQ\Cachet\Models\Incident $incident
*
* @return \Illuminate\Http\RedirectResponse
*/
public function createIncidentUpdateAction(Incident $incident)
{
try {
$incident = dispatch(new ReportIncidentUpdateCommand(
$incident,
Binput::get('status'),
Binput::get('message'),
$this->auth->user()
));
} catch (ValidationException $e) {
return Redirect::route('dashboard.incidents.update', ['id' => $incident->id])
->withInput(Binput::all())
->withTitle(sprintf('%s %s', trans('dashboard.notifications.whoops'), trans('dashboard.incidents.templates.edit.failure')))
->withErrors($e->getMessageBag());
}
return Redirect::route('dashboard.incidents.index')
->withSuccess(sprintf('%s %s', trans('dashboard.notifications.awesome'), trans('dashboard.incidents.delete.success')));
}
}

View File

@ -85,7 +85,7 @@ class StatusPageController extends AbstractApiController
$allIncidents = Incident::notScheduled()->where('visible', '>=', $incidentVisibility)->whereBetween('created_at', [
$startDate->copy()->subDays($daysToShow)->format('Y-m-d').' 00:00:00',
$startDate->format('Y-m-d').' 23:59:59',
])->orderBy('scheduled_at', 'desc')->orderBy('created_at', 'desc')->get()->groupBy(function (Incident $incident) {
])->orderBy('scheduled_at', 'desc')->orderBy('created_at', 'desc')->get()->load('updates')->groupBy(function (Incident $incident) {
return app(DateFactory::class)->make($incident->is_scheduled ? $incident->scheduled_at : $incident->created_at)->toDateString();
});

View File

@ -43,6 +43,9 @@ class ApiRoutes
$router->get('incidents', 'IncidentController@getIncidents');
$router->get('incidents/{incident}', 'IncidentController@getIncident');
$router->get('incidents/{incident}/updates', 'IncidentUpdateController@getIncidentUpdates');
$router->get('incidents/{incident}/updates/{update}', 'IncidentUpdateController@getIncidentUpdate');
$router->get('metrics', 'MetricController@getMetrics');
$router->get('metrics/{metric}', 'MetricController@getMetric');
$router->get('metrics/{metric}/points', 'MetricController@getMetricPoints');
@ -54,6 +57,7 @@ class ApiRoutes
$router->post('components', 'ComponentController@postComponents');
$router->post('components/groups', 'ComponentGroupController@postGroups');
$router->post('incidents', 'IncidentController@postIncidents');
$router->post('incidents/{incident}/updates', 'IncidentUpdateController@postIncidentUpdate');
$router->post('metrics', 'MetricController@postMetrics');
$router->post('metrics/{metric}/points', 'MetricPointController@postMetricPoints');
$router->post('subscribers', 'SubscriberController@postSubscribers');
@ -61,12 +65,14 @@ class ApiRoutes
$router->put('components/groups/{component_group}', 'ComponentGroupController@putGroup');
$router->put('components/{component}', 'ComponentController@putComponent');
$router->put('incidents/{incident}', 'IncidentController@putIncident');
$router->put('incidents/{incident}/updates/{update}', 'IncidentUpdateController@putIncidentUpdate');
$router->put('metrics/{metric}', 'MetricController@putMetric');
$router->put('metrics/{metric}/points/{metric_point}', 'MetricPointController@putMetricPoint');
$router->delete('components/groups/{component_group}', 'ComponentGroupController@deleteGroup');
$router->delete('components/{component}', 'ComponentController@deleteComponent');
$router->delete('incidents/{incident}', 'IncidentController@deleteIncident');
$router->delete('incidents/{incident}/updates/{update}', 'IncidentUpdateController@deleteIncidentUpdate');
$router->delete('metrics/{metric}', 'MetricController@deleteMetric');
$router->delete('metrics/{metric}/points/{metric_point}', 'MetricPointController@deleteMetricPoint');
$router->delete('subscribers/{subscriber}', 'SubscriberController@deleteSubscriber');

View File

@ -53,7 +53,12 @@ class IncidentRoutes
'as' => 'edit',
'uses' => 'IncidentController@showEditIncidentAction',
]);
$router->get('{incident}/update', [
'as' => 'update',
'uses' => 'IncidentController@showIncidentUpdateAction',
]);
$router->post('{incident}/edit', 'IncidentController@editIncidentAction');
$router->post('{incident}/update', 'IncidentController@createIncidentUpdateAction');
});
}
}

View File

@ -53,8 +53,11 @@ class System implements SystemContract
return $incident->status > 0;
});
$incidentCount = $incidents->count();
$unresolvedCount = $incidents->filter(function ($incident) {
return !$incident->is_resolved;
})->count();
if ($incidentCount === 0 || ($incidentCount >= 1 && (int) $incidents->first()->status === 4)) {
if ($incidentCount === 0 || ($incidentCount >= 1 && $unresolvedCount === 0)) {
$status = [
'system_status' => 'success',
'system_message' => trans_choice('cachet.service.good', $totalComponents),

View File

@ -25,6 +25,43 @@ class Incident extends Model implements HasPresenter
{
use SearchableTrait, SoftDeletes, SortableTrait, ValidatingTrait;
/**
* Status for incident being investigated.
*
* @var int
*/
const INVESTIGATING = 1;
/**
* Status for incident having been identified.
*
* @var int
*/
const IDENTIFIED = 2;
/**
* Status for incident being watched.
*
* @var int
*/
const WATCHED = 3;
/**
* Status for incident now being fixed.
*
* @var int
*/
const FIXED = 4;
/**
* The accessors to append to the model's array form.
*
* @var string[]
*/
protected $appends = [
'is_resolved',
];
/**
* The attributes that should be casted to native types.
*
@ -96,6 +133,13 @@ class Incident extends Model implements HasPresenter
'message',
];
/**
* The relations to eager load on every query.
*
* @var string[]
*/
protected $with = ['updates'];
/**
* Get the component relation.
*
@ -106,6 +150,16 @@ class Incident extends Model implements HasPresenter
return $this->belongsTo(Component::class, 'component_id', 'id');
}
/**
* Get the updates relation.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function updates()
{
return $this->hasMany(IncidentUpdate::class)->orderBy('created_at', 'desc');
}
/**
* Finds all visible incidents.
*
@ -168,6 +222,20 @@ class Incident extends Model implements HasPresenter
return $this->getOriginal('scheduled_at') !== null;
}
/**
* Is the incident resolved?
*
* @return bool
*/
public function getIsResolvedAttribute()
{
if ($updates = $this->updates->first()) {
return $updates->status === self::FIXED;
}
return $this->status === self::FIXED;
}
/**
* Get the presenter class.
*

View File

@ -0,0 +1,95 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Models;
use AltThree\Validator\ValidatingTrait;
use CachetHQ\Cachet\Models\Traits\SortableTrait;
use CachetHQ\Cachet\Presenters\IncidentUpdatePresenter;
use Illuminate\Database\Eloquent\Model;
use McCool\LaravelAutoPresenter\HasPresenter;
/**
* This is the incident update class.
*
* @author James Brooks <james@alt-three.com>
*/
class IncidentUpdate extends Model implements HasPresenter
{
use SortableTrait, ValidatingTrait;
/**
* The attributes that should be casted to native types.
*
* @var string[]
*/
protected $casts = [
'incident_id' => 'int',
'status' => 'int',
'message' => 'string',
'user_id' => 'int',
];
/**
* The fillable properties.
*
* @var string[]
*/
protected $fillable = [
'incident_id',
'status',
'message',
'user_id',
];
/**
* The validation rules.
*
* @var string[]
*/
public $rules = [
'incident_id' => 'int',
'status' => 'required|int',
'message' => 'required|string',
'user_id' => 'required|int',
];
/**
* The sortable fields.
*
* @var string[]
*/
protected $sortable = [
'id',
'status',
'user_id',
];
/**
* Get the incident relation.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function incident()
{
return $this->belongsTo(Incident::class);
}
/**
* Get the presenter class.
*
* @return string
*/
public function getPresenterClass()
{
return IncidentUpdatePresenter::class;
}
}

View File

@ -22,6 +22,19 @@ class IncidentPresenter extends BasePresenter implements Arrayable
{
use TimestampsTrait;
/**
* Inciden icon lookup.
*
* @var array
*/
protected $icons = [
0 => 'icon ion-android-calendar', // Scheduled
1 => 'icon ion-flag oranges', // Investigating
2 => 'icon ion-alert yellows', // Identified
3 => 'icon ion-eye blues', // Watching
4 => 'icon ion-checkmark greens', // Fixed
];
/**
* Renders the message from Markdown into HTML.
*
@ -32,6 +45,16 @@ class IncidentPresenter extends BasePresenter implements Arrayable
return Markdown::convertToHtml($this->wrappedObject->message);
}
/**
* Return the raw text of the message, even without Markdown.
*
* @return string
*/
public function raw_message()
{
return strip_tags($this->formattedMessage());
}
/**
* Present diff for humans date time.
*
@ -157,19 +180,8 @@ class IncidentPresenter extends BasePresenter implements Arrayable
*/
public function icon()
{
switch ($this->wrappedObject->status) {
case 0: // Scheduled
return 'icon ion-android-calendar';
case 1: // Investigating
return 'icon ion-flag oranges';
case 2: // Identified
return 'icon ion-alert yellows';
case 3: // Watching
return 'icon ion-eye blues';
case 4: // Fixed
return 'icon ion-checkmark greens';
default: // Something actually broke, this shouldn't happen.
return '';
if (isset($this->icons[$this->wrappedObject->status])) {
return $this->icons[$this->wrappedObject->status];
}
}
@ -183,6 +195,88 @@ class IncidentPresenter extends BasePresenter implements Arrayable
return trans('cachet.incidents.status.'.$this->wrappedObject->status);
}
/**
* Returns the latest update.
*
* @return int|null
*/
public function latest_status()
{
if ($update = $this->latest()) {
return $update->status;
}
return $this->wrappedObject->status;
}
/**
* Returns the latest update.
*
* @return string|null
*/
public function latest_human_status()
{
if ($update = $this->latest()) {
return trans('cachet.incidents.status.'.$update->status);
}
return $this->human_status();
}
/**
* Present the latest icon.
*
* @return string
*/
public function latest_icon()
{
if ($update = $this->latest()) {
if (isset($this->icons[$update->status])) {
return $this->icons[$update->status];
}
}
return $this->icon();
}
/**
* Fetch the latest incident update.
*
* @return \CachetHQ\Cachet\Models\IncidentUpdate|void
*/
public function latest()
{
if ($update = $this->wrappedObject->updates()->orderBy('created_at', 'desc')->first()) {
return $update;
}
}
/**
* Get the incident permalink.
*
* @return string
*/
public function permalink()
{
return route('incident', $this->wrappedObject->id);
}
/**
* The duration since the last update (in seconds).
*
* @return int
*/
public function duration()
{
if ($update = $this->latest()) {
dd($update->created_at->diffInSeconds($this->wrappedObject->created_at));
return $this->wrappedObject->created_at->diffInSeconds($update->created_at);
}
return 0;
}
/**
* Convert the presenter instance to an array.
*
@ -191,10 +285,16 @@ class IncidentPresenter extends BasePresenter implements Arrayable
public function toArray()
{
return array_merge($this->wrappedObject->toArray(), [
'human_status' => $this->human_status(),
'scheduled_at' => $this->scheduled_at(),
'created_at' => $this->created_at(),
'updated_at' => $this->updated_at(),
'human_status' => $this->human_status(),
'latest_update_id' => $this->latest() ? $this->latest()->id : null,
'latest_status' => $this->latest_status(),
'latest_human_status' => $this->latest_human_status(),
'latest_icon' => $this->latest_icon(),
'permalink' => $this->permalink(),
'duration' => $this->duration(),
'scheduled_at' => $this->scheduled_at(),
'created_at' => $this->created_at(),
'updated_at' => $this->updated_at(),
]);
}
}

View File

@ -0,0 +1,173 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Presenters;
use CachetHQ\Cachet\Dates\DateFactory;
use CachetHQ\Cachet\Presenters\Traits\TimestampsTrait;
use GrahamCampbell\Markdown\Facades\Markdown;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Facades\Config;
use McCool\LaravelAutoPresenter\BasePresenter;
/**
* This is the incident update presenter.
*
* @author James Brooks <james@alt-three.com>
*/
class IncidentUpdatePresenter extends BasePresenter implements Arrayable
{
use TimestampsTrait;
/**
* Renders the message from Markdown into HTML.
*
* @return string
*/
public function formattedMessage()
{
return Markdown::convertToHtml($this->wrappedObject->message);
}
/**
* Return the raw text of the message, even without Markdown.
*
* @return string
*/
public function raw_message()
{
return strip_tags($this->formattedMessage());
}
/**
* Present diff for humans date time.
*
* @return string
*/
public function created_at_diff()
{
return app(DateFactory::class)->make($this->wrappedObject->created_at)->diffForHumans();
}
/**
* Present formatted date time.
*
* @return string
*/
public function created_at_formatted()
{
return ucfirst(app(DateFactory::class)->make($this->wrappedObject->created_at)->format(Config::get('setting.incident_date_format', 'l jS F Y H:i:s')));
}
/**
* Formats the created_at time ready to be used by bootstrap-datetimepicker.
*
* @return string
*/
public function created_at_datetimepicker()
{
return app(DateFactory::class)->make($this->wrappedObject->created_at)->format('d/m/Y H:i');
}
/**
* Present formatted date time.
*
* @return string
*/
public function created_at_iso()
{
return app(DateFactory::class)->make($this->wrappedObject->created_at)->toISO8601String();
}
/**
* Returns a formatted timestamp for use within the timeline.
*
* @return string
*/
public function timestamp_formatted()
{
if ($this->wrappedObject->is_scheduled) {
return $this->scheduled_at_formatted;
}
return $this->created_at_formatted;
}
/**
* Return the iso timestamp for use within the timeline.
*
* @return string
*/
public function timestamp_iso()
{
if ($this->wrappedObject->is_scheduled) {
return $this->scheduled_at_iso;
}
return $this->created_at_iso;
}
/**
* Present the status with an icon.
*
* @return string
*/
public function icon()
{
switch ($this->wrappedObject->status) {
case 1: // Investigating
return 'icon ion-flag oranges';
case 2: // Identified
return 'icon ion-alert yellows';
case 3: // Watching
return 'icon ion-eye blues';
case 4: // Fixed
return 'icon ion-checkmark greens';
default: // Something actually broke, this shouldn't happen.
return '';
}
}
/**
* Returns a human readable version of the status.
*
* @return string
*/
public function human_status()
{
return trans('cachet.incidents.status.'.$this->wrappedObject->status);
}
/**
* Generate a permalink to the incident update.
*
* @return string
*/
public function permalink()
{
return route('incident', ['incident' => $this->wrappedObject->incident]).'#update-'.$this->wrappedObject->id;
}
/**
* Convert the presenter instance to an array.
*
* @return string[]
*/
public function toArray()
{
return array_merge($this->wrappedObject->toArray(), [
'human_status' => $this->human_status(),
'permalink' => $this->permalink(),
'created_at' => $this->created_at(),
'updated_at' => $this->updated_at(),
]);
}
}

View File

@ -13,6 +13,7 @@ use CachetHQ\Cachet\Models\Component;
use CachetHQ\Cachet\Models\ComponentGroup;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\IncidentTemplate;
use CachetHQ\Cachet\Models\IncidentUpdate;
use CachetHQ\Cachet\Models\Metric;
use CachetHQ\Cachet\Models\MetricPoint;
use CachetHQ\Cachet\Models\Setting;
@ -58,6 +59,15 @@ $factory->define(IncidentTemplate::class, function ($faker) {
];
});
$factory->define(IncidentUpdate::class, function ($faker) {
return [
'incident_id' => factory(Incident::class)->create()->id,
'message' => $faker->paragraph(),
'status' => random_int(1, 4),
'user_id' => factory(User::class)->create()->id,
];
});
$factory->define(Metric::class, function ($faker) {
return [
'name' => $faker->sentence(),

View File

@ -0,0 +1,46 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateIncidentUpdatesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('incident_updates', function (Blueprint $table) {
$table->increments('id');
$table->integer('incident_id')->unsigned();
$table->integer('status');
$table->longText('message');
$table->integer('user_id')->unsigned();
$table->timestamps();
$table->index('incident_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('incident_updates');
}
}

View File

@ -276,6 +276,14 @@ body.status-page {
line-height: 1.3em;
}
i.icon {
font-size: 21px;
line-height: 24px;
text-align: center;
display: inline-block;
min-width: 20px;
}
&.group-name {
background-color: $cachet_gray_light;
padding: {

View File

@ -33,6 +33,7 @@ return [
'stickied' => 'Stickied Incidents',
'scheduled' => 'Scheduled Maintenance',
'scheduled_at' => ', scheduled :timestamp',
'posted' => 'Posted :timestamp',
'status' => [
0 => 'Scheduled', // TODO: Hopefully remove this.
1 => 'Investigating',

View File

@ -20,6 +20,7 @@ return [
'logged' => '{0} There are no incidents, good work.|You have logged one incident.|You have reported <strong>:count</strong> incidents.',
'incident-create-template' => 'Create Template',
'incident-templates' => 'Incident Templates',
'updates' => '{0} Zero Updates|One Update|:count Updates',
'add' => [
'title' => 'Report an incident',
'success' => 'Incident added.',
@ -34,6 +35,10 @@ return [
'success' => 'The incident has been deleted and will not show on your status page.',
'failure' => 'The incident could not be deleted, please try again.',
],
'update' => [
'title' => 'Create new incident update',
'subtitle' => 'Add an update to <strong>:incident</strong>',
],
// Incident templates
'templates' => [

View File

@ -24,13 +24,14 @@
@foreach($incidents as $incident)
<div class="row striped-list-item">
<div class="col-xs-6">
<i class="{{ $incident->icon }}"></i> <strong>{{ $incident->name }}</strong>
<i class="{{ $incident->icon }}"></i> <strong>{{ $incident->name }}</strong> <span class="badge badge-info">{{ trans_choice('dashboard.incidents.updates', $incident->updates->count()) }}</span>
@if($incident->message)
<p><small>{{ Str::words($incident->message, 5) }}</small></p>
@endif
</div>
<div class="col-xs-6 text-right">
<a href="/dashboard/incidents/{{ $incident->id }}/edit" class="btn btn-default">{{ trans('forms.edit') }}</a>
<a href="/dashboard/incidents/{{ $incident->id }}/update" class="btn btn-info">{{ trans('forms.update') }}</a>
<a href="/dashboard/incidents/{{ $incident->id }}/delete" class="btn btn-danger confirm-action" data-method='DELETE'>{{ trans('forms.delete') }}</a>
</div>
</div>

View File

@ -0,0 +1,64 @@
@extends('layout.dashboard')
@section('content')
<div class="header">
<div class="sidebar-toggler visible-xs">
<i class="icon ion-navicon"></i>
</div>
<span class="uppercase">
<i class="icon ion-android-alert"></i> {{ trans('dashboard.incidents.incidents') }}
</span>
&gt; <small>{{ trans('dashboard.incidents.update.title') }}</small>
</div>
<div class="content-wrapper">
<div class="row">
<div class="col-md-12">
@include('dashboard.partials.errors')
<p class="lead">{!! trans('dashboard.incidents.update.subtitle', ['incident' => $incident->name]) !!}</p>
<form class="form-vertical" name="IncidentUpdateForm" role="form" method="POST" autocomplete="off">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<fieldset>
<div class="form-group">
<label for="incident-name">{{ trans('forms.incidents.status') }}</label><br>
<label class="radio-inline">
<input type="radio" name="status" value="1">
<i class="icon ion-flag"></i>
{{ trans('cachet.incidents.status')[1] }}
</label>
<label class="radio-inline">
<input type="radio" name="status" value="2">
<i class="icon ion-alert-circled"></i>
{{ trans('cachet.incidents.status')[2] }}
</label>
<label class="radio-inline">
<input type="radio" name="status" value="3">
<i class="icon ion-eye"></i>
{{ trans('cachet.incidents.status')[3] }}
</label>
<label class="radio-inline">
<input type="radio" name="status" value="4">
<i class="icon ion-checkmark"></i>
{{ trans('cachet.incidents.status')[4] }}
</label>
</div>
<div class="form-group">
<label>{{ trans('forms.incidents.message') }}</label>
<div class="markdown-control">
<textarea name="message" class="form-control autosize" rows="5" required></textarea>
</div>
</div>
</fieldset>
<input type="hidden" name="incident_id" value={{ $incident->id }}>
<div class="form-group">
<div class="btn-group">
<button type="submit" class="btn btn-success">{{ trans('forms.update') }}</button>
<a class="btn btn-default" href="{{ route('dashboard.incidents.index') }}">{{ trans('forms.cancel') }}</a>
</div>
</div>
</form>
</div>
</div>
</div>
@stop

View File

@ -22,4 +22,15 @@
<div class="panel-body markdown-body">
{!! $incident->formattedMessage !!}
</div>
@if($incident->updates->count())
<div class="list-group">
@foreach($incident->updates as $update)
<a class="list-group-item" href="{{ $update->permalink }}">
<i class="{{ $update->icon }}" title="{{ $update->human_status }}" data-toggle="tooltip"></i> <strong>{{ Str::limit($update->raw_message, 20) }}</strong>
<small>{{ $update->created_at_diff }}</small>
<span class="ion-ios-arrow-right pull-right"></span>
</a>
@endforeach
</div>
@endif
</div>

View File

@ -5,8 +5,8 @@
<div class="moment {{ $incidentID === 0 ? 'first' : null }}">
<div class="row event clearfix">
<div class="col-sm-1">
<div class="status-icon status-{{ $incident->status }}" data-toggle="tooltip" title="{{ $incident->human_status }}" data-placement="left">
<i class="{{ $incident->icon }}"></i>
<div class="status-icon status-{{ $incident->latest_human_status }}" data-toggle="tooltip" title="{{ $incident->latest_human_status }}" data-placement="left">
<i class="{{ $incident->latest_icon }}"></i>
</div>
</div>
<div class="col-xs-10 col-xs-offset-2 col-sm-11 col-sm-offset-0">

View File

@ -7,22 +7,39 @@
@stop
@section('content')
<h1>{{ formatted_date($incident->created_at) }}</h1>
<h1>{{ $incident->name }} <small>{{ formatted_date($incident->created_at) }}</small></h1>
<hr>
<div class="markdown-body">
{!! $incident->formattedMessage !!}
</div>
@if($incident->updates)
<div class="timeline">
<div class="content-wrapper">
<div class="moment first">
@foreach ($incident->updates as $index => $update)
<div class="moment {{ $index === 0 ? 'first' : null }}" id="update-{{ $update->id }}">
<div class="row event clearfix">
<div class="col-sm-1">
<div class="status-icon status-{{ $incident->status }}" data-toggle="tooltip" title="{{ $incident->human_status }}" data-placement="left">
<i class="{{ $incident->icon }}"></i>
<div class="status-icon status-{{ $update->status }}" data-toggle="tooltip" title="{{ $update->human_status }}" data-placement="left">
<i class="{{ $update->icon }}"></i>
</div>
</div>
<div class="col-xs-10 col-xs-offset-2 col-sm-11 col-sm-offset-0">
@include('partials.incident', ['incident' => $incident, 'with_link' => false])
<div class="panel panel-message incident">
<div class="panel-body">
<div class="markdown-body">
{!! $update->formattedMessage !!}
</div>
</div>
<div class="panel-footer"><small>{{ trans('cachet.incidents.posted', ['timestamp' => $update->created_at_diff]) }}</small></div>
</div>
</div>
</div>
</div>
@endforeach
</div>
</div>
@endif
@stop

View File

@ -0,0 +1,102 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Api;
/**
* This is the incident update test class.
*
* @author James Brooks <james@alt-three.com>
*/
class IncidentUpdateTest extends AbstractApiTestCase
{
public function testGetIncidentUpdates()
{
$incident = factory('CachetHQ\Cachet\Models\Incident')->create();
$updates = factory('CachetHQ\Cachet\Models\IncidentUpdate', 3)->create([
'incident_id' => $incident->id,
]);
$this->get("/api/v1/incidents/{$incident->id}/updates");
$this->assertResponseOk();
$this->seeJson(['id' => $updates[0]->id]);
$this->seeJson(['id' => $updates[1]->id]);
$this->seeJson(['id' => $updates[2]->id]);
}
public function testGetInvalidIncidentUpdate()
{
$this->get('/api/v1/incidents/1/updates/1');
$this->assertResponseStatus(404);
}
public function testPostIncidentUpdateUnauthorized()
{
$incident = factory('CachetHQ\Cachet\Models\Incident')->create();
$this->post("/api/v1/incidents/{$incident->id}/updates");
$this->assertResponseStatus(401);
}
public function testPostIncidentUpdateNoData()
{
$this->beUser();
$incident = factory('CachetHQ\Cachet\Models\Incident')->create();
$this->post("/api/v1/incidents/{$incident->id}/updates");
$this->assertResponseStatus(400);
}
public function testPostIncidentUpdate()
{
$this->beUser();
$incident = factory('CachetHQ\Cachet\Models\Incident')->create();
$this->post("/api/v1/incidents/{$incident->id}/updates", [
'status' => 4,
'message' => 'Incident fixed!',
]);
$this->assertResponseOk();
$this->seeJson(['incident_id' => $incident->id]);
}
public function testPutIncidentUpdate()
{
$this->beUser();
$incident = factory('CachetHQ\Cachet\Models\Incident')->create();
$update = factory('CachetHQ\Cachet\Models\IncidentUpdate')->create();
$this->put("/api/v1/incidents/{$incident->id}/updates/{$update->id}", [
'message' => 'Message updated :smile:',
]);
$this->assertResponseOk();
$this->seeJson(['message' => 'Message updated :smile:']);
}
public function testDeleteIncidentUpdate()
{
$this->beUser();
$incident = factory('CachetHQ\Cachet\Models\Incident')->create();
$update = factory('CachetHQ\Cachet\Models\IncidentUpdate')->create();
$this->delete("/api/v1/incidents/{$incident->id}/updates/{$update->id}");
$this->assertResponseStatus(204);
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Bus\Events\IncidentUpdate;
use AltThree\TestBench\EventTrait;
use CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateEventInterface;
use CachetHQ\Tests\Cachet\AbstractTestCase;
/**
* This is the abstract incident update event test case class.
*
* @author James Brooks <james@alt-three.com>
*/
abstract class AbstractIncidentUpdateCommandTest extends AbstractTestCase
{
use EventTrait;
protected function getEventInterfaces()
{
return [IncidentUpdateEventInterface::class];
}
}

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Bus\Commands\IncidentUpdate;
use AltThree\TestBench\CommandTrait;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\RemoveIncidentUpdateCommand;
use CachetHQ\Cachet\Bus\Handlers\Commands\IncidentUpdate\RemoveIncidentUpdateCommandHandler;
use CachetHQ\Cachet\Models\IncidentUpdate;
use CachetHQ\Tests\Cachet\AbstractTestCase;
/**
* This is the remove incident update command test class.
*
* @author James Brooks <james@alt-three.com>
*/
class RemoveIncidentUpdateCommandTest extends AbstractTestCase
{
use CommandTrait;
protected function getObjectAndParams()
{
$params = ['incidentUpdate' => new IncidentUpdate()];
$object = new RemoveIncidentUpdateCommand($params['incidentUpdate']);
return compact('params', 'object');
}
protected function getHandlerClass()
{
return RemoveIncidentUpdateCommandHandler::class;
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Bus\Commands\IncidentUpdate;
use AltThree\TestBench\CommandTrait;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\ReportIncidentUpdateCommand;
use CachetHQ\Cachet\Bus\Handlers\Commands\IncidentUpdate\ReportIncidentUpdateCommandHandler;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\User;
use CachetHQ\Tests\Cachet\AbstractTestCase;
/**
* This is the report incident update command test class.
*
* @author James Brooks <james@alt-three.com>
*/
class ReportIncidentUpdateCommandTest extends AbstractTestCase
{
use CommandTrait;
protected function getObjectAndParams()
{
$params = [
'incident' => new Incident(),
'status' => 1,
'message' => 'Foo',
'user' => new User(),
];
$object = new ReportIncidentUpdateCommand($params['incident'], $params['status'], $params['message'], $params['user']);
return compact('params', 'object');
}
protected function objectHasRules()
{
return true;
}
protected function getHandlerClass()
{
return ReportIncidentUpdateCommandHandler::class;
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Bus\Commands\IncidentUpdate;
use AltThree\TestBench\CommandTrait;
use CachetHQ\Cachet\Bus\Commands\IncidentUpdate\UpdateIncidentUpdateCommand;
use CachetHQ\Cachet\Bus\Handlers\Commands\IncidentUpdate\UpdateIncidentUpdateCommandHandler;
use CachetHQ\Cachet\Models\IncidentUpdate;
use CachetHQ\Cachet\Models\User;
use CachetHQ\Tests\Cachet\AbstractTestCase;
/**
* This is the update incident update command test class.
*
* @author James Brooks <james@alt-three.com>
*/
class UpdateIncidentUpdateCommandTest extends AbstractTestCase
{
use CommandTrait;
protected function getObjectAndParams()
{
$params = ['update' => new IncidentUpdate(), 'status' => 1, 'message' => 'Updating!', 'user' => new User()];
$object = new UpdateIncidentUpdateCommand(
$params['update'],
$params['status'],
$params['message'],
$params['user']
);
return compact('params', 'object');
}
protected function objectHasRules()
{
return true;
}
protected function getHandlerClass()
{
return UpdateIncidentUpdateCommandHandler::class;
}
}

View File

@ -0,0 +1,26 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Bus\Events\IncidentUpdate;
use AltThree\TestBench\EventTrait;
use CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateEventInterface;
use CachetHQ\Tests\Cachet\AbstractTestCase;
abstract class AbstractIncidentUpdateEventTestCase extends AbstractTestCase
{
use EventTrait;
protected function getEventInterfaces()
{
return [IncidentUpdateEventInterface::class];
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Bus\Events\IncidentUpdate;
use CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasRemovedEvent;
use CachetHQ\Cachet\Models\IncidentUpdate;
class IncidentUpdateWasRemovedEventTest extends AbstractIncidentUpdateEventTestCase
{
protected function objectHasHandlers()
{
return false;
}
protected function getObjectAndParams()
{
$params = ['update' => new IncidentUpdate()];
$object = new IncidentUpdateWasRemovedEvent($params['update']);
return compact('params', 'object');
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Bus\Events\IncidentUpdate;
use CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasReportedEvent;
use CachetHQ\Cachet\Models\IncidentUpdate;
class IncidentUpdateWasReportedEventTest extends AbstractIncidentUpdateEventTestCase
{
protected function objectHasHandlers()
{
return false;
}
protected function getObjectAndParams()
{
$params = ['update' => new IncidentUpdate()];
$object = new IncidentUpdateWasReportedEvent($params['update']);
return compact('params', 'object');
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Bus\Events\IncidentUpdate;
use CachetHQ\Cachet\Bus\Events\IncidentUpdate\IncidentUpdateWasUpdatedEvent;
use CachetHQ\Cachet\Models\IncidentUpdate;
class IncidentUpdateWasUpdatedEventTest extends AbstractIncidentUpdateEventTestCase
{
protected function objectHasHandlers()
{
return false;
}
protected function getObjectAndParams()
{
$params = ['update' => new IncidentUpdate()];
$object = new IncidentUpdateWasUpdatedEvent($params['update']);
return compact('params', 'object');
}
}

View File

@ -38,16 +38,14 @@ class StatusPageControllerTest extends AbstractTestCase
->setupConfig();
}
/** @test */
public function on_index_only_public_component_groups_are_shown_to_a_guest()
public function testIndexShowsOnlyPublicComponentGroupsToGues()
{
$this->visit('/')
->see(self::COMPONENT_GROUP_1_NAME)
->dontSee(self::COMPONENT_GROUP_2_NAME);
}
/** @test */
public function on_index_all_component_groups_are_displayed_to_logged_in_users()
public function testIndexShowsAllComponentGroupsToLoggedInUsers()
{
$this->signIn();

View File

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Tests\Cachet\Models;
use AltThree\TestBench\ValidationTrait;
use CachetHQ\Cachet\Models\IncidentUpdate;
use CachetHQ\Tests\Cachet\AbstractTestCase;
/**
* This is the incident model test class.
*
* @author James Brooks <james@alt-three.com>
*/
class IncidentUpdateTest extends AbstractTestCase
{
use ValidationTrait;
public function testValidation()
{
$this->checkRules(new IncidentUpdate());
}
}