Skip to content

Latest commit

Β 

History

History
376 lines (300 loc) Β· 11.9 KB

File metadata and controls

376 lines (300 loc) Β· 11.9 KB

Notification Plan - API Validation Corrections

Date: 2025-11-03
Status: βœ… All notification detection methods validated against live iTop API

Summary

The notification implementation plan (PLAN_NOTIFICATIONS.md) has been fully validated against the iTop REST API. This document contains corrections and clarifications based on real API responses.


Critical Corrections Required

1. SLA Breach Detection Value ⚠️

Issue: Plan states SLA breach uses newvalue='yes'
Reality: iTop stores SLA breaches as newvalue='1' (integer, not string)

Locations to Update in PLAN_NOTIFICATIONS.md:

Line 87 - Notification Types Table

-|| **SLA breach (TTO/TTR)** | Ticket enters escalated status | CMDBChangeOp: `attcode IN ('sla_tto_passed','sla_ttr_passed')`, `newvalue='yes'` | `notify_ticket_sla_breach` |
+|| **SLA breach (TTO/TTR)** | Ticket enters escalated status | CMDBChangeOp: `attcode IN ('sla_tto_passed','sla_ttr_passed')`, `newvalue='1'` | `notify_ticket_sla_breach` |

Detection Code:

// Correct implementation
if (in_array($change['attcode'], ['sla_tto_passed', 'sla_ttr_passed']) 
    && $change['newvalue'] == '1') {  // NOT 'yes'!
    
    $type = str_replace('sla_', '', str_replace('_passed', '', $change['attcode']));
    // $type = 'tto' or 'ttr'
    
    notify('ticket_sla_breach', [
        'type' => $type,
        'ticket_id' => $change['objkey'],
        'when' => $change['date']
    ]);
}

Validated API Response:

{
  "attcode": "sla_tto_passed",
  "oldvalue": "",
  "newvalue": "1"
}

2. Case Log Structure - Major Clarification πŸ”

Issue: Plan doesn't document that case logs use a different class with different structure

Reality:

  • Scalar changes: CMDBChangeOpSetAttributeScalar with oldvalue/newvalue
  • Case log changes: CMDBChangeOpSetAttributeCaseLog with lastentry (NO oldvalue/newvalue)

Add to Line ~107 - CMDBChangeOp Structure

#### CMDBChangeOp Structure

CMDBChange (parent) β”œβ”€β”€ id β”œβ”€β”€ date (timestamp) β”œβ”€β”€ userinfo (who made the change) └── user_id

CMDBChangeOpSetAttributeScalar (child - for scalar fields) β”œβ”€β”€ objclass (e.g., "UserRequest", "Incident") β”œβ”€β”€ objkey (ticket ID) β”œβ”€β”€ attcode (field name: "agent_id", "status", "priority", "sla_tto_passed", "sla_ttr_passed") β”œβ”€β”€ oldvalue └── newvalue

CMDBChangeOpSetAttributeCaseLog (child - for log fields) β”œβ”€β”€ objclass (e.g., "UserRequest", "Incident") β”œβ”€β”€ objkey (ticket ID) β”œβ”€β”€ attcode (field name: "public_log", "private_log") β”œβ”€β”€ lastentry (entry index: 0, 1, 2, ...) └── NO oldvalue/newvalue (logs are append-only)

\`\`\`

**Key Differences**:
| Feature | Scalar Changes | Case Log Changes |
|---------|---------------|------------------|
| Class | `CMDBChangeOpSetAttributeScalar` | `CMDBChangeOpSetAttributeCaseLog` |
| Oldβ†’New tracking | βœ… `oldvalue`, `newvalue` | ❌ Not applicable |
| Entry tracking | ❌ Not present | βœ… `lastentry` (index) |
| Query method | `attcode IN ('status','agent_id',...)` | `attcode IN ('public_log','private_log')` |

Update Line ~206 - Case Log Detection Section

### Case Log Change Detection

#### CMDBChangeOpSetAttributeCaseLog

When a log entry is added, iTop creates a `CMDBChangeOpSetAttributeCaseLog` operation.

**Important Differences from Scalar Changes**:
- **No `oldvalue`/`newvalue` fields** (logs are append-only)
- **Has `lastentry` field**: Entry index (0 = first entry, 1 = second, etc.)
- Each log addition creates a new change operation with incremented `lastentry`
- Must use **separate class** `CMDBChangeOpSetAttributeCaseLog` (not the scalar class)

**Detection**:
```sql
SELECT CMDBChangeOpSetAttributeCaseLog
WHERE change->date > :last_check
  AND objclass IN ('UserRequest', 'Incident')
  AND objkey IN (my_relevant_tickets)
  AND attcode IN ('public_log', 'private_log')
  AND user_id != '' -- Exclude system-generated entries

