8 Commits
1.1 ... 1.6

Author SHA1 Message Date
264e0b72ba add link action 2025-10-01 10:46:20 +02:00
48c509d1c9 fix middleware 2025-07-04 13:13:05 +02:00
b69480ccb3 add iframe action 2025-07-02 18:07:13 +02:00
f8804fc651 fix keys 2024-12-01 16:03:43 +01:00
6edb16ccbf add form json 2024-11-29 16:57:06 +01:00
52cbb95648 add form json 2024-11-29 16:12:33 +01:00
3295c3da5f update README; add Laravel stubs; add form support 2024-11-29 14:06:14 +01:00
bdfcba39da fix slug 2024-11-12 13:28:17 +01:00
10 changed files with 307 additions and 5 deletions

27
README.md Normal file
View File

@@ -0,0 +1,27 @@
# Pilot SDK
## Installation
```bash
composer config repositories.pilot vcs "https://git.bluesquare.io/bluesquare/pilot-sdk.git"
composer require bluesquare/pilot-sdk
```
## Configuration
### Laravel
Ajouter la clé d'API Pilot au `.env` :
```dotenv
PILOT_KEY=xxxxx
```
## Utilisation
### Laravel
```php
php artisan pilot:metric MyMetric
php artisan pilot:action MyAction
```

View File

