From c94919f1b942f80aa4123b256878b9582b763197 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Thu, 6 Oct 2016 17:21:18 +0100 Subject: [PATCH 01/15] Backport Incident Updates from v3.0.0 --- .../RemoveIncidentUpdateCommand.php | 41 +++++ .../ReportIncidentUpdateCommand.php | 81 ++++++++ .../UpdateIncidentUpdateCommand.php | 81 ++++++++ .../IncidentUpdateEventInterface.php | 24 +++ .../IncidentUpdateWasRemovedEvent.php | 41 +++++ .../IncidentUpdateWasReportedEvent.php | 41 +++++ .../IncidentUpdateWasUpdatedEvent.php | 41 +++++ .../RemoveIncidentUpdateCommandHandler.php | 39 ++++ .../ReportIncidentUpdateCommandHandler.php | 65 +++++++ .../UpdateIncidentUpdateCommandHandler.php | 59 ++++++ app/Console/Commands/DemoSeederCommand.php | 105 ++++++----- .../Providers/EventServiceProvider.php | 9 + .../Api/IncidentUpdateController.php | 132 +++++++++++++ .../Dashboard/IncidentController.php | 59 +++++- app/Http/Controllers/StatusPageController.php | 2 +- app/Http/Routes/ApiRoutes.php | 6 + app/Http/Routes/Dashboard/IncidentRoutes.php | 5 + app/Integrations/Core/System.php | 5 +- app/Models/Incident.php | 68 +++++++ app/Models/IncidentUpdate.php | 95 ++++++++++ app/Presenters/IncidentPresenter.php | 134 ++++++++++++-- app/Presenters/IncidentUpdatePresenter.php | 173 ++++++++++++++++++ database/factories/ModelFactory.php | 10 + ...3_08_125729_CreateIncidentUpdatesTable.php | 46 +++++ .../assets/sass/status-page/_status-page.scss | 8 + resources/lang/en/cachet.php | 1 + resources/lang/en/dashboard.php | 5 + .../views/dashboard/incidents/index.blade.php | 3 +- .../dashboard/incidents/update.blade.php | 64 +++++++ resources/views/partials/incident.blade.php | 11 ++ resources/views/partials/incidents.blade.php | 4 +- resources/views/single-incident.blade.php | 27 ++- tests/Api/IncidentUpdateTest.php | 102 +++++++++++ .../AbstractIncidentUpdateCommandTest.php | 31 ++++ .../RemoveIncidentUpdateCommandTest.php | 41 +++++ .../ReportIncidentUpdateCommandTest.php | 52 ++++++ .../UpdateIncidentUpdateCommandTest.php | 52 ++++++ .../AbstractIncidentUpdateEventTestCase.php | 26 +++ .../IncidentUpdateWasRemovedEventTest.php | 31 ++++ .../IncidentUpdateWasReportedEventTest.php | 31 ++++ .../IncidentUpdateWasUpdatedEventTest.php | 31 ++++ .../Controllers/StatusPageControllerTest.php | 6 +- tests/Models/IncidentUpdateTest.php | 31 ++++ 43 files changed, 1834 insertions(+), 85 deletions(-) create mode 100644 app/Bus/Commands/IncidentUpdate/RemoveIncidentUpdateCommand.php create mode 100644 app/Bus/Commands/IncidentUpdate/ReportIncidentUpdateCommand.php create mode 100644 app/Bus/Commands/IncidentUpdate/UpdateIncidentUpdateCommand.php create mode 100644 app/Bus/Events/IncidentUpdate/IncidentUpdateEventInterface.php create mode 100644 app/Bus/Events/IncidentUpdate/IncidentUpdateWasRemovedEvent.php create mode 100644 app/Bus/Events/IncidentUpdate/IncidentUpdateWasReportedEvent.php create mode 100644 app/Bus/Events/IncidentUpdate/IncidentUpdateWasUpdatedEvent.php create mode 100644 app/Bus/Handlers/Commands/IncidentUpdate/RemoveIncidentUpdateCommandHandler.php create mode 100644 app/Bus/Handlers/Commands/IncidentUpdate/ReportIncidentUpdateCommandHandler.php create mode 100644 app/Bus/Handlers/Commands/IncidentUpdate/UpdateIncidentUpdateCommandHandler.php create mode 100644 app/Http/Controllers/Api/IncidentUpdateController.php create mode 100644 app/Models/IncidentUpdate.php create mode 100644 app/Presenters/IncidentUpdatePresenter.php create mode 100644 database/migrations/2016_03_08_125729_CreateIncidentUpdatesTable.php create mode 100644 resources/views/dashboard/incidents/update.blade.php create mode 100644 tests/Api/IncidentUpdateTest.php create mode 100644 tests/Bus/Commands/IncidentUpdate/AbstractIncidentUpdateCommandTest.php create mode 100644 tests/Bus/Commands/IncidentUpdate/RemoveIncidentUpdateCommandTest.php create mode 100644 tests/Bus/Commands/IncidentUpdate/ReportIncidentUpdateCommandTest.php create mode 100644 tests/Bus/Commands/IncidentUpdate/UpdateIncidentUpdateCommandTest.php create mode 100644 tests/Bus/Events/IncidentUpdate/AbstractIncidentUpdateEventTestCase.php create mode 100644 tests/Bus/Events/IncidentUpdate/IncidentUpdateWasRemovedEventTest.php create mode 100644 tests/Bus/Events/IncidentUpdate/IncidentUpdateWasReportedEventTest.php create mode 100644 tests/Bus/Events/IncidentUpdate/IncidentUpdateWasUpdatedEventTest.php create mode 100644 tests/Models/IncidentUpdateTest.php diff --git a/app/Bus/Commands/IncidentUpdate/RemoveIncidentUpdateCommand.php b/app/Bus/Commands/IncidentUpdate/RemoveIncidentUpdateCommand.php new file mode 100644 index 00000000..7440e352 --- /dev/null +++ b/app/Bus/Commands/IncidentUpdate/RemoveIncidentUpdateCommand.php @@ -0,0 +1,41 @@ + + */ +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; + } +} diff --git a/app/Bus/Commands/IncidentUpdate/ReportIncidentUpdateCommand.php b/app/Bus/Commands/IncidentUpdate/ReportIncidentUpdateCommand.php new file mode 100644 index 00000000..d88e5140 --- /dev/null +++ b/app/Bus/Commands/IncidentUpdate/ReportIncidentUpdateCommand.php @@ -0,0 +1,81 @@ + + */ +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; + } +} diff --git a/app/Bus/Commands/IncidentUpdate/UpdateIncidentUpdateCommand.php b/app/Bus/Commands/IncidentUpdate/UpdateIncidentUpdateCommand.php new file mode 100644 index 00000000..55370e2a --- /dev/null +++ b/app/Bus/Commands/IncidentUpdate/UpdateIncidentUpdateCommand.php @@ -0,0 +1,81 @@ + + */ +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; + } +} diff --git a/app/Bus/Events/IncidentUpdate/IncidentUpdateEventInterface.php b/app/Bus/Events/IncidentUpdate/IncidentUpdateEventInterface.php new file mode 100644 index 00000000..9f1687af --- /dev/null +++ b/app/Bus/Events/IncidentUpdate/IncidentUpdateEventInterface.php @@ -0,0 +1,24 @@ + + */ +interface IncidentUpdateEventInterface extends EventInterface +{ + // +} diff --git a/app/Bus/Events/IncidentUpdate/IncidentUpdateWasRemovedEvent.php b/app/Bus/Events/IncidentUpdate/IncidentUpdateWasRemovedEvent.php new file mode 100644 index 00000000..1fba40ae --- /dev/null +++ b/app/Bus/Events/IncidentUpdate/IncidentUpdateWasRemovedEvent.php @@ -0,0 +1,41 @@ + + */ +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; + } +} diff --git a/app/Bus/Events/IncidentUpdate/IncidentUpdateWasReportedEvent.php b/app/Bus/Events/IncidentUpdate/IncidentUpdateWasReportedEvent.php new file mode 100644 index 00000000..e2cf8928 --- /dev/null +++ b/app/Bus/Events/IncidentUpdate/IncidentUpdateWasReportedEvent.php @@ -0,0 +1,41 @@ + + */ +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; + } +} diff --git a/app/Bus/Events/IncidentUpdate/IncidentUpdateWasUpdatedEvent.php b/app/Bus/Events/IncidentUpdate/IncidentUpdateWasUpdatedEvent.php new file mode 100644 index 00000000..042b3af7 --- /dev/null +++ b/app/Bus/Events/IncidentUpdate/IncidentUpdateWasUpdatedEvent.php @@ -0,0 +1,41 @@ + + */ +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; + } +} diff --git a/app/Bus/Handlers/Commands/IncidentUpdate/RemoveIncidentUpdateCommandHandler.php b/app/Bus/Handlers/Commands/IncidentUpdate/RemoveIncidentUpdateCommandHandler.php new file mode 100644 index 00000000..40bc6def --- /dev/null +++ b/app/Bus/Handlers/Commands/IncidentUpdate/RemoveIncidentUpdateCommandHandler.php @@ -0,0 +1,39 @@ + + */ +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(); + } +} diff --git a/app/Bus/Handlers/Commands/IncidentUpdate/ReportIncidentUpdateCommandHandler.php b/app/Bus/Handlers/Commands/IncidentUpdate/ReportIncidentUpdateCommandHandler.php new file mode 100644 index 00000000..dd33c73e --- /dev/null +++ b/app/Bus/Handlers/Commands/IncidentUpdate/ReportIncidentUpdateCommandHandler.php @@ -0,0 +1,65 @@ + + */ +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; + } +} diff --git a/app/Bus/Handlers/Commands/IncidentUpdate/UpdateIncidentUpdateCommandHandler.php b/app/Bus/Handlers/Commands/IncidentUpdate/UpdateIncidentUpdateCommandHandler.php new file mode 100644 index 00000000..e6593980 --- /dev/null +++ b/app/Bus/Handlers/Commands/IncidentUpdate/UpdateIncidentUpdateCommandHandler.php @@ -0,0 +1,59 @@ + + */ +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; + }); + } +} diff --git a/app/Console/Commands/DemoSeederCommand.php b/app/Console/Commands/DemoSeederCommand.php index 283f6d61..c2025185 100644 --- a/app/Console/Commands/DemoSeederCommand.php +++ b/app/Console/Commands/DemoSeederCommand.php @@ -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. * diff --git a/app/Foundation/Providers/EventServiceProvider.php b/app/Foundation/Providers/EventServiceProvider.php index b0f970da..bf808404 100644 --- a/app/Foundation/Providers/EventServiceProvider.php +++ b/app/Foundation/Providers/EventServiceProvider.php @@ -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' => [ // ], diff --git a/app/Http/Controllers/Api/IncidentUpdateController.php b/app/Http/Controllers/Api/IncidentUpdateController.php new file mode 100644 index 00000000..84a01d7a --- /dev/null +++ b/app/Http/Controllers/Api/IncidentUpdateController.php @@ -0,0 +1,132 @@ + + */ +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(); + } +} diff --git a/app/Http/Controllers/Dashboard/IncidentController.php b/app/Http/Controllers/Dashboard/IncidentController.php index 5a1284c8..9d707164 100644 --- a/app/Http/Controllers/Dashboard/IncidentController.php +++ b/app/Http/Controllers/Dashboard/IncidentController.php @@ -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 + */ 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'))); + } } diff --git a/app/Http/Controllers/StatusPageController.php b/app/Http/Controllers/StatusPageController.php index 9d29febb..3c8feffe 100644 --- a/app/Http/Controllers/StatusPageController.php +++ b/app/Http/Controllers/StatusPageController.php @@ -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(); }); diff --git a/app/Http/Routes/ApiRoutes.php b/app/Http/Routes/ApiRoutes.php index c288b96b..bb5303db 100644 --- a/app/Http/Routes/ApiRoutes.php +++ b/app/Http/Routes/ApiRoutes.php @@ -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'); diff --git a/app/Http/Routes/Dashboard/IncidentRoutes.php b/app/Http/Routes/Dashboard/IncidentRoutes.php index d63ab4e6..68fa5ecc 100644 --- a/app/Http/Routes/Dashboard/IncidentRoutes.php +++ b/app/Http/Routes/Dashboard/IncidentRoutes.php @@ -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'); }); } } diff --git a/app/Integrations/Core/System.php b/app/Integrations/Core/System.php index 77c55e57..77395af6 100644 --- a/app/Integrations/Core/System.php +++ b/app/Integrations/Core/System.php @@ -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), diff --git a/app/Models/Incident.php b/app/Models/Incident.php index e89364c2..b294a5f7 100644 --- a/app/Models/Incident.php +++ b/app/Models/Incident.php @@ -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. * diff --git a/app/Models/IncidentUpdate.php b/app/Models/IncidentUpdate.php new file mode 100644 index 00000000..d905fb9c --- /dev/null +++ b/app/Models/IncidentUpdate.php @@ -0,0 +1,95 @@ + + */ +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; + } +} diff --git a/app/Presenters/IncidentPresenter.php b/app/Presenters/IncidentPresenter.php index 655be27c..be2f6d88 100644 --- a/app/Presenters/IncidentPresenter.php +++ b/app/Presenters/IncidentPresenter.php @@ -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(), ]); } } diff --git a/app/Presenters/IncidentUpdatePresenter.php b/app/Presenters/IncidentUpdatePresenter.php new file mode 100644 index 00000000..84ae8127 --- /dev/null +++ b/app/Presenters/IncidentUpdatePresenter.php @@ -0,0 +1,173 @@ + + */ +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(), + ]); + } +} diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index de96e720..1df47845 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -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(), diff --git a/database/migrations/2016_03_08_125729_CreateIncidentUpdatesTable.php b/database/migrations/2016_03_08_125729_CreateIncidentUpdatesTable.php new file mode 100644 index 00000000..1f8f23be --- /dev/null +++ b/database/migrations/2016_03_08_125729_CreateIncidentUpdatesTable.php @@ -0,0 +1,46 @@ +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'); + } +} diff --git a/resources/assets/sass/status-page/_status-page.scss b/resources/assets/sass/status-page/_status-page.scss index 1e8360d3..764313d1 100644 --- a/resources/assets/sass/status-page/_status-page.scss +++ b/resources/assets/sass/status-page/_status-page.scss @@ -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: { diff --git a/resources/lang/en/cachet.php b/resources/lang/en/cachet.php index 945afaff..d883a4b6 100644 --- a/resources/lang/en/cachet.php +++ b/resources/lang/en/cachet.php @@ -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', diff --git a/resources/lang/en/dashboard.php b/resources/lang/en/dashboard.php index 4b47968f..d019e51d 100644 --- a/resources/lang/en/dashboard.php +++ b/resources/lang/en/dashboard.php @@ -20,6 +20,7 @@ return [ 'logged' => '{0} There are no incidents, good work.|You have logged one incident.|You have reported :count 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 :incident', + ], // Incident templates 'templates' => [ diff --git a/resources/views/dashboard/incidents/index.blade.php b/resources/views/dashboard/incidents/index.blade.php index d10ccfc6..0e20f418 100644 --- a/resources/views/dashboard/incidents/index.blade.php +++ b/resources/views/dashboard/incidents/index.blade.php @@ -24,13 +24,14 @@ @foreach($incidents as $incident)
- {{ $incident->name }} + {{ $incident->name }} {{ trans_choice('dashboard.incidents.updates', $incident->updates->count()) }} @if($incident->message)

{{ Str::words($incident->message, 5) }}

@endif
diff --git a/resources/views/dashboard/incidents/update.blade.php b/resources/views/dashboard/incidents/update.blade.php new file mode 100644 index 00000000..b1e29028 --- /dev/null +++ b/resources/views/dashboard/incidents/update.blade.php @@ -0,0 +1,64 @@ +@extends('layout.dashboard') + +@section('content') +
+ + + {{ trans('dashboard.incidents.incidents') }} + + > {{ trans('dashboard.incidents.update.title') }} +
+
+
+
+ @include('dashboard.partials.errors') +

{!! trans('dashboard.incidents.update.subtitle', ['incident' => $incident->name]) !!}

+
+ +
+
+
+ + + + +
+
+ +
+ +
+
+
+ + id }}> + +
+
+ + {{ trans('forms.cancel') }} +
+
+
+
+
+
+@stop diff --git a/resources/views/partials/incident.blade.php b/resources/views/partials/incident.blade.php index 0d4ea113..d7ee4b64 100644 --- a/resources/views/partials/incident.blade.php +++ b/resources/views/partials/incident.blade.php @@ -22,4 +22,15 @@
{!! $incident->formattedMessage !!}
+ @if($incident->updates->count()) +
+ @foreach($incident->updates as $update) + + {{ Str::limit($update->raw_message, 20) }} + {{ $update->created_at_diff }} + + + @endforeach +
+ @endif diff --git a/resources/views/partials/incidents.blade.php b/resources/views/partials/incidents.blade.php index 85088084..a668e886 100644 --- a/resources/views/partials/incidents.blade.php +++ b/resources/views/partials/incidents.blade.php @@ -5,8 +5,8 @@
-
- +
+
diff --git a/resources/views/single-incident.blade.php b/resources/views/single-incident.blade.php index 8ab2522b..ff97c933 100644 --- a/resources/views/single-incident.blade.php +++ b/resources/views/single-incident.blade.php @@ -7,22 +7,39 @@ @stop @section('content') -

