diff --git a/app/Bus/Commands/Incident/ReportIncidentCommand.php b/app/Bus/Commands/Incident/ReportIncidentCommand.php index 4e1e972f..67158bfc 100644 --- a/app/Bus/Commands/Incident/ReportIncidentCommand.php +++ b/app/Bus/Commands/Incident/ReportIncidentCommand.php @@ -62,6 +62,13 @@ final class ReportIncidentCommand */ public $notify; + /** + * Whether to stick the incident on top. + * + * @var bool + */ + public $stickied; + /** * The date at which the incident occurred. * @@ -96,6 +103,7 @@ final class ReportIncidentCommand 'component_id' => 'int|required_with:component_status', 'component_status' => 'int|min:1|max:4|required_with:component_id', 'notify' => 'bool', + 'stickied' => 'bool', 'incident_date' => 'string', 'template' => 'string', ]; @@ -110,13 +118,14 @@ final class ReportIncidentCommand * @param int $component_id * @param int $component_status * @param bool $notify + * @param bool $stickied * @param string|null $incident_date * @param string|null $template * @param array|null $template_vars * * @return void */ - public function __construct($name, $status, $message, $visible, $component_id, $component_status, $notify, $incident_date, $template, array $template_vars = null) + public function __construct($name, $status, $message, $visible, $component_id, $component_status, $notify, $stickied, $incident_date, $template, array $template_vars = null) { $this->name = $name; $this->status = $status; @@ -125,6 +134,7 @@ final class ReportIncidentCommand $this->component_id = $component_id; $this->component_status = $component_status; $this->notify = $notify; + $this->stickied = $stickied; $this->incident_date = $incident_date; $this->template = $template; $this->template_vars = $template_vars; diff --git a/app/Bus/Commands/Incident/UpdateIncidentCommand.php b/app/Bus/Commands/Incident/UpdateIncidentCommand.php index 2d5f2c73..928e037a 100644 --- a/app/Bus/Commands/Incident/UpdateIncidentCommand.php +++ b/app/Bus/Commands/Incident/UpdateIncidentCommand.php @@ -71,6 +71,13 @@ final class UpdateIncidentCommand */ public $notify; + /** + * Whether to stick the incident on top. + * + * @var bool + */ + public $stickied; + /** * The date that the incident occurred on. * @@ -105,6 +112,7 @@ final class UpdateIncidentCommand 'component_id' => 'int', 'component_status' => 'int|min:1|max:4|required_with:component_id', 'notify' => 'bool', + 'stickied' => 'bool', 'template' => 'string', ]; @@ -119,13 +127,14 @@ final class UpdateIncidentCommand * @param int $component_id * @param int $component_status * @param bool $notify + * @param bool $stickied * @param string|null $incident_date * @param string|null $template * @param array|null $template_vars * * @return void */ - public function __construct(Incident $incident, $name, $status, $message, $visible, $component_id, $component_status, $notify, $incident_date, $template, array $template_vars = null) + public function __construct(Incident $incident, $name, $status, $message, $visible, $component_id, $component_status, $notify, $stickied, $incident_date, $template, array $template_vars = null) { $this->incident = $incident; $this->name = $name; @@ -135,6 +144,7 @@ final class UpdateIncidentCommand $this->component_id = $component_id; $this->component_status = $component_status; $this->notify = $notify; + $this->stickied = $stickied; $this->incident_date = $incident_date; $this->template = $template; $this->template_vars = $template_vars; diff --git a/app/Bus/Handlers/Commands/Incident/ReportIncidentCommandHandler.php b/app/Bus/Handlers/Commands/Incident/ReportIncidentCommandHandler.php index 61170748..cae91633 100644 --- a/app/Bus/Handlers/Commands/Incident/ReportIncidentCommandHandler.php +++ b/app/Bus/Handlers/Commands/Incident/ReportIncidentCommandHandler.php @@ -65,9 +65,10 @@ class ReportIncidentCommandHandler public function handle(ReportIncidentCommand $command) { $data = [ - 'name' => $command->name, - 'status' => $command->status, - 'visible' => $command->visible, + 'name' => $command->name, + 'status' => $command->status, + 'visible' => $command->visible, + 'stickied' => $command->stickied, ]; if ($command->template) { diff --git a/app/Bus/Handlers/Commands/Incident/ReportMaintenanceCommandHandler.php b/app/Bus/Handlers/Commands/Incident/ReportMaintenanceCommandHandler.php index 4126909d..b63b767a 100644 --- a/app/Bus/Handlers/Commands/Incident/ReportMaintenanceCommandHandler.php +++ b/app/Bus/Handlers/Commands/Incident/ReportMaintenanceCommandHandler.php @@ -54,6 +54,7 @@ class ReportMaintenanceCommandHandler 'scheduled_at' => $scheduledAt, 'status' => 0, 'visible' => 1, + 'stickied' => false, ]); $maintenanceEvent->notify = (bool) $command->notify; diff --git a/app/Bus/Handlers/Commands/Incident/UpdateIncidentCommandHandler.php b/app/Bus/Handlers/Commands/Incident/UpdateIncidentCommandHandler.php index 53bc3f7f..8001cf6a 100644 --- a/app/Bus/Handlers/Commands/Incident/UpdateIncidentCommandHandler.php +++ b/app/Bus/Handlers/Commands/Incident/UpdateIncidentCommandHandler.php @@ -107,6 +107,7 @@ class UpdateIncidentCommandHandler 'status' => $command->status, 'message' => $command->message, 'visible' => $command->visible, + 'stickied' => $command->stickied, 'component_id' => $command->component_id, 'component_status' => $command->component_status, 'notify' => $command->notify, diff --git a/app/Composers/Modules/StickiedComposer.php b/app/Composers/Modules/StickiedComposer.php new file mode 100644 index 00000000..dfb18659 --- /dev/null +++ b/app/Composers/Modules/StickiedComposer.php @@ -0,0 +1,41 @@ + + * @author Connor S. Parks + * @author Antoine Girard + */ +class StickiedComposer +{ + /** + * Index page view composer. + * + * @param \Illuminate\Contracts\View\View $view + * + * @return void + */ + public function compose(View $view) + { + $stickiedIncidents = Incident::stickied()->orderBy('scheduled_at', 'desc')->orderBy('created_at', 'desc')->get()->groupBy(function (Incident $incident) { + return app(DateFactory::class)->make($incident->is_scheduled ? $incident->scheduled_at : $incident->created_at)->toDateString(); + }); + $view->withStickiedIncidents($stickiedIncidents); + } +} diff --git a/app/Console/Commands/DemoSeederCommand.php b/app/Console/Commands/DemoSeederCommand.php index 8048891c..2eb88353 100644 --- a/app/Console/Commands/DemoSeederCommand.php +++ b/app/Console/Commands/DemoSeederCommand.php @@ -205,6 +205,7 @@ EINCIDENT; 'component_id' => 0, 'scheduled_at' => null, 'visible' => 1, + 'stickied' => false, ], [ 'name' => 'Awesome', @@ -213,6 +214,7 @@ EINCIDENT; 'component_id' => 0, 'scheduled_at' => null, 'visible' => 1, + 'stickied' => false, ], [ 'name' => 'Monitoring the fix', @@ -221,6 +223,7 @@ EINCIDENT; 'component_id' => 0, 'scheduled_at' => null, 'visible' => 1, + 'stickied' => false, ], [ 'name' => 'Update', @@ -229,6 +232,7 @@ EINCIDENT; 'component_id' => 0, 'scheduled_at' => null, 'visible' => 1, + 'stickied' => false, ], [ 'name' => 'Test Incident', @@ -237,6 +241,7 @@ EINCIDENT; 'component_id' => 0, 'scheduled_at' => null, 'visible' => 1, + 'stickied' => false, ], [ 'name' => 'Investigating the API', @@ -245,6 +250,16 @@ EINCIDENT; 'component_id' => 1, 'scheduled_at' => null, 'visible' => 1, + 'stickied' => false, + ], + [ + 'name' => 'Stickied to the top', + 'message' => 'Will be forever hanged here.', + 'status' => 1, + 'component_id' => 1, + 'scheduled_at' => null, + 'visible' => 1, + 'stickied' => true, ], ]; diff --git a/app/Foundation/Providers/ComposerServiceProvider.php b/app/Foundation/Providers/ComposerServiceProvider.php index 876903d3..445d2e08 100644 --- a/app/Foundation/Providers/ComposerServiceProvider.php +++ b/app/Foundation/Providers/ComposerServiceProvider.php @@ -19,6 +19,7 @@ use CachetHQ\Cachet\Composers\Modules\ComponentsComposer as ComponentsModuleComp use CachetHQ\Cachet\Composers\Modules\MetricsComposer as MetricsModuleComposer; use CachetHQ\Cachet\Composers\Modules\ScheduledComposer as ScheduledModuleComposer; use CachetHQ\Cachet\Composers\Modules\StatusComposer as StatusModuleComposer; +use CachetHQ\Cachet\Composers\Modules\StickiedComposer as StickiedModuleComposer; use CachetHQ\Cachet\Composers\Modules\TimelineComposer as TimelineModuleComposer; use CachetHQ\Cachet\Composers\ThemeComposer; use CachetHQ\Cachet\Composers\TimezoneLocaleComposer; @@ -43,6 +44,7 @@ class ComposerServiceProvider extends ServiceProvider $factory->composer('*', ModuleComposer::class); $factory->composer('partials.modules.components', ComponentsModuleComposer::class); $factory->composer('partials.modules.metrics', MetricsModuleComposer::class); + $factory->composer('partials.modules.stickied', StickiedModuleComposer::class); $factory->composer('partials.modules.scheduled', ScheduledModuleComposer::class); $factory->composer('partials.modules.status', StatusModuleComposer::class); $factory->composer('partials.modules.timeline', TimelineModuleComposer::class); diff --git a/app/Foundation/Providers/ModuleServiceProvider.php b/app/Foundation/Providers/ModuleServiceProvider.php index c997d1e7..895f3d58 100644 --- a/app/Foundation/Providers/ModuleServiceProvider.php +++ b/app/Foundation/Providers/ModuleServiceProvider.php @@ -28,6 +28,7 @@ class ModuleServiceProvider extends ServiceProvider ['group' => 'status', 'partial' => 'partials.modules.status'], ['group' => 'components', 'partial' => 'partials.modules.components'], ['group' => 'metrics', 'partial' => 'partials.modules.metrics'], + ['group' => 'stickied', 'partial' => 'partials.modules.stickied'], ['group' => 'scheduled', 'partial' => 'partials.modules.scheduled'], ['group' => 'timeline', 'partial' => 'partials.modules.timeline'], ], @@ -45,7 +46,8 @@ class ModuleServiceProvider extends ServiceProvider 'components' => 30000, 'metrics' => 40000, 'scheduled' => 50000, - 'timeline' => 60000, + 'stickied' => 60000, + 'timeline' => 70000, ], ]; diff --git a/app/Http/Controllers/Api/IncidentController.php b/app/Http/Controllers/Api/IncidentController.php index 3a3066e6..0d7c8993 100644 --- a/app/Http/Controllers/Api/IncidentController.php +++ b/app/Http/Controllers/Api/IncidentController.php @@ -75,6 +75,7 @@ class IncidentController extends AbstractApiController Binput::get('component_id'), Binput::get('component_status'), Binput::get('notify', true), + Binput::get('stickied', false), Binput::get('created_at'), Binput::get('template'), Binput::get('vars') @@ -105,6 +106,7 @@ class IncidentController extends AbstractApiController Binput::get('component_id'), Binput::get('component_status'), Binput::get('notify', true), + Binput::get('stickied', false), Binput::get('created_at'), Binput::get('template'), Binput::get('vars') diff --git a/app/Http/Controllers/Dashboard/IncidentController.php b/app/Http/Controllers/Dashboard/IncidentController.php index 80b5bd50..5a1284c8 100644 --- a/app/Http/Controllers/Dashboard/IncidentController.php +++ b/app/Http/Controllers/Dashboard/IncidentController.php @@ -115,6 +115,7 @@ class IncidentController extends Controller Binput::get('component_id'), Binput::get('component_status'), Binput::get('notify', false), + Binput::get('stickied', false), Binput::get('created_at'), null, null @@ -240,6 +241,7 @@ class IncidentController extends Controller Binput::get('component_id'), Binput::get('component_status'), Binput::get('notify', true), + Binput::get('stickied', false), Binput::get('created_at'), null, null diff --git a/app/Models/Incident.php b/app/Models/Incident.php index 19fe678f..e89364c2 100644 --- a/app/Models/Incident.php +++ b/app/Models/Incident.php @@ -32,6 +32,7 @@ class Incident extends Model implements HasPresenter */ protected $casts = [ 'visible' => 'int', + 'stickied' => 'int', 'scheduled_at' => 'date', 'deleted_at' => 'date', ]; @@ -46,6 +47,7 @@ class Incident extends Model implements HasPresenter 'name', 'status', 'visible', + 'stickied', 'message', 'scheduled_at', 'created_at', @@ -62,6 +64,7 @@ class Incident extends Model implements HasPresenter 'name' => 'required', 'status' => 'required|int', 'visible' => 'required|bool', + 'stickied' => 'bool', 'message' => 'required', ]; @@ -76,6 +79,7 @@ class Incident extends Model implements HasPresenter 'name', 'status', 'visible', + 'stickied', ]; /** @@ -88,6 +92,7 @@ class Incident extends Model implements HasPresenter 'name', 'status', 'visible', + 'stickied', 'message', ]; @@ -113,6 +118,18 @@ class Incident extends Model implements HasPresenter return $query->where('visible', 1); } + /** + * Finds all stickied incidents. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeStickied(Builder $query) + { + return $query->where('stickied', true); + } + /** * Finds all scheduled incidents (maintenance). * diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 15862fa3..3c70bdbe 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -40,10 +40,11 @@ $factory->define(ComponentGroup::class, function ($faker) { $factory->define(Incident::class, function ($faker) { return [ - 'name' => $faker->sentence(), - 'message' => $faker->paragraph(), - 'status' => random_int(1, 4), - 'visible' => 1, + 'name' => $faker->sentence(), + 'message' => $faker->paragraph(), + 'status' => random_int(1, 4), + 'visible' => 1, + 'stickied' => false, ]; }); diff --git a/database/migrations/2016_09_04_100000_AlterTableIncidentsAddStickiedColumn.php b/database/migrations/2016_09_04_100000_AlterTableIncidentsAddStickiedColumn.php new file mode 100644 index 00000000..056c21e1 --- /dev/null +++ b/database/migrations/2016_09_04_100000_AlterTableIncidentsAddStickiedColumn.php @@ -0,0 +1,39 @@ +boolean('stickied')->after('visible')->default(false); + + $table->index('stickied'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('incidents', function (Blueprint $table) { + $table->dropColumn('stickied'); + }); + } +} diff --git a/resources/lang/en/cachet.php b/resources/lang/en/cachet.php index fef9dc9c..945afaff 100644 --- a/resources/lang/en/cachet.php +++ b/resources/lang/en/cachet.php @@ -30,6 +30,7 @@ return [ 'past' => 'Past Incidents', 'previous_week' => 'Previous week', 'next_week' => 'Next week', + 'stickied' => 'Stickied Incidents', 'scheduled' => 'Scheduled Maintenance', 'scheduled_at' => ', scheduled :timestamp', 'status' => [ diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php index 1eb9246e..fac80593 100644 --- a/resources/lang/en/forms.php +++ b/resources/lang/en/forms.php @@ -53,6 +53,9 @@ return [ 'incident_time' => 'When did this incident occur?', 'notify_subscribers' => 'Notify subscribers?', 'visibility' => 'Incident Visibility', + 'stick_status' => 'Stick Incident', + 'stickied' => 'Stickied', + 'not_stickied' => 'Not Stickied', 'public' => 'Viewable by public', 'logged_in_only' => 'Only visible to logged in users', 'templates' => [ diff --git a/resources/views/dashboard/incidents/add.blade.php b/resources/views/dashboard/incidents/add.blade.php index f799042f..09c8c47e 100644 --- a/resources/views/dashboard/incidents/add.blade.php +++ b/resources/views/dashboard/incidents/add.blade.php @@ -62,6 +62,13 @@ +
+ + +
@if(!$components_in_groups->isEmpty() || !$components_out_groups->isEmpty())
diff --git a/resources/views/dashboard/incidents/edit.blade.php b/resources/views/dashboard/incidents/edit.blade.php index d42d6520..f8beec8d 100644 --- a/resources/views/dashboard/incidents/edit.blade.php +++ b/resources/views/dashboard/incidents/edit.blade.php @@ -51,6 +51,13 @@
+
+ + +
@if($incident->component)
diff --git a/resources/views/partials/modules/stickied.blade.php b/resources/views/partials/modules/stickied.blade.php new file mode 100644 index 00000000..1b36cb3b --- /dev/null +++ b/resources/views/partials/modules/stickied.blade.php @@ -0,0 +1,8 @@ +@if(!$stickied_incidents->isEmpty()) +
+

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

+ @foreach($stickied_incidents as $date => $incidents) + @include('partials.incidents', [compact($date), compact($incidents)]) + @endforeach +
+@endif diff --git a/tests/Api/IncidentTest.php b/tests/Api/IncidentTest.php index f0ebafc3..dfd260a8 100644 --- a/tests/Api/IncidentTest.php +++ b/tests/Api/IncidentTest.php @@ -55,10 +55,11 @@ class IncidentTest extends AbstractApiTestCase $this->beUser(); $this->post('/api/v1/incidents', [ - 'name' => 'Foo', - 'message' => 'Lorem ipsum dolor sit amet', - 'status' => 1, - 'visible' => 1, + 'name' => 'Foo', + 'message' => 'Lorem ipsum dolor sit amet', + 'status' => 1, + 'visible' => 1, + 'stickied' => false, ]); $this->seeJson(['name' => 'Foo']); $this->assertResponseOk(); @@ -77,6 +78,7 @@ class IncidentTest extends AbstractApiTestCase 'component_id' => $component->id, 'component_status' => 1, 'visible' => 1, + 'stickied' => false, ]); $this->seeJson(['name' => 'Foo']); $this->assertResponseOk(); @@ -91,6 +93,7 @@ class IncidentTest extends AbstractApiTestCase 'name' => 'Foo', 'status' => 1, 'visible' => 1, + 'stickied' => false, 'template' => $template->slug, 'vars' => [ 'name' => 'Foo', diff --git a/tests/Bus/Commands/Incident/ReportIncidentCommandTest.php b/tests/Bus/Commands/Incident/ReportIncidentCommandTest.php index 0b66aa72..09ac5c4c 100644 --- a/tests/Bus/Commands/Incident/ReportIncidentCommandTest.php +++ b/tests/Bus/Commands/Incident/ReportIncidentCommandTest.php @@ -36,6 +36,7 @@ class ReportIncidentCommandTest extends AbstractTestCase 'component_id' => 1, 'component_status' => 1, 'notify' => false, + 'stickied' => false, 'incident_date' => null, 'template' => null, 'template_vars' => null, @@ -49,6 +50,7 @@ class ReportIncidentCommandTest extends AbstractTestCase $params['component_id'], $params['component_status'], $params['notify'], + $params['stickied'], $params['incident_date'], $params['template'], $params['template_vars'] diff --git a/tests/Bus/Commands/Incident/UpdateIncidentCommandTest.php b/tests/Bus/Commands/Incident/UpdateIncidentCommandTest.php index 6a4926e5..1957dea2 100644 --- a/tests/Bus/Commands/Incident/UpdateIncidentCommandTest.php +++ b/tests/Bus/Commands/Incident/UpdateIncidentCommandTest.php @@ -38,6 +38,7 @@ class UpdateIncidentCommandTest extends AbstractTestCase 'component_id' => 1, 'component_status' => 1, 'notify' => false, + 'stickied' => false, 'incident_date' => null, 'template' => null, 'template_vars' => null, @@ -52,6 +53,7 @@ class UpdateIncidentCommandTest extends AbstractTestCase $params['component_id'], $params['component_status'], $params['notify'], + $params['stickied'], $params['incident_date'], $params['template'], $params['template_vars']