@@ -2,6 +2,8 @@
namespace Bluesquare\Pilot\Entity; namespace Bluesquare\Pilot\Entity;
use Illuminate\Support\Str;
abstract class Action extends Entity abstract class Action extends Entity
{ {
public function action( public function action(
@@ -30,6 +32,40 @@ abstract class Action extends Entity
]; ];
} }
public function iframe($url, $expires = 240)
{
$key = 'pilot_action_' . Str::random(10);
$token = Str::random(40);
cache()->add($key, $token, now()->addMinutes($expires));
$url = $url . (str_contains($url, '?') ? '&' : '?') . "pilot_token=$key|$token";
return [
'json' => [
'type' => 'iframe',
'url' => $url,
],
];
}
public function link($url, $expires = 5)
{
$key = 'pilot_action_' . Str::random(10);
$token = Str::random(40);
cache()->add($key, $token, now()->addMinutes($expires));
$url = $url . (str_contains($url, '?') ? '&' : '?') . "pilot_token=$key|$token";
return [
'json' => [
'type' => 'link',
'url' => $url,
],
];
}
public function error($message) public function error($message)
{ {
return [ return [

View File

@@ -4,9 +4,13 @@ namespace Bluesquare\Pilot\Entity;
abstract class Entity abstract class Entity
{ {
public function __construct(protected $slug = null) protected $slug = null;
{
public function __construct($slug = null)
{
if (! is_null($slug)) {
$this->slug = $slug;
}
} }
public function slug() public function slug()

View File

@@ -0,0 +1,47 @@
<?php
namespace Bluesquare\Pilot\Laravel\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
class MakeAction extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'pilot:action {name?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate files for a Bluesquare Pilot action';
/**
* Execute the console command.
*/
public function handle()
{
$name = ! empty($this->argument('name'))
? $this->argument('name')
: $this->ask('What is the name of the action?', 'MakeSmoothie');
$contents = file_get_contents(__DIR__.'/../stubs/action.php');
$contents = str_replace('MakeSmoothie', $name, $contents);
$contents = str_replace('make-smoothie', Str::snake($name, '-'), $contents);
$path = app_path('Pilot/Actions/'.$name.'.php');
if (file_exists($path)) {
$this->error('Action already exists!');
return;
}
@mkdir(dirname($path), 0755, true);
file_put_contents($path, $contents);
$this->info('Action created successfully!');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Bluesquare\Pilot\Laravel\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
class MakeMetric extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'pilot:metric {name?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate files for a Bluesquare Pilot metric';
/**
* Execute the console command.
*/
public function handle()
{
$name = ! empty($this->argument('name'))
? $this->argument('name')
: $this->ask('What is the name of the metric?', 'SmoothiesCount');
$contents = file_get_contents(__DIR__.'/../stubs/metric.php');
$contents = str_replace('SmoothiesCount', $name, $contents);
$contents = str_replace('smoothies-count', Str::snake($name, '-'), $contents);
$path = app_path('Pilot/Metrics/'.$name.'.php');
if (file_exists($path)) {
$this->error('Metric already exists!');
return;
}
@mkdir(dirname($path), 0755, true);
file_put_contents($path, $contents);
$this->info('Metric created successfully!');
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Bluesquare\Pilot\Laravel\Middlewares;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class CheckPilotToken
{
public function handle(Request $request, Closure $next)
{
if (request('pilot_token')) {
list($key, $token) = explode('|', request('pilot_token'));
if (cache()->get($key) == $token) {
cache()->set($key, $token, now()->addHour());
session()->put('pilot_token', true);
}
}
if (! session('pilot_token') && ! app()->environment('local')) {
abort(403, "Pilot session expired.");
}
return $next($request);
}
}

View File

@@ -2,6 +2,7 @@
namespace Bluesquare\Pilot\Laravel; namespace Bluesquare\Pilot\Laravel;
use Bluesquare\Pilot\Laravel\Middlewares\CheckPilotToken;
use Bluesquare\Pilot\Pilot; use Bluesquare\Pilot\Pilot;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@@ -35,6 +36,13 @@ class PilotServiceProvider extends ServiceProvider
Route::any('api/pilot', function (Request $request) { Route::any('api/pilot', function (Request $request) {
return $this->handleRequest($request); return $this->handleRequest($request);
}); });
Route::aliasMiddleware('pilot', CheckPilotToken::class);
$this->commands([
\Bluesquare\Pilot\Laravel\Commands\MakeAction::class,
\Bluesquare\Pilot\Laravel\Commands\MakeMetric::class,
]);
} }
protected function registerPilot() protected function registerPilot()
@@ -49,6 +57,10 @@ class PilotServiceProvider extends ServiceProvider
if (class_exists($class)) { if (class_exists($class)) {
$action = new $class; $action = new $class;
if (method_exists($action, 'form'))
$this->pilot->form($action->slug(), [$action, 'form']);
$this->pilot->action($action->slug(), [$action, 'handle']); $this->pilot->action($action->slug(), [$action, 'handle']);
} }
} }
@@ -76,7 +88,7 @@ class PilotServiceProvider extends ServiceProvider
abort(403); abort(403);
} }
$output = $this->pilot->handle($request->all(), $request->files->all()); $output = $this->pilot->handle($request->all(), $request->allFiles());
if (isset($output['json'])) { if (isset($output['json'])) {
return response()->json($output['json']); return response()->json($output['json']);

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Pilot\Actions;
use Bluesquare\Pilot\Entity\Action;
class MakeSmoothie extends Action
{
protected $slug = 'make-smoothie';
// Optional: you may remove this method if you don't need a form
public function form()
{
return [
'flavour' => [
'type' => 'text',
'label' => 'Flavour',
'required' => true,
],
];
}
/**
* Available params:
* @param $data array The form data
* @param $files array The uploaded files
* @param $entry mixed|null The Pilot entry ID to execute the action for
* @param $entries array<mixed> The Pilot entries ID to execute the action for
*/
public function handle(array $data)
{
if ($data['flavour'] === 'mayonnaise') {
return $this->error('Please choose a better flavour');
}
// ...
return $this->action(
type: 'info',
title: 'Smoothie',
message: 'Your smoothie is ready!'
);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Pilot\Metrics;
use Bluesquare\Pilot\Entity\Metric;
class SmoothiesCount extends Metric
{
protected $slug = 'smoothies-count';
/**
* Available params:
* @param $entry mixed|null The Pilot entry ID to compute the metric for
* @param $entries array<mixed> The Pilot entries ID to compute the metric for
*/
public function compute($entry = null)
{
// Availability
// return $this->monitor(true);
// Progress bar
// return $this->progress(10, 100);
// Doughnut chart
// return $this->partition([
// ['label' => 'Banana', 'value' => 90],
// ['label' => 'Pineapple', 'value' => 89],
// ]);
// Trend chart
// return $this->trend([
// ['label' => 'Banana', 'value' => 90],
// ['label' => 'Pineapple', 'value' => 89],
// ]);
// Simple value
// return $this->value(12);
// Simple value with filters
return $this->filters([
$this->filter('Banana', 'banana', fn () => $this->value(90, 10)),
$this->filter('Pineapple', 'pineapple', fn () => $this->value(89, 20)),
]);
}
}

View File

@@ -9,6 +9,7 @@ class Pilot
*/ */
protected $registry = [ protected $registry = [
'action' => [], 'action' => [],
'form' => [],
'metric' => [], 'metric' => [],
]; ];
@@ -24,6 +25,17 @@ class Pilot
} }
} }
public function form($slug, callable $function)
{
$this->register('form', $slug, function () use ($function) {
return [
'json' => [
'form' => $function()
],
];
});
}
public function action($slug, callable $function) public function action($slug, callable $function)
{ {
$this->register('action', $slug, $function); $this->register('action', $slug, $function);
@@ -41,8 +53,8 @@ class Pilot
public function handle($data = [], $files = []) public function handle($data = [], $files = [])
{ {
$type = $data['type'] ?? null; $type = $data['_type'] ?? null;
$slug = $data['slug'] ?? null; $slug = $data['_slug'] ?? null;
if (isset($this->registry[$type]) && isset($this->registry[$type][$slug])) { if (isset($this->registry[$type]) && isset($this->registry[$type][$slug])) {
$params = $this->resolveDependencies($slug, $this->registry[$type][$slug], $data, $files); $params = $this->resolveDependencies($slug, $this->registry[$type][$slug], $data, $files);