Skip to content

Commit

Permalink
Implement basic OhDear integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrooksuk committed Nov 26, 2024
1 parent 35a4b24 commit 7287c0a
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('incidents', function (Blueprint $table) {
$table->string('external_provider')->nullable()->after('guid');
$table->string('external_id')->nullable();
});
}
};
8 changes: 8 additions & 0 deletions resources/svg/oh-dear.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions resources/views/filament/pages/integrations/oh-dear.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<x-filament::page>
<x-filament-panels::form wire:submit.prevent="importFeed">
{{ $this->form }}

<div>
<x-filament::button type="submit" color="primary">
{{ __('Import Feed') }}
</x-filament::button>
</div>
</x-filament-panels::form>
</x-filament::page>
69 changes: 69 additions & 0 deletions src/Actions/Integrations/ImportOhDearFeed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Cachet\Actions\Integrations;

use Cachet\Enums\ComponentStatusEnum;
use Cachet\Enums\ExternalProviderEnum;
use Cachet\Models\Component;
use Cachet\Models\Incident;
use Illuminate\Support\Carbon;

class ImportOhDearFeed
{
/**
* Import an OhDear feed.
*/
public function __invoke(array $data, bool $importSites, ?int $componentGroupId, bool $importIncidents): void
{
if ($importSites) {
$this->importSites($data['sites']['ungrouped'], $componentGroupId);
}

if ($importIncidents) {
$this->importIncidents($data['updatesPerDay']);
}
}

/**
* Import OhDear sites as components.
*/
private function importSites(array $sites, ?int $componentGroupId): void
{
foreach ($sites as $site) {
Component::updateOrCreate(
['link' => $site['url']],
[
'name' => $site['label'],
'component_group_id' => $componentGroupId,
'status' => $site['status'] === 'up' ? ComponentStatusEnum::operational : ComponentStatusEnum::partial_outage,
]
);
}
}

/**
* Import OhDear incidents.
*/
private function importIncidents(array $updatesPerDay): void
{
Incident::unguard();

foreach ($updatesPerDay as $day => $incidents) {
foreach ($incidents as $incident) {
Incident::updateOrCreate(
[
'external_provider' => $provider = ExternalProviderEnum::OhDear,
'external_id' => $incident['id']
],
[
'name' => $incident['title'],
'status' => $provider->status($incident['severity']),
'message' => $incident['text'],
'occurred_at' => Carbon::createFromTimestamp($incident['time']),
'created_at' => Carbon::createFromTimestamp($incident['time']),
]
);
}
}
}
}
3 changes: 3 additions & 0 deletions src/CachetDashboardServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public function panel(Panel $panel): Panel
->label(__('Settings'))
->collapsed()
->icon('cachet-settings'),
NavigationGroup::make('Integrations')
->label(__('Integrations'))
->collapsed(),
NavigationGroup::make(__('Resources'))
->collapsible(false),
])
Expand Down
22 changes: 22 additions & 0 deletions src/Enums/ExternalProviderEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Cachet\Enums;

enum ExternalProviderEnum: string
{
case OhDear = 'OhDear';

/**
* Match the status to the Cachet status.
*/
public function status(mixed $status): IncidentStatusEnum
{
if ($this === self::OhDear) {
return match ($status) {
'resolved' => IncidentStatusEnum::fixed,
'warning' => IncidentStatusEnum::investigating,
default => IncidentStatusEnum::unknown,
};
}
}
}
125 changes: 125 additions & 0 deletions src/Filament/Pages/Integrations/OhDear.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

namespace Cachet\Filament\Pages\Integrations;

use Cachet\Actions\Integrations\ImportOhDearFeed;
use Cachet\Filament\Resources\ComponentGroupResource;
use Cachet\Models\Component;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;

class OhDear extends Page
{
use InteractsWithForms;

protected static ?string $navigationIcon = 'cachet-oh-dear';

protected static ?string $navigationGroup = 'Integrations';

protected static string $view = 'cachet::filament.pages.integrations.oh-dear';

public string $url;
public bool $import_sites;
public ?int $component_group_id;
public bool $import_incidents;

/**
* Mount the page.
*/
public function mount(): void
{
$this->form->fill([
'url' => '',
'import_sites' => true,
'component_group_id' => null,
'import_incidents' => true,
]);
}

/**
* Get the form schema definition.
*/
protected function getFormSchema(): array
{
return [
Section::make()->schema([
TextInput::make('url')
->label(__('OhDear Status Page URL'))
->placeholder('https://status.example.com')
->url()
->required()
->suffix('/json')
->helperText(__('Enter the URL of your OhDear status page (e.g., https://status.example.com).')),

Toggle::make('import_sites')
->label(__('Import Sites as Components'))
->helperText(__('Sites configured in Oh Dear will be imported as components in Cachet.'))
->default(true)
->reactive(),

Select::make('component_group_id')
->searchable()
->visible(fn (Get $get) => $get('import_sites') === true)
->relationship('group', 'name')
->model(Component::class)
->label(__('Component Group'))
->helperText(__('The component group to assign imported components to.'))
->createOptionForm(fn (Form $form) => ComponentGroupResource::form($form))
->preload(),

Toggle::make('import_incidents')
->label(__('Import Incidents'))
->helperText(__('Recent incidents from Oh Dear will be imported as incidents in Cachet.'))
->default(false),
])
];
}

/**
* Import the OhDear feed.
*/
public function importFeed(ImportOhDearFeed $importOhDearFeedAction): void
{
$this->validate();

try {
$ohDear = Http::baseUrl(rtrim($this->url))
->get('/json')
->throw()
->json();
} catch (ConnectionException $e) {
$this->addError('url', $e->getMessage());

return;
} catch (RequestException $e) {
$this->addError('url', 'The provided URL is not a valid OhDear status page endpoint.');

return;
}

if (! isset($ohDear['sites'], $ohDear['summarizedStatus'])) {
$this->addError('url', 'The provided URL is not a valid OhDear status page endpoint.');

return;
}

$importOhDearFeedAction->__invoke($ohDear, $this->import_sites, $this->component_group_id, $this->import_incidents);

Notification::make()
->title(__('OhDear feed imported successfully'))
->success()
->send();

$this->reset(['url', 'import_sites', 'import_incidents']);
}
}
2 changes: 2 additions & 0 deletions src/Models/Incident.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class Incident extends Model

protected $fillable = [
'guid',
'external_provider',
'external_id',
'user_id',
'component_id',
'name',
Expand Down

0 comments on commit 7287c0a

Please sign in to comment.