{{ formatted_date($incident->created_at) }}

+

{{ $incident->name }} {{ formatted_date($incident->created_at) }}

+
+ +
+ {!! $incident->formattedMessage !!} +
+ +@if($incident->updates)
-
+ @foreach ($incident->updates as $index => $update) +
-
- +
+
- @include('partials.incident', ['incident' => $incident, 'with_link' => false]) +
+
+
+ {!! $update->formattedMessage !!} +
+
+ +
+ @endforeach
+@endif @stop diff --git a/tests/Api/IncidentUpdateTest.php b/tests/Api/IncidentUpdateTest.php new file mode 100644 index 00000000..c23137a0 --- /dev/null +++ b/tests/Api/IncidentUpdateTest.php @@ -0,0 +1,102 @@ + + */ +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); + } +} diff --git a/tests/Bus/Commands/IncidentUpdate/AbstractIncidentUpdateCommandTest.php b/tests/Bus/Commands/IncidentUpdate/AbstractIncidentUpdateCommandTest.php new file mode 100644 index 00000000..1bc30dcf --- /dev/null +++ b/tests/Bus/Commands/IncidentUpdate/AbstractIncidentUpdateCommandTest.php @@ -0,0 +1,31 @@ + + */ +abstract class AbstractIncidentUpdateCommandTest extends AbstractTestCase +{ + use EventTrait; + + protected function getEventInterfaces() + { + return [IncidentUpdateEventInterface::class]; + } +} diff --git a/tests/Bus/Commands/IncidentUpdate/RemoveIncidentUpdateCommandTest.php b/tests/Bus/Commands/IncidentUpdate/RemoveIncidentUpdateCommandTest.php new file mode 100644 index 00000000..b59503f1 --- /dev/null +++ b/tests/Bus/Commands/IncidentUpdate/RemoveIncidentUpdateCommandTest.php @@ -0,0 +1,41 @@ + + */ +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; + } +} diff --git a/tests/Bus/Commands/IncidentUpdate/ReportIncidentUpdateCommandTest.php b/tests/Bus/Commands/IncidentUpdate/ReportIncidentUpdateCommandTest.php new file mode 100644 index 00000000..3a81fd18 --- /dev/null +++ b/tests/Bus/Commands/IncidentUpdate/ReportIncidentUpdateCommandTest.php @@ -0,0 +1,52 @@ + + */ +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; + } +} diff --git a/tests/Bus/Commands/IncidentUpdate/UpdateIncidentUpdateCommandTest.php b/tests/Bus/Commands/IncidentUpdate/UpdateIncidentUpdateCommandTest.php new file mode 100644 index 00000000..a78d3845 --- /dev/null +++ b/tests/Bus/Commands/IncidentUpdate/UpdateIncidentUpdateCommandTest.php @@ -0,0 +1,52 @@ + + */ +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; + } +} diff --git a/tests/Bus/Events/IncidentUpdate/AbstractIncidentUpdateEventTestCase.php b/tests/Bus/Events/IncidentUpdate/AbstractIncidentUpdateEventTestCase.php new file mode 100644 index 00000000..b0acab8a --- /dev/null +++ b/tests/Bus/Events/IncidentUpdate/AbstractIncidentUpdateEventTestCase.php @@ -0,0 +1,26 @@ + new IncidentUpdate()]; + $object = new IncidentUpdateWasRemovedEvent($params['update']); + + return compact('params', 'object'); + } +} diff --git a/tests/Bus/Events/IncidentUpdate/IncidentUpdateWasReportedEventTest.php b/tests/Bus/Events/IncidentUpdate/IncidentUpdateWasReportedEventTest.php new file mode 100644 index 00000000..f7d01478 --- /dev/null +++ b/tests/Bus/Events/IncidentUpdate/IncidentUpdateWasReportedEventTest.php @@ -0,0 +1,31 @@ + new IncidentUpdate()]; + $object = new IncidentUpdateWasReportedEvent($params['update']); + + return compact('params', 'object'); + } +} diff --git a/tests/Bus/Events/IncidentUpdate/IncidentUpdateWasUpdatedEventTest.php b/tests/Bus/Events/IncidentUpdate/IncidentUpdateWasUpdatedEventTest.php new file mode 100644 index 00000000..c576beeb --- /dev/null +++ b/tests/Bus/Events/IncidentUpdate/IncidentUpdateWasUpdatedEventTest.php @@ -0,0 +1,31 @@ + new IncidentUpdate()]; + $object = new IncidentUpdateWasUpdatedEvent($params['update']); + + return compact('params', 'object'); + } +} diff --git a/tests/Http/Controllers/StatusPageControllerTest.php b/tests/Http/Controllers/StatusPageControllerTest.php index 6f9a3c68..9c36494a 100644 --- a/tests/Http/Controllers/StatusPageControllerTest.php +++ b/tests/Http/Controllers/StatusPageControllerTest.php @@ -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(); diff --git a/tests/Models/IncidentUpdateTest.php b/tests/Models/IncidentUpdateTest.php new file mode 100644 index 00000000..e66e94d5 --- /dev/null +++ b/tests/Models/IncidentUpdateTest.php @@ -0,0 +1,31 @@ + + */ +class IncidentUpdateTest extends AbstractTestCase +{ + use ValidationTrait; + + public function testValidation() + { + $this->checkRules(new IncidentUpdate()); + } +} From 1e7e9e8fe36868f28446c88dea01bf507177cc32 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 10:40:59 +0100 Subject: [PATCH 02/15] Updated deps --- composer.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.lock b/composer.lock index 017cd1c2..2208cec5 100644 --- a/composer.lock +++ b/composer.lock @@ -325,16 +325,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.19.12", + "version": "3.19.13", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "5d5697c7bc9ab22ce718afb51e5496d0c5cb8778" + "reference": "57b0efed8fcf5d8c854bc1c26cf4a685af9108d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5d5697c7bc9ab22ce718afb51e5496d0c5cb8778", - "reference": "5d5697c7bc9ab22ce718afb51e5496d0c5cb8778", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/57b0efed8fcf5d8c854bc1c26cf4a685af9108d9", + "reference": "57b0efed8fcf5d8c854bc1c26cf4a685af9108d9", "shasum": "" }, "require": { @@ -401,7 +401,7 @@ "s3", "sdk" ], - "time": "2016-09-29 21:07:49" + "time": "2016-10-06 21:17:44" }, { "name": "backup-manager/backup-manager", @@ -1904,16 +1904,16 @@ }, { "name": "jenssegers/date", - "version": "v3.2.5", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/jenssegers/date.git", - "reference": "e4d2b95009354756932cd12e40e3cbb18d20d88c" + "reference": "ce421c34d7d3be22f3afccaae3718cb58f82a488" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jenssegers/date/zipball/e4d2b95009354756932cd12e40e3cbb18d20d88c", - "reference": "e4d2b95009354756932cd12e40e3cbb18d20d88c", + "url": "https://api.github.com/repos/jenssegers/date/zipball/ce421c34d7d3be22f3afccaae3718cb58f82a488", + "reference": "ce421c34d7d3be22f3afccaae3718cb58f82a488", "shasum": "" }, "require": { @@ -1957,7 +1957,7 @@ "time", "translation" ], - "time": "2016-09-14 08:53:13" + "time": "2016-10-06 13:59:12" }, { "name": "jeremeamia/SuperClosure", @@ -4057,16 +4057,16 @@ }, { "name": "twig/twig", - "version": "v1.26.0", + "version": "v1.26.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "81c2b5fd36581370c7731387f05dcdb577050513" + "reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/81c2b5fd36581370c7731387f05dcdb577050513", - "reference": "81c2b5fd36581370c7731387f05dcdb577050513", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a09d8ee17ac1cfea29ed60c83960ad685c6a898d", + "reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d", "shasum": "" }, "require": { @@ -4114,7 +4114,7 @@ "keywords": [ "templating" ], - "time": "2016-10-02 16:19:13" + "time": "2016-10-05 18:57:41" }, { "name": "vlucas/phpdotenv", From c6c439017a96bbf4fc6b8373ede6755d249de988 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:19:47 +0100 Subject: [PATCH 03/15] Only show the incidents headline if we have anything to show --- resources/views/partials/modules/timeline.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/partials/modules/timeline.blade.php b/resources/views/partials/modules/timeline.blade.php index 48ab9cb7..a0440535 100644 --- a/resources/views/partials/modules/timeline.blade.php +++ b/resources/views/partials/modules/timeline.blade.php @@ -1,4 +1,4 @@ -@if($days_to_show > 0) +@if($days_to_show > 0 && $all_incidents)

{{ trans('cachet.incidents.past') }}

@foreach($all_incidents as $date => $incidents) From ca34f5279a8697c17e17961a0b42b6603c3adde1 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:20:47 +0100 Subject: [PATCH 04/15] Move the setup views into own namespace --- app/Http/Controllers/SetupController.php | 2 +- resources/views/{setup.blade.php => setup/index.blade.php} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename resources/views/{setup.blade.php => setup/index.blade.php} (100%) diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index b4f56a93..239c0913 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -125,7 +125,7 @@ class SetupController extends Controller } } - return View::make('setup') + return View::make('setup.index') ->withPageTitle(trans('setup.setup')) ->withCacheDrivers($this->cacheDrivers) ->withMailDrivers($this->mailDrivers) diff --git a/resources/views/setup.blade.php b/resources/views/setup/index.blade.php similarity index 100% rename from resources/views/setup.blade.php rename to resources/views/setup/index.blade.php From f08fdbd5247259732c41ba8861fb08d6d1edd4a7 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:20:58 +0100 Subject: [PATCH 05/15] Don't indent --- resources/views/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php index 7b541d0d..f2534889 100644 --- a/resources/views/index.blade.php +++ b/resources/views/index.blade.php @@ -1,5 +1,5 @@ @extends('layout.master') @section('content') - @modules +@modules @stop From b19d321baecf1697ebc87367f07c354ff61dac86 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:23:04 +0100 Subject: [PATCH 06/15] Added header --- app/Dates/DateFactory.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Dates/DateFactory.php b/app/Dates/DateFactory.php index a853daaa..523628a2 100644 --- a/app/Dates/DateFactory.php +++ b/app/Dates/DateFactory.php @@ -14,6 +14,12 @@ namespace CachetHQ\Cachet\Dates; use DateTimeZone; use Jenssegers\Date\Date; +/** + * This is the date factory class. + * + * @author Graham Campbell + * @author James Brooks + */ class DateFactory { /** From 0d9cd8fad9936a78183c00a3d5edeb9db284cae4 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:24:19 +0100 Subject: [PATCH 07/15] Reword console command descriptions --- app/Console/Commands/BeaconCommand.php | 2 +- app/Console/Commands/DemoMetricPointSeederCommand.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/BeaconCommand.php b/app/Console/Commands/BeaconCommand.php index 03391eb4..0425b048 100644 --- a/app/Console/Commands/BeaconCommand.php +++ b/app/Console/Commands/BeaconCommand.php @@ -33,7 +33,7 @@ class BeaconCommand extends Command * * @var string */ - protected $description = 'Send a beacon to the Cachet server.'; + protected $description = 'Communicate with the Cachet Beacon.'; /** * Execute the console command. diff --git a/app/Console/Commands/DemoMetricPointSeederCommand.php b/app/Console/Commands/DemoMetricPointSeederCommand.php index 7c731b6c..2399a2df 100644 --- a/app/Console/Commands/DemoMetricPointSeederCommand.php +++ b/app/Console/Commands/DemoMetricPointSeederCommand.php @@ -39,7 +39,7 @@ class DemoMetricPointSeederCommand extends Command * * @var string */ - protected $description = 'Seeds the demo Cachet metric with points.'; + protected $description = 'Seeds the demo Cachet metrics with points.'; /** * Execute the console command. From c60c519755ffbd8c82323301e02fa25bf6e999b4 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:25:15 +0100 Subject: [PATCH 08/15] Added console kernel header --- app/Console/Kernel.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c7a2896c..059aa0cc 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -17,6 +17,13 @@ use CachetHQ\Cachet\Console\Commands\DemoSeederCommand; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +/** + * This is the console kernel class. + * + * @author Graham Campbell + * @author Joseph Cohen + * @author James Brooks + */ class Kernel extends ConsoleKernel { /** From bed038653a1465aff3da40b1f158d4342af7bbbd Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:25:40 +0100 Subject: [PATCH 09/15] Standardise @joecohens name in the class headers --- .../Commands/Subscriber/SubscribeSubscriberCommandHandler.php | 2 +- app/Foundation/Providers/AppServiceProvider.php | 2 +- app/Foundation/Providers/ConfigServiceProvider.php | 2 +- app/Http/Controllers/Api/ComponentGroupController.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Bus/Handlers/Commands/Subscriber/SubscribeSubscriberCommandHandler.php b/app/Bus/Handlers/Commands/Subscriber/SubscribeSubscriberCommandHandler.php index 40d9848c..c51ee01e 100644 --- a/app/Bus/Handlers/Commands/Subscriber/SubscribeSubscriberCommandHandler.php +++ b/app/Bus/Handlers/Commands/Subscriber/SubscribeSubscriberCommandHandler.php @@ -22,7 +22,7 @@ use CachetHQ\Cachet\Models\Subscription; * This is the subscribe subscriber command handler. * * @author James Brooks - * @author Joe Cohen + * @author Joseph Cohen * @author Graham Campbell */ class SubscribeSubscriberCommandHandler diff --git a/app/Foundation/Providers/AppServiceProvider.php b/app/Foundation/Providers/AppServiceProvider.php index 9f3a0c21..0c93d6ed 100644 --- a/app/Foundation/Providers/AppServiceProvider.php +++ b/app/Foundation/Providers/AppServiceProvider.php @@ -21,7 +21,7 @@ use Illuminate\Support\Str; * This is the app service provider. * * @author James Brooks - * @author Joe Cohen + * @author Joseph Cohen * @author Graham Campbell */ class AppServiceProvider extends ServiceProvider diff --git a/app/Foundation/Providers/ConfigServiceProvider.php b/app/Foundation/Providers/ConfigServiceProvider.php index ee476356..4de26693 100644 --- a/app/Foundation/Providers/ConfigServiceProvider.php +++ b/app/Foundation/Providers/ConfigServiceProvider.php @@ -23,7 +23,7 @@ use Jenssegers\Date\Date; * * @author James Brooks * @author Graham Campbell - * @author Joe Cohen + * @author Joseph Cohen */ class ConfigServiceProvider extends ServiceProvider { diff --git a/app/Http/Controllers/Api/ComponentGroupController.php b/app/Http/Controllers/Api/ComponentGroupController.php index 096c4973..796eab69 100644 --- a/app/Http/Controllers/Api/ComponentGroupController.php +++ b/app/Http/Controllers/Api/ComponentGroupController.php @@ -26,7 +26,7 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; * * @author James Brooks * @author Graham Campbell - * @author Joe Cohen + * @author Joseph Cohen */ class ComponentGroupController extends AbstractApiController { From f7a8b7ed19cd7bb00de519314871d547d506fe07 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:29:18 +0100 Subject: [PATCH 10/15] Fix setup location --- app/Foundation/Providers/ComposerServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Foundation/Providers/ComposerServiceProvider.php b/app/Foundation/Providers/ComposerServiceProvider.php index 445d2e08..9bb13534 100644 --- a/app/Foundation/Providers/ComposerServiceProvider.php +++ b/app/Foundation/Providers/ComposerServiceProvider.php @@ -39,7 +39,7 @@ class ComposerServiceProvider extends ServiceProvider $factory->composer('*', CurrentUserComposer::class); $factory->composer(['index', 'single-incident', 'subscribe.*', 'signup', 'dashboard.settings.theme', 'emails.*'], ThemeComposer::class); $factory->composer('dashboard.*', DashboardComposer::class); - $factory->composer(['setup', 'dashboard.settings.localization'], TimezoneLocaleComposer::class); + $factory->composer(['setup.*', 'dashboard.settings.localization'], TimezoneLocaleComposer::class); $factory->composer('*', ModuleComposer::class); $factory->composer('partials.modules.components', ComponentsModuleComposer::class); From 985881c5570e3401d3ac134f475a3a6537604226 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:38:36 +0100 Subject: [PATCH 11/15] Fix validation of setup values at step 1 --- app/Http/Controllers/SetupController.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 239c0913..363b6643 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -145,11 +145,17 @@ class SetupController extends Controller $v = Validator::make($postData, $this->rulesStep1); $v->sometimes('env.mail_host', 'required', function ($input) { - return $input->mail_driver === 'smtp'; + return $input->env['mail_driver'] === 'smtp'; }); - $v->sometimes(['env.mail_address', 'env.mail_username', 'env.mail_password'], 'required', function ($input) { - return $input->mail_driver !== 'log'; + $v->sometimes('env.mail_address', 'required', function ($input) { + return $input->env['mail_driver'] !== 'log'; + }); + $v->sometimes('env.mail_username', 'required', function ($input) { + return $input->env['mail_driver'] !== 'log'; + }); + $v->sometimes('env.mail_password', 'required', function ($input) { + return $input->env['mail_driver'] !== 'log'; }); if ($v->passes()) { From a8261d23c08294aff4f9f1f5fdaf41ee35da5dc3 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:39:16 +0100 Subject: [PATCH 12/15] Condense validation rules into array --- app/Http/Controllers/SetupController.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 363b6643..52851236 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -148,13 +148,7 @@ class SetupController extends Controller return $input->env['mail_driver'] === 'smtp'; }); - $v->sometimes('env.mail_address', 'required', function ($input) { - return $input->env['mail_driver'] !== 'log'; - }); - $v->sometimes('env.mail_username', 'required', function ($input) { - return $input->env['mail_driver'] !== 'log'; - }); - $v->sometimes('env.mail_password', 'required', function ($input) { + $v->sometimes(['env.mail_address', 'env.mail_username', 'env.mail_password'], 'required', function ($input) { return $input->env['mail_driver'] !== 'log'; }); From eb470abf092f43a70a1dbedcb7614aa18b548d50 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:47:55 +0100 Subject: [PATCH 13/15] Clean up the timezone listings --- app/Composers/TimezoneLocaleComposer.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/app/Composers/TimezoneLocaleComposer.php b/app/Composers/TimezoneLocaleComposer.php index 799af560..44f012ef 100644 --- a/app/Composers/TimezoneLocaleComposer.php +++ b/app/Composers/TimezoneLocaleComposer.php @@ -16,6 +16,13 @@ use DateTimeZone; use Illuminate\Contracts\Config\Repository; use Illuminate\Contracts\View\View; +/** + * This is the timezone locale composer. + * + * @author Joseph Cohen + * @author James Brooks + * @author Graham Campbell + */ class TimezoneLocaleComposer { /** @@ -57,6 +64,7 @@ class TimezoneLocaleComposer $langs = call_user_func_array('array_merge', $langs); $regions = [ + 'UTC' => DateTimeZone::UTC, 'Africa' => DateTimeZone::AFRICA, 'America' => DateTimeZone::AMERICA, 'Antarctica' => DateTimeZone::ANTARCTICA, @@ -66,7 +74,6 @@ class TimezoneLocaleComposer 'Europe' => DateTimeZone::EUROPE, 'Indian' => DateTimeZone::INDIAN, 'Pacific' => DateTimeZone::PACIFIC, - 'UTC' => DateTimeZone::UTC, ]; $timezones = []; @@ -78,10 +85,12 @@ class TimezoneLocaleComposer // Lets sample the time there right now $time = new DateTime(null, new DateTimeZone($timezone)); - $ampm = $time->format('H') > 12 ? ' ('.$time->format('g:i a').')' : ''; - - // Remove region name and add a sample time - $timezones[$name][$timezone] = substr($timezone, strlen($name) + 1).' - '.$time->format('H:i').$ampm; + if ($timezone !== 'UTC') { + // Remove region name and add a sample time + $timezones[$name][$timezone] = substr($timezone, strlen($name) + 1).' - '.$time->format('H:i'); + } else { + $timezones[$name][$timezone] = 'UTC - '.$time->format('H:i'); + } $timezones[$name] = str_replace('_', ' ', $timezones[$name]); } From b1e23e4145ca3dca129d19f051c7fc50b6498c3f Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 12:48:56 +0100 Subject: [PATCH 14/15] Added missing Arctic timezone --- app/Composers/TimezoneLocaleComposer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Composers/TimezoneLocaleComposer.php b/app/Composers/TimezoneLocaleComposer.php index 44f012ef..a262cc3f 100644 --- a/app/Composers/TimezoneLocaleComposer.php +++ b/app/Composers/TimezoneLocaleComposer.php @@ -68,6 +68,7 @@ class TimezoneLocaleComposer 'Africa' => DateTimeZone::AFRICA, 'America' => DateTimeZone::AMERICA, 'Antarctica' => DateTimeZone::ANTARCTICA, + 'Arctic' => DateTimeZone::ARCTIC, 'Asia' => DateTimeZone::ASIA, 'Atlantic' => DateTimeZone::ATLANTIC, 'Australia' => DateTimeZone::AUSTRALIA, From 42713f51e37b7c510650c8f0c47da214854cdeba Mon Sep 17 00:00:00 2001 From: James Brooks Date: Fri, 7 Oct 2016 13:51:59 +0100 Subject: [PATCH 15/15] Fix some docblocks --- app/Composers/AppComposer.php | 6 ++++++ app/Composers/CurrentUserComposer.php | 7 +++++++ app/Composers/DashboardComposer.php | 6 ++++++ app/Composers/ModuleComposer.php | 5 +++++ app/Composers/ThemeComposer.php | 6 ++++++ app/Http/Controllers/SetupController.php | 7 +++++++ resources/views/setup/index.blade.php | 6 +++--- 7 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/Composers/AppComposer.php b/app/Composers/AppComposer.php index f1f48d01..c136124c 100644 --- a/app/Composers/AppComposer.php +++ b/app/Composers/AppComposer.php @@ -16,6 +16,12 @@ use GrahamCampbell\Markdown\Facades\Markdown; use Illuminate\Contracts\Config\Repository; use Illuminate\Contracts\View\View; +/** + * This is the app composer. + * + * @author James Brooks + * @author Graham Campbell + */ class AppComposer { /** diff --git a/app/Composers/CurrentUserComposer.php b/app/Composers/CurrentUserComposer.php index 7543ad8d..b90f6272 100644 --- a/app/Composers/CurrentUserComposer.php +++ b/app/Composers/CurrentUserComposer.php @@ -14,6 +14,13 @@ namespace CachetHQ\Cachet\Composers; use Illuminate\Contracts\View\View; use Illuminate\Support\Facades\Auth; +/** + * This is the current user composer. + * + * @author Joseph Cohen + * @author James Brooks + * @author Graham Campbell + */ class CurrentUserComposer { /** diff --git a/app/Composers/DashboardComposer.php b/app/Composers/DashboardComposer.php index e36c9fd0..e30145f0 100644 --- a/app/Composers/DashboardComposer.php +++ b/app/Composers/DashboardComposer.php @@ -17,6 +17,12 @@ use CachetHQ\Cachet\Models\IncidentTemplate; use CachetHQ\Cachet\Models\Subscriber; use Illuminate\Contracts\View\View; +/** + * This is the dashboard composer. + * + * @author James Brooks + * @author Graham Campbell + */ class DashboardComposer { /** diff --git a/app/Composers/ModuleComposer.php b/app/Composers/ModuleComposer.php index f15c186d..b758401b 100644 --- a/app/Composers/ModuleComposer.php +++ b/app/Composers/ModuleComposer.php @@ -15,6 +15,11 @@ use CachetHQ\Cachet\Services\Modules\Manager; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\View; +/** + * This is the module composer. + * + * @author Connor S. Parks + */ class ModuleComposer { /** diff --git a/app/Composers/ThemeComposer.php b/app/Composers/ThemeComposer.php index 191b8946..b4c6c07e 100644 --- a/app/Composers/ThemeComposer.php +++ b/app/Composers/ThemeComposer.php @@ -14,6 +14,12 @@ namespace CachetHQ\Cachet\Composers; use Illuminate\Contracts\Config\Repository; use Illuminate\Contracts\View\View; +/** + * This is the theme composer. + * + * @author James Brooks + * @author Graham Campbell + */ class ThemeComposer { /** diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 52851236..5c27b0c1 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -25,6 +25,13 @@ use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\View; +/** + * This is the setup controller. + * + * @author James Brooks + * @author Graham Campbell + * @author Joseph Cohen + */ class SetupController extends Controller { /** diff --git a/resources/views/setup/index.blade.php b/resources/views/setup/index.blade.php index df2db0e3..e6fd6009 100644 --- a/resources/views/setup/index.blade.php +++ b/resources/views/setup/index.blade.php @@ -35,7 +35,7 @@ @if($errors->has('env.cache_driver')) @@ -47,7 +47,7 @@ @if($errors->has('env.session_driver')) @@ -59,7 +59,7 @@ @if($errors->has('env.mail_driver'))