Filter out system entries:

  • Empty or missing user_id field β†’ automated system changes
  • E.g., Automatic status transition logs with no human author

For Portal Users: Only public_log
For Agents: Both public_log + private_log


#### Update Line ~514 - getCaseLogChanges() Method

```php
/**
 * Get case log changes (comments) since timestamp
 * 
 * IMPORTANT: Uses CMDBChangeOpSetAttributeCaseLog class (different from scalar changes)
 * 
 * @param string $userId Nextcloud user ID
 * @param array $ticketIds Limit to these ticket IDs
 * @param string $since ISO 8601 timestamp
 * @param array $logAttributes ['public_log', 'private_log']
 * @return array Change operations with user_id != '' (has 'lastentry' field, NO oldvalue/newvalue)
 */
public function getCaseLogChanges(
    string $userId, 
    array $ticketIds, 
    string $since, 
    array $logAttributes
): array {
    $ticketIdList = implode(',', $ticketIds);
    
    // CRITICAL: Use CMDBChangeOpSetAttributeCaseLog class, not Scalar
    $oql = "SELECT CMDBChangeOpSetAttributeCaseLog 
            WHERE objclass IN ('UserRequest','Incident')
              AND objkey IN ($ticketIdList)
              AND attcode IN ('" . implode("','", $logAttributes) . "')
              AND change->date > '$since'
              AND user_id != ''
            ORDER BY change->date ASC";
    
    $result = $this->request($userId, [
        'operation' => 'core/get',
        'class' => 'CMDBChangeOpSetAttributeCaseLog',  // Different class!
        'key' => $oql,
        'output_fields' => '*'
    ]);
    
    // Returns entries with 'lastentry' field, NO oldvalue/newvalue
    return $result['objects'] ?? [];
}

3. Agent Assignment Detection - Value Clarification βœ…

Finding: iTop uses "0" (string "0") for NULL/empty foreign keys, not actual NULL

Validated API Response:

{
  "attcode": "agent_id",
  "oldvalue": "0",    // Unassigned
  "newvalue": "16"    // Assigned to person_id 16
}

Detection Logic:

// New assignment (NULL β†’ me)
if ($change['attcode'] == 'agent_id' 
    && $change['oldvalue'] == '0'  // Use string "0", not NULL
    && $change['newvalue'] == $myPersonId) {
    notify('ticket_assigned');
}

// Reassignment (other β†’ me)
if ($change['attcode'] == 'agent_id' 
    && $change['oldvalue'] != '0' 
    && $change['oldvalue'] != $myPersonId 
    && $change['newvalue'] == $myPersonId) {
    notify('ticket_reassigned');
}

4. API Response Examples - Add Case Log Example

Add to Appendix A (Line ~894) - After Scalar Example

#### Response (Case Log Changes)

**Query**:
```json
{
  "operation": "core/get",
  "class": "CMDBChangeOpSetAttributeCaseLog",
  "key": "SELECT CMDBChangeOpSetAttributeCaseLog WHERE objclass='UserRequest' AND objkey='12'",
  "output_fields": "*"
}

Response:

{
  "code": 0,
  "message": "Found: 3",
  "objects": {
    "CMDBChangeOpSetAttributeCaseLog::940": {
      "key": "940",
      "fields": {
        "change": "253",
        "date": "2025-11-03 09:21:55",
        "userinfo": "My first name My last name",
        "user_id": "1",
        "objclass": "UserRequest",
        "objkey": "12",
        "attcode": "public_log",
        "lastentry": "0",
        "finalclass": "CMDBChangeOpSetAttributeCaseLog",
        "user_id_friendlyname": "admin"
      }
    },
    "CMDBChangeOpSetAttributeCaseLog::945": {
      "key": "945",
      "fields": {
        "change": "255",
        "date": "2025-11-03 11:05:53",
        "userinfo": "My first name My last name",
        "user_id": "1",
        "objclass": "UserRequest",
        "objkey": "12",
        "attcode": "public_log",
        "lastentry": "1",
        "finalclass": "CMDBChangeOpSetAttributeCaseLog",
        "user_id_friendlyname": "admin"
      }
    },
    "CMDBChangeOpSetAttributeCaseLog::946": {
      "key": "946",
      "fields": {
        "change": "256",
        "date": "2025-11-03 11:06:03",
        "userinfo": "My first name My last name",
        "user_id": "1",
        "objclass": "UserRequest",
        "objkey": "12",
        "attcode": "private_log",
        "lastentry": "0",
        "finalclass": "CMDBChangeOpSetAttributeCaseLog",
        "user_id_friendlyname": "admin"
      }
    }
  }
}

