Refactor Execution Record Model and Update Context Handling
- Removed RobotID from ExecutionRecord and DeliveryContext structures, emphasizing the use of MemberID as the globally unique identifier. - Updated relevant documentation in DESIGN.md, TECHNICAL.md, and TODO.md to reflect these changes, ensuring clarity on the new context handling. - Revised methods in ExecutionStore and Executor to align with the updated model, enhancing data management and execution tracking. - Improved test cases to validate the new structure and ensure comprehensive coverage of execution scenarios.
This commit is contained in:
parent
480063f5b2
commit
ef7f32b428
10 changed files with 428 additions and 239 deletions
|
|
@ -504,7 +504,7 @@ P4 generates delivery content and pushes to Delivery Center. **Agent only genera
|
|||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ DeliveryRequest │
|
||||
│ - Content: Summary, Body, Attachments │
|
||||
│ - Context: robot_id, member_id, execution_id, trigger, team│
|
||||
│ - Context: member_id, execution_id, trigger, team │
|
||||
│ (No Channels - Delivery Center decides) │
|
||||
└─────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
|
|
@ -554,8 +554,7 @@ type DeliveryAttachment struct {
|
|||
|
||||
// DeliveryContext - tracking and audit info
|
||||
type DeliveryContext struct {
|
||||
RobotID string `json:"robot_id"` // Robot config ID
|
||||
MemberID string `json:"member_id"` // Robot member ID (user identity)
|
||||
MemberID string `json:"member_id"` // Robot member ID (globally unique)
|
||||
ExecutionID string `json:"execution_id"`
|
||||
TriggerType TriggerType `json:"trigger_type"`
|
||||
TeamID string `json:"team_id"`
|
||||
|
|
|
|||
|
|
@ -1237,8 +1237,7 @@ func (r *Robot) GetExecutions() []*Execution {
|
|||
// Relationship: 1 Execution = 1 job.Job
|
||||
type Execution struct {
|
||||
ID string `json:"id"` // unique execution ID
|
||||
RobotID string `json:"robot_id"` // robot config ID
|
||||
MemberID string `json:"member_id"` // robot member ID (user identity)
|
||||
MemberID string `json:"member_id"` // robot member ID (globally unique)
|
||||
TeamID string `json:"team_id"`
|
||||
TriggerType TriggerType `json:"trigger_type"` // clock | human | event
|
||||
StartTime time.Time `json:"start_time"`
|
||||
|
|
@ -1426,8 +1425,7 @@ type DeliveryAttachment struct {
|
|||
|
||||
// DeliveryContext - tracking and audit info
|
||||
type DeliveryContext struct {
|
||||
RobotID string `json:"robot_id"` // Robot config ID
|
||||
MemberID string `json:"member_id"` // Robot member ID (user identity)
|
||||
MemberID string `json:"member_id"` // Robot member ID (globally unique)
|
||||
ExecutionID string `json:"execution_id"`
|
||||
TriggerType TriggerType `json:"trigger_type"`
|
||||
TeamID string `json:"team_id"`
|
||||
|
|
@ -2036,8 +2034,7 @@ type DeliveryAttachment struct {
|
|||
|
||||
// DeliveryContext - tracking and audit info
|
||||
type DeliveryContext struct {
|
||||
RobotID string `json:"robot_id"` // Robot config ID
|
||||
MemberID string `json:"member_id"` // Robot member ID (user identity)
|
||||
MemberID string `json:"member_id"` // Robot member ID (globally unique)
|
||||
ExecutionID string `json:"execution_id"`
|
||||
TriggerType TriggerType `json:"trigger_type"`
|
||||
TeamID string `json:"team_id"`
|
||||
|
|
@ -2054,7 +2051,6 @@ type DeliveryContext struct {
|
|||
"attachments": [{"title": "Report.pdf", "file": "__yao.attachment://abc123"}]
|
||||
},
|
||||
"context": {
|
||||
"robot_id": "robot_sales_001",
|
||||
"member_id": "mem_abc123",
|
||||
"execution_id": "exec_xyz789",
|
||||
"trigger_type": "clock",
|
||||
|
|
@ -2182,7 +2178,7 @@ type DeliveryCenter struct {
|
|||
// Deliver - main entry point
|
||||
func (dc *DeliveryCenter) Deliver(ctx context.Context, req *DeliveryRequest) *DeliveryResult {
|
||||
requestID := generateID()
|
||||
prefs := dc.getDeliveryPreferences(ctx, req.Context.RobotID)
|
||||
prefs := dc.getDeliveryPreferences(ctx, req.Context.MemberID)
|
||||
|
||||
var results []ChannelResult
|
||||
allSuccess := true
|
||||
|
|
@ -2355,8 +2351,7 @@ Robot execution history is stored in `__yao.agent_execution` table for UI displa
|
|||
type ExecutionRecord struct {
|
||||
ID int64 `json:"id,omitempty"` // Auto-increment primary key
|
||||
ExecutionID string `json:"execution_id"` // Unique execution identifier
|
||||
RobotID string `json:"robot_id"` // Robot config ID
|
||||
MemberID string `json:"member_id"` // Robot member ID (user identity)
|
||||
MemberID string `json:"member_id"` // Robot member ID (globally unique)
|
||||
TeamID string `json:"team_id"` // Team ID
|
||||
JobID string `json:"job_id,omitempty"` // Linked job.Job ID
|
||||
TriggerType TriggerType `json:"trigger_type"` // clock | human | event
|
||||
|
|
@ -2424,12 +2419,11 @@ func (s *ExecutionStore) UpdateCurrent(ctx context.Context, executionID string,
|
|||
func (s *ExecutionStore) Delete(ctx context.Context, executionID string) error
|
||||
|
||||
// Conversion helpers
|
||||
func FromExecution(exec *Execution, robotID string) *ExecutionRecord
|
||||
func FromExecution(exec *Execution) *ExecutionRecord
|
||||
func (r *ExecutionRecord) ToExecution() *Execution
|
||||
|
||||
type ListOptions struct {
|
||||
RobotID string // Filter by robot config ID
|
||||
MemberID string // Filter by robot member ID
|
||||
MemberID string // Filter by robot member ID (globally unique)
|
||||
TeamID string // Filter by team
|
||||
Status ExecStatus // Filter by status
|
||||
TriggerType TriggerType // Filter by trigger
|
||||
|
|
|
|||
|
|
@ -875,7 +875,7 @@ Created new `yao/assert` package for universal assertion/validation:
|
|||
|
||||
- [x] `yao/models/agent/execution.mod.yao` - Execution record model (`agent_execution` table)
|
||||
- [x] id, execution_id (unique)
|
||||
- [x] robot_id, member_id, team_id, job_id
|
||||
- [x] member_id (globally unique), team_id, job_id
|
||||
- [x] trigger_type (enum: clock, human, event)
|
||||
- [x] **Status tracking** (synced with runtime Execution):
|
||||
- [x] status (enum: pending, running, completed, failed, cancelled)
|
||||
|
|
@ -903,7 +903,12 @@ Created new `yao/assert` package for universal assertion/validation:
|
|||
- [x] `FromExecution(exec, robotID)` - convert runtime Execution to record
|
||||
- [x] `ToExecution()` - convert record to runtime Execution
|
||||
- [x] Tests: `agent/robot/store/execution_test.go` (9 test groups, all passing)
|
||||
- [ ] Integrate into Executor - call `UpdatePhase()` after each phase completes
|
||||
- [x] Integrate into Executor - call `UpdatePhase()` after each phase completes
|
||||
- [x] Added `SkipPersistence` config option to `executor/types/Config`
|
||||
- [x] Added `ExecutionStore` to `executor/standard/Executor`
|
||||
- [x] Save execution record at start of `Execute()`
|
||||
- [x] Call `UpdatePhase()` after each phase completes in `runPhase()`
|
||||
- [x] Call `UpdateStatus()` on status changes (running, completed, failed)
|
||||
|
||||
### 10.2 Messenger Attachment Support ✅
|
||||
|
||||
|
|
@ -979,8 +984,7 @@ type DeliveryAttachment struct {
|
|||
|
||||
// DeliveryContext - tracking info
|
||||
type DeliveryContext struct {
|
||||
RobotID string `json:"robot_id"` // Robot config ID
|
||||
MemberID string `json:"member_id"` // Robot member ID (user identity)
|
||||
MemberID string `json:"member_id"` // Robot member ID (globally unique)
|
||||
ExecutionID string `json:"execution_id"`
|
||||
TriggerType TriggerType `json:"trigger_type"` // clock | human | event
|
||||
TeamID string `json:"team_id"`
|
||||
|
|
|
|||
|
|
@ -7,16 +7,19 @@ import (
|
|||
|
||||
"github.com/yaoapp/yao/agent/robot/executor/types"
|
||||
"github.com/yaoapp/yao/agent/robot/job"
|
||||
"github.com/yaoapp/yao/agent/robot/store"
|
||||
robottypes "github.com/yaoapp/yao/agent/robot/types"
|
||||
)
|
||||
|
||||
// Executor implements the standard executor with real Agent calls
|
||||
// This is the production executor that:
|
||||
// - Creates Job records for tracking
|
||||
// - Persists execution history to database
|
||||
// - Calls real Agents via Assistant.Stream()
|
||||
// - Logs phase transitions and errors
|
||||
type Executor struct {
|
||||
config types.Config
|
||||
store *store.ExecutionStore
|
||||
execCount atomic.Int32
|
||||
currentCount atomic.Int32
|
||||
onStart func()
|
||||
|
|
@ -25,13 +28,16 @@ type Executor struct {
|
|||
|
||||
// New creates a new standard executor
|
||||
func New() *Executor {
|
||||
return &Executor{}
|
||||
return &Executor{
|
||||
store: store.NewExecutionStore(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewWithConfig creates a new standard executor with configuration
|
||||
func NewWithConfig(config types.Config) *Executor {
|
||||
return &Executor{
|
||||
config: config,
|
||||
store: store.NewExecutionStore(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +82,18 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
|
|||
// Set robot reference for phase methods
|
||||
exec.SetRobot(robot)
|
||||
|
||||
// Persist execution record to database
|
||||
// Robot is identified by member_id (globally unique in __yao.member table)
|
||||
if !e.config.SkipPersistence && e.store != nil {
|
||||
record := store.FromExecution(exec)
|
||||
if err := e.store.Save(ctx.Context, record); err != nil {
|
||||
// Log warning but don't fail execution
|
||||
if !e.config.SkipJobIntegration {
|
||||
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to persist execution record: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire execution slot
|
||||
if !robot.TryAcquireSlot(exec) {
|
||||
if !e.config.SkipJobIntegration && exec.JobID != "" {
|
||||
|
|
@ -105,6 +123,14 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
|
|||
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to update status to running: %v", err))
|
||||
}
|
||||
}
|
||||
// Persist running status
|
||||
if !e.config.SkipPersistence && e.store != nil {
|
||||
if err := e.store.UpdateStatus(ctx.Context, exec.ID, robottypes.ExecRunning, ""); err != nil {
|
||||
if !e.config.SkipJobIntegration {
|
||||
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to persist running status: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for simulated failure (for testing)
|
||||
if dataStr, ok := data.(string); ok && dataStr == "simulate_failure" {
|
||||
|
|
@ -113,6 +139,10 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
|
|||
if !e.config.SkipJobIntegration {
|
||||
_ = job.FailExecution(ctx, exec, fmt.Errorf("simulated failure"))
|
||||
}
|
||||
// Persist failed status
|
||||
if !e.config.SkipPersistence && e.store != nil {
|
||||
_ = e.store.UpdateStatus(ctx.Context, exec.ID, robottypes.ExecFailed, "simulated failure")
|
||||
}
|
||||
return exec, nil
|
||||
}
|
||||
|
||||
|
|
@ -125,6 +155,10 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
|
|||
if !e.config.SkipJobIntegration {
|
||||
_ = job.FailExecution(ctx, exec, err)
|
||||
}
|
||||
// Persist failed status
|
||||
if !e.config.SkipPersistence && e.store != nil {
|
||||
_ = e.store.UpdateStatus(ctx.Context, exec.ID, robottypes.ExecFailed, err.Error())
|
||||
}
|
||||
return exec, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -139,6 +173,14 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
|
|||
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to mark execution as completed: %v", err))
|
||||
}
|
||||
}
|
||||
// Persist completed status
|
||||
if !e.config.SkipPersistence && e.store != nil {
|
||||
if err := e.store.UpdateStatus(ctx.Context, exec.ID, robottypes.ExecCompleted, ""); err != nil {
|
||||
if !e.config.SkipJobIntegration {
|
||||
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to persist completed status: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exec, nil
|
||||
}
|
||||
|
|
@ -183,6 +225,19 @@ func (e *Executor) runPhase(ctx *robottypes.Context, exec *robottypes.Execution,
|
|||
return err
|
||||
}
|
||||
|
||||
// Persist phase output to database
|
||||
if !e.config.SkipPersistence && e.store != nil {
|
||||
phaseData := e.getPhaseData(exec, phase)
|
||||
if phaseData != nil {
|
||||
if err := e.store.UpdatePhase(ctx.Context, exec.ID, phase, phaseData); err != nil {
|
||||
// Log warning but don't fail execution
|
||||
if !e.config.SkipJobIntegration {
|
||||
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to persist phase %s data: %v", phase, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if e.config.OnPhaseEnd != nil {
|
||||
e.config.OnPhaseEnd(phase)
|
||||
}
|
||||
|
|
@ -195,6 +250,26 @@ func (e *Executor) runPhase(ctx *robottypes.Context, exec *robottypes.Execution,
|
|||
return nil
|
||||
}
|
||||
|
||||
// getPhaseData extracts the output data for a specific phase from execution
|
||||
func (e *Executor) getPhaseData(exec *robottypes.Execution, phase robottypes.Phase) interface{} {
|
||||
switch phase {
|
||||
case robottypes.PhaseInspiration:
|
||||
return exec.Inspiration
|
||||
case robottypes.PhaseGoals:
|
||||
return exec.Goals
|
||||
case robottypes.PhaseTasks:
|
||||
return exec.Tasks
|
||||
case robottypes.PhaseRun:
|
||||
return exec.Results
|
||||
case robottypes.PhaseDelivery:
|
||||
return exec.Delivery
|
||||
case robottypes.PhaseLearning:
|
||||
return exec.Learning
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExecCount returns total execution count
|
||||
func (e *Executor) ExecCount() int {
|
||||
return int(e.execCount.Load())
|
||||
|
|
|
|||
158
agent/robot/executor/standard/executor_test.go
Normal file
158
agent/robot/executor/standard/executor_test.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
package standard_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/yaoapp/yao/agent/robot/executor/standard"
|
||||
"github.com/yaoapp/yao/agent/robot/executor/types"
|
||||
"github.com/yaoapp/yao/agent/robot/store"
|
||||
robottypes "github.com/yaoapp/yao/agent/robot/types"
|
||||
"github.com/yaoapp/yao/agent/testutils"
|
||||
oauthTypes "github.com/yaoapp/yao/openapi/oauth/types"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Executor Persistence Integration Tests
|
||||
// ============================================================================
|
||||
|
||||
func TestExecutorPersistence(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test")
|
||||
}
|
||||
|
||||
testutils.Prepare(t)
|
||||
defer testutils.Clean(t)
|
||||
|
||||
t.Run("persists_execution_record_on_start", func(t *testing.T) {
|
||||
ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
|
||||
UserID: "user_persist_001",
|
||||
TeamID: "team_persist_001",
|
||||
})
|
||||
|
||||
robot := createPersistenceTestRobot("member_persist_001", "team_persist_001")
|
||||
|
||||
// Create executor with persistence enabled but skip job integration
|
||||
e := standard.NewWithConfig(types.Config{
|
||||
SkipJobIntegration: true,
|
||||
SkipPersistence: false,
|
||||
})
|
||||
|
||||
// Execute with simulated failure to ensure we get a result
|
||||
exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, exec)
|
||||
|
||||
// Verify execution record was persisted
|
||||
s := store.NewExecutionStore()
|
||||
record, err := s.Get(context.Background(), exec.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, record)
|
||||
|
||||
assert.Equal(t, exec.ID, record.ExecutionID)
|
||||
assert.Equal(t, "member_persist_001", record.MemberID)
|
||||
assert.Equal(t, "team_persist_001", record.TeamID)
|
||||
assert.Equal(t, robottypes.TriggerHuman, record.TriggerType)
|
||||
assert.Equal(t, robottypes.ExecFailed, record.Status)
|
||||
assert.Equal(t, "simulated failure", record.Error)
|
||||
|
||||
// Cleanup
|
||||
_ = s.Delete(context.Background(), exec.ID)
|
||||
})
|
||||
|
||||
t.Run("persists_failed_status_with_error", func(t *testing.T) {
|
||||
ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
|
||||
UserID: "user_persist_002",
|
||||
TeamID: "team_persist_002",
|
||||
})
|
||||
|
||||
robot := createPersistenceTestRobot("member_persist_002", "team_persist_002")
|
||||
|
||||
e := standard.NewWithConfig(types.Config{
|
||||
SkipJobIntegration: true,
|
||||
SkipPersistence: false,
|
||||
})
|
||||
|
||||
// Execute with simulated failure
|
||||
exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, exec)
|
||||
|
||||
// Verify the record has failed status with error message
|
||||
s := store.NewExecutionStore()
|
||||
record, err := s.Get(context.Background(), exec.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, record)
|
||||
|
||||
assert.Equal(t, robottypes.ExecFailed, record.Status)
|
||||
assert.Equal(t, "simulated failure", record.Error)
|
||||
assert.NotNil(t, record.StartTime)
|
||||
|
||||
// Cleanup
|
||||
_ = s.Delete(context.Background(), exec.ID)
|
||||
})
|
||||
|
||||
t.Run("skips_persistence_when_disabled", func(t *testing.T) {
|
||||
ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
|
||||
UserID: "user_persist_003",
|
||||
TeamID: "team_persist_003",
|
||||
})
|
||||
|
||||
robot := createPersistenceTestRobot("member_persist_003", "team_persist_003")
|
||||
|
||||
// Create executor with persistence disabled
|
||||
e := standard.NewWithConfig(types.Config{
|
||||
SkipJobIntegration: true,
|
||||
SkipPersistence: true,
|
||||
})
|
||||
|
||||
exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, exec)
|
||||
|
||||
// Verify no record was created
|
||||
s := store.NewExecutionStore()
|
||||
record, err := s.Get(context.Background(), exec.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, record) // Should not exist
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
func createPersistenceTestRobot(memberID, teamID string) *robottypes.Robot {
|
||||
return &robottypes.Robot{
|
||||
MemberID: memberID,
|
||||
TeamID: teamID,
|
||||
DisplayName: "Persistence Test Robot",
|
||||
Status: robottypes.RobotIdle,
|
||||
AutonomousMode: true,
|
||||
Config: &robottypes.Config{
|
||||
Identity: &robottypes.Identity{
|
||||
Role: "Test Robot",
|
||||
Duties: []string{"Testing persistence"},
|
||||
},
|
||||
Quota: &robottypes.Quota{
|
||||
Max: 5,
|
||||
Queue: 10,
|
||||
},
|
||||
Triggers: &robottypes.Triggers{
|
||||
Intervene: &robottypes.TriggerSwitch{Enabled: true},
|
||||
},
|
||||
Resources: &robottypes.Resources{
|
||||
Phases: map[robottypes.Phase]string{
|
||||
robottypes.PhaseInspiration: "robot.inspiration",
|
||||
robottypes.PhaseGoals: "robot.goals",
|
||||
robottypes.PhaseTasks: "robot.tasks",
|
||||
robottypes.PhaseRun: "robot.validation",
|
||||
"validation": "robot.validation",
|
||||
},
|
||||
Agents: []string{"experts.text-writer", "experts.data-analyst"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -53,6 +53,9 @@ type Config struct {
|
|||
// SkipJobIntegration skips job system integration (for testing)
|
||||
SkipJobIntegration bool
|
||||
|
||||
// SkipPersistence skips execution record persistence (for testing)
|
||||
SkipPersistence bool
|
||||
|
||||
// OnPhaseStart callback when a phase starts
|
||||
OnPhaseStart func(phase robottypes.Phase)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ import (
|
|||
type ExecutionRecord struct {
|
||||
ID int64 `json:"id,omitempty"` // Auto-increment primary key
|
||||
ExecutionID string `json:"execution_id"` // Unique execution identifier
|
||||
RobotID string `json:"robot_id"` // Robot config ID
|
||||
MemberID string `json:"member_id"` // Robot member ID (user identity)
|
||||
MemberID string `json:"member_id"` // Robot member ID (globally unique)
|
||||
TeamID string `json:"team_id"` // Team ID
|
||||
JobID string `json:"job_id,omitempty"` // Linked job.Job ID
|
||||
TriggerType types.TriggerType `json:"trigger_type"` // clock | human | event
|
||||
|
|
@ -53,8 +52,7 @@ type CurrentState struct {
|
|||
|
||||
// ListOptions - options for listing execution records
|
||||
type ListOptions struct {
|
||||
RobotID string `json:"robot_id,omitempty"`
|
||||
MemberID string `json:"member_id,omitempty"`
|
||||
MemberID string `json:"member_id,omitempty"` // Filter by robot member ID
|
||||
TeamID string `json:"team_id,omitempty"`
|
||||
Status types.ExecStatus `json:"status,omitempty"`
|
||||
TriggerType types.TriggerType `json:"trigger_type,omitempty"`
|
||||
|
|
@ -146,9 +144,6 @@ func (s *ExecutionStore) List(ctx context.Context, opts *ListOptions) ([]*Execut
|
|||
// Build where conditions
|
||||
var wheres []model.QueryWhere
|
||||
if opts != nil {
|
||||
if opts.RobotID != "" {
|
||||
wheres = append(wheres, model.QueryWhere{Column: "robot_id", Value: opts.RobotID})
|
||||
}
|
||||
if opts.MemberID != "" {
|
||||
wheres = append(wheres, model.QueryWhere{Column: "member_id", Value: opts.MemberID})
|
||||
}
|
||||
|
|
@ -349,9 +344,6 @@ func (s *ExecutionStore) recordToMap(record *ExecutionRecord) map[string]interfa
|
|||
"phase": string(record.Phase),
|
||||
}
|
||||
|
||||
if record.RobotID != "" {
|
||||
data["robot_id"] = record.RobotID
|
||||
}
|
||||
if record.JobID != "" {
|
||||
data["job_id"] = record.JobID
|
||||
}
|
||||
|
|
@ -410,9 +402,6 @@ func (s *ExecutionStore) mapToRecord(row map[string]interface{}) (*ExecutionReco
|
|||
if v, ok := row["execution_id"].(string); ok {
|
||||
record.ExecutionID = v
|
||||
}
|
||||
if v, ok := row["robot_id"].(string); ok {
|
||||
record.RobotID = v
|
||||
}
|
||||
if v, ok := row["member_id"].(string); ok {
|
||||
record.MemberID = v
|
||||
}
|
||||
|
|
@ -640,10 +629,9 @@ func (s *ExecutionStore) parseTime(v interface{}) *time.Time {
|
|||
}
|
||||
|
||||
// FromExecution creates an ExecutionRecord from a runtime Execution
|
||||
func FromExecution(exec *types.Execution, robotID string) *ExecutionRecord {
|
||||
func FromExecution(exec *types.Execution) *ExecutionRecord {
|
||||
record := &ExecutionRecord{
|
||||
ExecutionID: exec.ID,
|
||||
RobotID: robotID,
|
||||
MemberID: exec.MemberID,
|
||||
TeamID: exec.TeamID,
|
||||
JobID: exec.JobID,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ func TestExecutionStoreSave(t *testing.T) {
|
|||
startTime := time.Now()
|
||||
record := &store.ExecutionRecord{
|
||||
ExecutionID: "exec_test_save_001",
|
||||
RobotID: "robot_config_001",
|
||||
MemberID: "member_test_001",
|
||||
TeamID: "team_test_001",
|
||||
JobID: "job_test_001",
|
||||
|
|
@ -52,7 +51,6 @@ func TestExecutionStoreSave(t *testing.T) {
|
|||
require.NotNil(t, saved)
|
||||
|
||||
assert.Equal(t, "exec_test_save_001", saved.ExecutionID)
|
||||
assert.Equal(t, "robot_config_001", saved.RobotID)
|
||||
assert.Equal(t, "member_test_001", saved.MemberID)
|
||||
assert.Equal(t, "team_test_001", saved.TeamID)
|
||||
assert.Equal(t, "job_test_001", saved.JobID)
|
||||
|
|
@ -68,7 +66,6 @@ func TestExecutionStoreSave(t *testing.T) {
|
|||
startTime := time.Now()
|
||||
record := &store.ExecutionRecord{
|
||||
ExecutionID: "exec_test_save_002",
|
||||
RobotID: "robot_config_002",
|
||||
MemberID: "member_test_002",
|
||||
TeamID: "team_test_002",
|
||||
TriggerType: types.TriggerHuman,
|
||||
|
|
@ -124,7 +121,6 @@ func TestExecutionStoreGet(t *testing.T) {
|
|||
require.NotNil(t, record)
|
||||
|
||||
assert.Equal(t, "exec_test_get_001", record.ExecutionID)
|
||||
assert.Equal(t, "robot_config_get", record.RobotID)
|
||||
assert.Equal(t, "member_test_get", record.MemberID)
|
||||
assert.Equal(t, "team_test_get", record.TeamID)
|
||||
assert.Equal(t, types.TriggerClock, record.TriggerType)
|
||||
|
|
@ -184,17 +180,6 @@ func TestExecutionStoreList(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("filters_by_robot_id", func(t *testing.T) {
|
||||
records, err := s.List(ctx, &store.ListOptions{
|
||||
RobotID: "robot_list_001",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(records))
|
||||
for _, r := range records {
|
||||
assert.Equal(t, "robot_list_001", r.RobotID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("filters_by_team_id", func(t *testing.T) {
|
||||
records, err := s.List(ctx, &store.ListOptions{
|
||||
TeamID: "team_list_001",
|
||||
|
|
@ -269,7 +254,6 @@ func TestExecutionStoreUpdatePhase(t *testing.T) {
|
|||
startTime := time.Now()
|
||||
record := &store.ExecutionRecord{
|
||||
ExecutionID: "exec_test_phase_001",
|
||||
RobotID: "robot_phase_001",
|
||||
MemberID: "member_phase_001",
|
||||
TeamID: "team_phase_001",
|
||||
TriggerType: types.TriggerClock,
|
||||
|
|
@ -611,10 +595,9 @@ func TestExecutionRecordConversion(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
record := store.FromExecution(exec, "robot_convert_001")
|
||||
record := store.FromExecution(exec)
|
||||
|
||||
assert.Equal(t, "exec_convert_001", record.ExecutionID)
|
||||
assert.Equal(t, "robot_convert_001", record.RobotID)
|
||||
assert.Equal(t, "member_convert_001", record.MemberID)
|
||||
assert.Equal(t, "team_convert_001", record.TeamID)
|
||||
assert.Equal(t, "job_convert_001", record.JobID)
|
||||
|
|
@ -636,7 +619,6 @@ func TestExecutionRecordConversion(t *testing.T) {
|
|||
endTime := now.Add(time.Hour)
|
||||
record := &store.ExecutionRecord{
|
||||
ExecutionID: "exec_convert_002",
|
||||
RobotID: "robot_convert_002",
|
||||
MemberID: "member_convert_002",
|
||||
TeamID: "team_convert_002",
|
||||
JobID: "job_convert_002",
|
||||
|
|
@ -702,7 +684,6 @@ func setupTestExecution(t *testing.T, s *store.ExecutionStore, ctx context.Conte
|
|||
|
||||
record := &store.ExecutionRecord{
|
||||
ExecutionID: "exec_test_get_001",
|
||||
RobotID: "robot_config_get",
|
||||
MemberID: "member_test_get",
|
||||
TeamID: "team_test_get",
|
||||
JobID: "job_test_get",
|
||||
|
|
@ -743,7 +724,6 @@ func setupTestExecutionsForList(t *testing.T, s *store.ExecutionStore, ctx conte
|
|||
records := []*store.ExecutionRecord{
|
||||
{
|
||||
ExecutionID: "exec_test_list_001",
|
||||
RobotID: "robot_list_001",
|
||||
MemberID: "member_list_001",
|
||||
TeamID: "team_list_001",
|
||||
TriggerType: types.TriggerClock,
|
||||
|
|
@ -753,7 +733,6 @@ func setupTestExecutionsForList(t *testing.T, s *store.ExecutionStore, ctx conte
|
|||
},
|
||||
{
|
||||
ExecutionID: "exec_test_list_002",
|
||||
RobotID: "robot_list_001",
|
||||
MemberID: "member_list_001",
|
||||
TeamID: "team_list_001",
|
||||
TriggerType: types.TriggerClock,
|
||||
|
|
@ -763,7 +742,6 @@ func setupTestExecutionsForList(t *testing.T, s *store.ExecutionStore, ctx conte
|
|||
},
|
||||
{
|
||||
ExecutionID: "exec_test_list_003",
|
||||
RobotID: "robot_list_002",
|
||||
MemberID: "member_list_002",
|
||||
TeamID: "team_list_001",
|
||||
TriggerType: types.TriggerHuman,
|
||||
|
|
@ -773,7 +751,6 @@ func setupTestExecutionsForList(t *testing.T, s *store.ExecutionStore, ctx conte
|
|||
},
|
||||
{
|
||||
ExecutionID: "exec_test_list_004",
|
||||
RobotID: "robot_list_002",
|
||||
MemberID: "member_list_002",
|
||||
TeamID: "team_list_002",
|
||||
TriggerType: types.TriggerEvent,
|
||||
|
|
|
|||
334
data/bindata.go
334
data/bindata.go
File diff suppressed because it is too large
Load diff
|
|
@ -24,15 +24,6 @@
|
|||
"unique": true,
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "robot_id",
|
||||
"type": "string",
|
||||
"label": "Robot ID",
|
||||
"comment": "Robot configuration ID (from robot_config)",
|
||||
"length": 64,
|
||||
"nullable": true,
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "member_id",
|
||||
"type": "string",
|
||||
|
|
@ -197,10 +188,10 @@
|
|||
"comment": "Index for trigger type analysis"
|
||||
},
|
||||
{
|
||||
"name": "idx_agent_execution_robot_start",
|
||||
"columns": ["robot_id", "start_time"],
|
||||
"name": "idx_agent_execution_member_start",
|
||||
"columns": ["member_id", "start_time"],
|
||||
"type": "index",
|
||||
"comment": "Index for robot execution history"
|
||||
"comment": "Index for robot execution history by member"
|
||||
}
|
||||
],
|
||||
"option": { "timestamps": true, "soft_deletes": false, "permission": true }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue