Automations editor:
- {{ }} expression autocomplete in CodeMirror, scoped to the braces and
graph-aware (suggests only what upstream nodes provide + variables + now);
migrate the Generate prompt to CodeMirror so it shares the same completions
- Expandable editors: an expand button slides out a side-by-side panel
(matching the sidebar card), with a minimize control; the inline field
collapses to a hint while editing in the panel
- Hover-revealed editor toolbar (expand/copy) with styled tooltips so the
buttons no longer obscure the text while reading
- Beta badge on the Automations sidebar item
- Delete a single connection with Backspace/Delete (edge selection)
- Re-key node config so switching between same-type nodes refreshes the form
HTTP fetch node — cover every JSON response shape:
- Top-level array, object map (items_path=*), array of primitives, and NDJSON
- Key-based dedup via item_key_path (seen-set, FIFO-capped) for feeds without
dates; first poll records a baseline and emits nothing (date path too)
Fan-out test visibility:
- root_run_id links every forked branch back to the run that started a test,
so the test panel aggregates all branches instead of one
Fix a few pre-existing type issues (ScheduleData import, padded minute,
optional created_at).
62 lines
2.2 KiB
PHP
62 lines
2.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Enums\Automation\Node\Type as NodeType;
|
|
use App\Enums\UserWorkspace\Role;
|
|
use App\Models\Automation;
|
|
use App\Models\AutomationNodeRun;
|
|
use App\Models\AutomationRun;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
|
|
beforeEach(function () {
|
|
$this->workspace = Workspace::factory()->create();
|
|
$this->user = User::factory()->create(['current_workspace_id' => $this->workspace->id]);
|
|
$this->workspace->members()->attach($this->user->id, ['role' => Role::Admin->value]);
|
|
$this->user->refresh();
|
|
});
|
|
|
|
it('aggregates node runs from every fan-out branch under the root run', function () {
|
|
$automation = Automation::factory()->for($this->workspace)->create();
|
|
|
|
$root = AutomationRun::factory()->for($automation)->create();
|
|
$sibling = AutomationRun::factory()->for($automation)->create(['root_run_id' => $root->id]);
|
|
|
|
AutomationNodeRun::factory()->create([
|
|
'run_id' => $root->id,
|
|
'node_id' => 'fetch_rss_1',
|
|
'node_type' => NodeType::FetchRss,
|
|
]);
|
|
AutomationNodeRun::factory()->create([
|
|
'run_id' => $sibling->id,
|
|
'node_id' => 'http_1',
|
|
'node_type' => NodeType::HttpRequest,
|
|
]);
|
|
|
|
$response = $this->actingAs($this->user)
|
|
->getJson(route('app.automations.runs.show', [$automation, $root]));
|
|
|
|
$response->assertOk();
|
|
|
|
$nodeIds = collect($response->json('node_runs'))->pluck('node_id');
|
|
expect($nodeIds)->toHaveCount(2)
|
|
->and($nodeIds)->toContain('fetch_rss_1')
|
|
->and($nodeIds)->toContain('http_1');
|
|
});
|
|
|
|
it('returns the same aggregated family when queried from a sibling run', function () {
|
|
$automation = Automation::factory()->for($this->workspace)->create();
|
|
|
|
$root = AutomationRun::factory()->for($automation)->create();
|
|
$sibling = AutomationRun::factory()->for($automation)->create(['root_run_id' => $root->id]);
|
|
|
|
AutomationNodeRun::factory()->create(['run_id' => $root->id, 'node_id' => 'a']);
|
|
AutomationNodeRun::factory()->create(['run_id' => $sibling->id, 'node_id' => 'b']);
|
|
|
|
$response = $this->actingAs($this->user)
|
|
->getJson(route('app.automations.runs.show', [$automation, $sibling]));
|
|
|
|
$response->assertOk();
|
|
expect($response->json('node_runs'))->toHaveCount(2);
|
|
});
|