Key Observations:

  • Entry 940: First public_log entry (lastentry: "0")
  • Entry 945: Second public_log entry (lastentry: "1")
  • Entry 946: First private_log entry (lastentry: "0")
  • Note: No oldvalue/newvalue fields - case logs are append-only

---

## Validation Summary

### βœ… Fully Validated Detection Methods

| Notification Type | Status | Test Data | Notes |
|-------------------|--------|-----------|-------|
| **Status changes** | βœ… Validated | 7 entries | `oldvalue` β†’ `newvalue` transitions work perfectly |
| **Agent assignments** | βœ… Validated | 2 entries | `oldvalue="0"` (NULL) β†’ `newvalue="16"` (assigned) |
| **Priority β†’ Critical** | βœ… Validated | 1 entry | `oldvalue="4"` (Low) β†’ `newvalue="1"` (Critical) |
| **SLA TTO breach** | βœ… Validated | 1 entry | **Uses `newvalue="1"` not `"yes"`** ⚠️ |
| **SLA TTR breach** | βœ… Validated | 1 entry | **Uses `newvalue="1"` not `"yes"`** ⚠️ |
| **Public comments** | βœ… Validated | 2 entries | **Separate class with `lastentry` field** πŸ” |
| **Private comments** | βœ… Validated | 1 entry | **Separate class with `lastentry` field** πŸ” |

### 🎯 Key Findings

1. **SLA breach values**: Use `'1'` (string) not `'yes'`
2. **Case logs**: Require `CMDBChangeOpSetAttributeCaseLog` class (different from scalar)
3. **Case log structure**: Has `lastentry` (index), no `oldvalue`/`newvalue`
4. **Agent assignment NULL**: Represented as string `"0"` not actual NULL
5. **User filtering**: Use `user_id != ''` (not `user_login`)

### πŸ“Š API Validation Test Queries

All queries successful:
```bash
# 1. Status changes - 7 results βœ…
curl ... "SELECT CMDBChangeOpSetAttributeScalar WHERE objclass='UserRequest' AND attcode='status'"

# 2. Agent assignments - 2 results βœ…
curl ... "SELECT CMDBChangeOpSetAttributeScalar WHERE objclass='UserRequest' AND attcode='agent_id'"

# 3. Priority changes - 1 result βœ…
curl ... "SELECT CMDBChangeOpSetAttributeScalar WHERE objclass='UserRequest' AND attcode='priority'"

# 4. SLA breaches - 2 results βœ…
curl ... "SELECT CMDBChangeOpSetAttributeScalar WHERE attcode IN ('sla_tto_passed','sla_ttr_passed')"

# 5. Case logs - 3 results βœ…
curl ... "SELECT CMDBChangeOpSetAttributeCaseLog WHERE objclass='UserRequest' AND objkey='12'"

Implementation Impact

No Architectural Changes Required βœ…

The core notification system architecture remains 100% valid:

  • βœ… CMDBChangeOp change tracking works as planned
  • βœ… Timestamp-based querying eliminates duplicate notifications
  • βœ… No per-ticket state storage needed
  • βœ… Crossing-time algorithm for SLA warnings validated
  • βœ… All notification types detectable

Code Changes Required πŸ”§

Minor adjustments only:

  1. SLA breach detection: Change value check from 'yes' to '1'
  2. Case log queries: Use CMDBChangeOpSetAttributeCaseLog class
  3. Case log parsing: Expect lastentry field, not oldvalue/newvalue
  4. Agent assignment: Compare against string "0" for NULL

Estimated impact: < 1 hour to update code examples in plan


Conclusion

Status: βœ… All notification detection mechanisms validated and working

The notification implementation plan is technically sound and ready for development. The corrections documented here are minor clarifications that don't affect the overall architecture or feasibility.

Next Steps:

  1. Update PLAN_NOTIFICATIONS.md with corrections (or reference this document)
  2. Proceed with Phase 1: Portal Notifications implementation
  3. Use validated API patterns from this document during development

Validated by: API testing against live iTop instance
Test Date: 2025-11-03
iTop Version: 3.3.0-dev
Test Tickets: R-000001, R-000002, R-000007, R-000012, I-000004