Merge pull request #485 from cachethq/schedules

Added scheduled maintenance. Closes #112 (again)
This commit is contained in:
Graham Campbell
2015-02-28 20:51:20 +00:00
36 changed files with 1034 additions and 115 deletions

View File

@@ -25,7 +25,7 @@ class IndexComposer
if (Component::notStatus(1)->count() === 0) {
// If all our components are ok, do we have any non-fixed incidents?
$incidents = Incident::orderBy('created_at', 'desc')->get();
$incidents = Incident::notScheduled()->orderBy('created_at', 'desc')->get();
$incidentCount = $incidents->count();
if ($incidentCount === 0 || ($incidentCount >= 1 && (int) $incidents->first()->status === 4)) {

View File

@@ -12,6 +12,39 @@ use Illuminate\Support\Facades\View;
class DashIncidentController extends Controller
{
/**
* Stores the sub-sidebar tree list.
*
* @var array
*/
protected $subMenu = [];
/**
* Creates a new DashIncidentController instance.
*
* @return \CachetHQ\Cachet\Http\Controllers\DashScheduleController
*/
public function __construct()
{
$this->subMenu = [
'incidents' => [
'title' => trans('dashboard.incidents.incidents'),
'url' => route('dashboard.incidents'),
'icon' => 'ion-android-checkmark-circle',
'active' => true,
],
'schedule' => [
'title' => trans('dashboard.schedule.schedule'),
'url' => route('dashboard.schedule'),
'icon' => 'ion-android-calendar',
'active' => false,
],
];
View::share('subMenu', $this->subMenu);
View::share('subTitle', trans('dashboard.incidents.title'));
}
/**
* Shows the incidents view.
*
@@ -19,7 +52,7 @@ class DashIncidentController extends Controller
*/
public function showIncidents()
{
$incidents = Incident::orderBy('created_at', 'desc')->get();
$incidents = Incident::notScheduled()->orderBy('created_at', 'desc')->get();
return View::make('dashboard.incidents.index')->with([
'pageTitle' => trans('dashboard.incidents.incidents').' - '.trans('dashboard.dashboard'),

View File

@@ -0,0 +1,228 @@
<?php
namespace CachetHQ\Cachet\Http\Controllers;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\IncidentTemplate;
use Carbon\Carbon;
use GrahamCampbell\Binput\Facades\Binput;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View;
use Illuminate\Support\MessageBag;
class DashScheduleController extends Controller
{
/**
* Stores the sub-sidebar tree list.
*
* @var array
*/
protected $subMenu = [];
/**
* Creates a new DashScheduleController instance.
*
* @return \CachetHQ\Cachet\Http\Controllers\DashScheduleController
*/
public function __construct()
{
// TODO: Remove this from DashIncidentController, so it's shared?
$this->subMenu = [
'incidents' => [
'title' => trans('dashboard.incidents.incidents'),
'url' => route('dashboard.incidents'),
'icon' => 'ion-android-checkmark-circle',
'active' => false,
],
'schedule' => [
'title' => trans('dashboard.schedule.schedule'),
'url' => route('dashboard.schedule'),
'icon' => 'ion-android-calendar',
'active' => true,
],
];
View::share('subMenu', $this->subMenu);
View::share('subTitle', trans('dashboard.incidents.title'));
}
/**
* Lists all scheduled maintenance.
*
* @return \Illuminate\View\View
*/
public function showIndex()
{
$schedule = Incident::scheduled()->orderBy('created_at')->get();
return View::make('dashboard.schedule.index')->withSchedule($schedule);
}
/**
* Shows the add schedule maintenance form.
*
* @return \Illuminate\View\View
*/
public function showAddSchedule()
{
$incidentTemplates = IncidentTemplate::all();
return View::make('dashboard.schedule.add')->with([
'incidentTemplates' => $incidentTemplates,
]);
}
/**
* Creates a new scheduled maintenance "incident".
*
* @return \Illuminate\Http\RedirectResponse
*/
public function addScheduleAction()
{
$scheduleData = Binput::get('incident');
// Parse the schedule date.
$scheduledAt = Carbon::createFromFormat('d/m/Y H:i', $scheduleData['scheduled_at']);
if ($scheduledAt->isPast()) {
$messageBag = new MessageBag();
$messageBag->add('scheduled_at', trans('validation.date', [
'attribute' => 'scheduled time you supplied',
]));
return Redirect::back()->withErrors($messageBag);
}
$scheduleData['scheduled_at'] = $scheduledAt;
// Bypass the incident.status field.
$scheduleData['status'] = 0;
$incident = Incident::create($scheduleData);
if (! $incident->isValid()) {
segment_track('Dashboard', [
'event' => 'Created Scheduled Maintenance',
'success' => false,
]);
return Redirect::back()->withInput(Binput::all())
->with('success', sprintf(
'<strong>%s</strong> %s',
trans('dashboard.notifications.whoops'),
trans('dashboard.schedule.add.failure')
))
->with('errors', $incident->getErrors());
}
segment_track('Dashboard', [
'event' => 'Created Scheduled Maintenance',
'success' => true,
]);
$successMsg = sprintf(
'<strong>%s</strong> %s',
trans('dashboard.notifications.awesome'),
trans('dashboard.schedule.add.success')
);
return Redirect::back()->with('success', $successMsg);
}
/**
* Shows the edit schedule maintenance form.
*
* @param \CachetHQ\Cachet\Models\Incident $schedule
*
* @return \Illuminate\View\View
*/
public function showEditSchedule(Incident $schedule)
{
$incidentTemplates = IncidentTemplate::all();
return View::make('dashboard.schedule.edit')->with([
'incidentTemplates' => $incidentTemplates,
'schedule' => $schedule,
]);
}
/**
* Updates the given incident.
*
* @param \CachetHQ\Cachet\Models\Incident $schedule
*
* @return \Illuminate\Http\RedirectResponse
*/
public function editScheduleAction(Incident $schedule)
{
$scheduleData = Binput::get('incident');
// Parse the schedule date.
$scheduledAt = Carbon::createFromFormat('d/m/Y H:i', $scheduleData['scheduled_at']);
if ($scheduledAt->isPast()) {
$messageBag = new MessageBag();
$messageBag->add('scheduled_at', trans('validation.date', [
'attribute' => 'scheduled time you supplied',
]));
return Redirect::back()->withErrors($messageBag);
}
$scheduleData['scheduled_at'] = $scheduledAt;
// Bypass the incident.status field.
$scheduleData['status'] = 0;
$schedule->update($scheduleData);
if (! $schedule->isValid()) {
segment_track('Dashboard', [
'event' => 'Edited Schedule',
'success' => false,
]);
return Redirect::back()->withInput(Binput::all())
->with('title', sprintf(
'<strong>%s</strong> %s',
trans('dashboard.notifications.awesome'),
trans('dashboard.schedule.edit.failure')
))
->with('errors', $schedule->getErrors());
}
segment_track('Dashboard', [
'event' => 'Edited Schedule',
'success' => true,
]);
$successMsg = sprintf(
'<strong>%s</strong> %s',
trans('dashboard.notifications.awesome'),
trans('dashboard.schedule.edit.success')
);
return Redirect::to('dashboard/schedule')->with('success', $successMsg);
}
/**
* Deletes a given schedule.
*
* @param \CachetHQ\Cachet\Models\Incident $schedule
*
* @return \Illuminate\Http\RedirectResponse
*/
public function deleteScheduleAction(Incident $schedule)
{
if ($schedule->delete()) {
return Redirect::back()->with('success', sprintf(
'<strong>%s</strong> %s',
trans('dashboard.notifications.awesome'),
trans('dashboard.schedule.delete.success')
));
}
return Redirect::back()->with('warning', sprintf(
'<strong>%s</strong> %s',
trans('dashboard.notifications.whoops'),
trans('dashboard.schedule.delete.failure')
));
}
}

View File

@@ -73,10 +73,12 @@ class HomeController extends Controller
$metrics = Metric::where('display_chart', 1)->get();
}
$scheduledMaintenance = Incident::scheduled()->orderBy('scheduled_at')->get();
foreach (range(0, $incidentDays) as $i) {
$date = $startDate->copy()->subDays($i);
$incidents = Incident::whereBetween('created_at', [
$incidents = Incident::notScheduled()->whereBetween('created_at', [
$date->format('Y-m-d').' 00:00:00',
$date->format('Y-m-d').' 23:59:59',
])->orderBy('created_at', 'desc')->get();
@@ -88,15 +90,16 @@ class HomeController extends Controller
}
return View::make('index', [
'components' => $components,
'displayMetrics' => $displayMetrics,
'metrics' => $metrics,
'allIncidents' => $allIncidents,
'pageTitle' => Setting::get('app_name'),
'aboutApp' => Markdown::render(Setting::get('app_about')),
'canPageForward' => (bool) $today->gt($startDate),
'previousDate' => $startDate->copy()->subWeek()->subDay()->toDateString(),
'nextDate' => $startDate->copy()->addWeek()->addDay()->toDateString(),
'components' => $components,
'displayMetrics' => $displayMetrics,
'metrics' => $metrics,
'allIncidents' => $allIncidents,
'scheduledMaintenance' => $scheduledMaintenance,
'pageTitle' => Setting::get('app_name'),
'aboutApp' => Markdown::render(Setting::get('app_about')),
'canPageForward' => (bool) $today->gt($startDate),
'previousDate' => $startDate->copy()->subWeek()->subDay()->toDateString(),
'nextDate' => $startDate->copy()->addWeek()->addDay()->toDateString(),
]);
}
}

View File

@@ -43,7 +43,7 @@ class Incident extends Model implements TransformableInterface, PresenterInterfa
*
* @var string[]
*/
protected $fillable = ['user_id', 'component_id', 'name', 'status', 'message'];
protected $fillable = ['user_id', 'component_id', 'name', 'status', 'message', 'scheduled_at'];
/**
* The accessors to append to the model's serialized form.
@@ -52,6 +52,39 @@ class Incident extends Model implements TransformableInterface, PresenterInterfa
*/
protected $appends = ['humanStatus'];
/**
* The attributes that should be mutated to dates.
*
* @var string[]
*/
protected $dates = ['scheduled_at', 'deleted_at'];
/**
* Finds all scheduled incidents (maintenance).
*
* @param \Illuminate\Database\Eloquent\Builder $query
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeScheduled($query)
{
return $query->where('status', 0)->whereRaw('scheduled_at >= NOW()');
}
/**
* Finds all non-scheduled incidents.
*
* @param \Illuminate\Database\Eloquent\Builder $query
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeNotScheduled($query)
{
return $query->where(function ($query) {
return $query->where('scheduled_at', '0000-00-00 00:00:00')->orWhereRaw('scheduled_at <= NOW()');
});
}
/**
* Get presenter class.
*
@@ -84,6 +117,16 @@ class Incident extends Model implements TransformableInterface, PresenterInterfa
return $statuses[$this->status];
}
/**
* Returns whether the "incident" is scheduled or not.
*
* @return bool
*/
public function getIsScheduledAttribute()
{
return $this->getOriginal('scheduled_at') != '0000-00-00 00:00:00';
}
/**
* Get the transformer instance.
*

View File

@@ -59,7 +59,7 @@ class IncidentPresenter extends BasePresenter
{
return ucfirst((new Date($this->resource->created_at))
->setTimezone($this->tz)
->format('l j F Y H:i:s'));
->format('l jS F Y H:i:s'));
}
/**
@@ -72,6 +72,50 @@ class IncidentPresenter extends BasePresenter
return $this->resource->created_at->setTimezone($this->tz)->toISO8601String();
}
/**
* Present diff for humans date time.
*
* @return string
*/
public function scheduled_at_diff()
{
return (new Date($this->resource->scheduled_at))
->setTimezone($this->tz)
->diffForHumans();
}
/**
* Present formated date time.
*
* @return string
*/
public function scheduled_at_formatted()
{
return ucfirst((new Date($this->resource->scheduled_at))
->setTimezone($this->tz)
->format('l jS F Y H:i:s'));
}
/**
* Present formatted date time.
*
* @return string
*/
public function scheduled_at_iso()
{
return $this->resource->scheduled_at->setTimezone($this->tz)->toISO8601String();
}
/**
* Formats the scheduled_at time ready to be used by bootstrap-datetimepicker.
*
* @return string
*/
public function scheduled_at_datetimepicker()
{
return $this->resource->scheduled_at->setTimezone($this->tz)->format('d/m/Y H:i');
}
/**
* Present the status with an icon.
*
@@ -80,15 +124,17 @@ class IncidentPresenter extends BasePresenter
public function icon()
{
switch ($this->resource->status) {
case 1:
case 0: // Scheduled
return 'icon ion-android-calendar';
case 1: // Investigating
return 'icon ion-flag';
case 2:
case 2: // Identified
return 'icon ion-alert';
case 3:
case 3: // Watching
return 'icon ion-eye';
case 4:
case 4: // Fixed
return 'icon ion-checkmark';
default:
default: // Something actually broke, this shouldn't happen.
return '';
}
}