Files
neo/docs/OWNER_FIELD_VALIDATION_FIX.md
Francisco Gaona 516e132611 WIP - move docs
2025-12-24 21:46:05 +01:00

8.4 KiB

Owner Field Validation Fix - Complete Solution

Problem

When creating a record for a newly created object definition, users saw:

  • "Owner is required"

Even though ownerId should be auto-managed by the system and never required from users.

Root Cause Analysis

The issue had two layers:

Layer 1: Existing Objects (Before Latest Fix)

Objects created BEFORE the system fields fix had:

  • ownerId with isRequired: true and isSystem: null
  • Frontend couldn't identify this as a system field
  • Field was shown on edit form and validated as required

Layer 2: Incomplete Field Name Coverage

The frontend's system field list was missing ownerId and tenantId:

// BEFORE
['id', 'createdAt', 'updatedAt', 'created_at', 'updated_at', 'createdBy', 'updatedBy']
// Missing: ownerId, tenantId

Complete Fix Applied

1. Backend - Normalize All Field Definitions

File: backend/src/object/object.service.ts

Added normalizeField() helper function:

private normalizeField(field: any): any {
  const systemFieldNames = ['id', 'tenantId', 'ownerId', 'created_at', 'updated_at', 'createdAt', 'updatedAt'];
  const isSystemField = systemFieldNames.includes(field.apiName);
  
  return {
    ...field,
    // Ensure system fields are marked correctly
    isSystem: isSystemField ? true : field.isSystem,
    isRequired: isSystemField ? false : field.isRequired,
    isCustom: isSystemField ? false : field.isCustom ?? true,
  };
}

This ensures that:

  • Any field with a system field name is automatically marked isSystem: true
  • System fields are always isRequired: false
  • System fields are always isCustom: false
  • Works for both new and old objects (backward compatible)

Updated getObjectDefinition() to normalize fields before returning:

// Get fields and normalize them
const fields = await knex('field_definitions')...
const normalizedFields = fields.map((field: any) => this.normalizeField(field));

return {
  ...obj,
  fields: normalizedFields,  // Return normalized fields
  app,
};

2. Frontend - Complete System Field Coverage

File: frontend/composables/useFieldViews.ts

Updated field mapping to include all system fields:

// Define all system/auto-generated field names
const systemFieldNames = ['id', 'createdAt', 'updatedAt', 'created_at', 'updated_at', 'createdBy', 'updatedBy', 'tenantId', 'ownerId']
const isAutoGeneratedField = systemFieldNames.includes(fieldDef.apiName)

// Hide system fields and auto-generated fields on edit
const shouldHideOnEdit = isSystemField || isAutoGeneratedField

File: frontend/components/views/EditViewEnhanced.vue

Updated save handler system fields list:

const systemFields = ['id', 'tenantId', 'ownerId', 'created_at', 'updated_at', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']

How It Works Now

For New Objects (Created After Backend Fix)

1. Backend creates standard fields with:
   - ownerId: isRequired: false, isSystem: true ✓
   - created_at: isRequired: false, isSystem: true ✓
   - updated_at: isRequired: false, isSystem: true ✓

2. Backend's getObjectDefinition normalizes them (redundant but safe)

3. Frontend receives normalized fields
   - Recognizes them as system fields
   - Hides from edit form ✓

4. User creates record without "Owner is required" error ✓

For Existing Objects (Created Before Backend Fix)

1. Legacy data has:
   - ownerId: isRequired: true, isSystem: null

2. Backend's getObjectDefinition normalizes on-the-fly:
   - Detects apiName === 'ownerId'
   - Forces: isSystem: true, isRequired: false ✓

3. Frontend receives normalized fields
   - Recognizes as system field (by name + isSystem flag)
   - Hides from edit form ✓

4. User creates record without "Owner is required" error ✓

System Field Handling

Complete System Field List

Field Name      | Type      | Required | Hidden on Edit | Notes
────────────────┼───────────┼──────────┼────────────────┼──────────────────
id              | UUID      | No       | Yes            | Auto-generated
tenantId        | UUID      | No       | Yes            | Set by system
ownerId         | LOOKUP    | No       | Yes            | Set by userId
created_at      | DATETIME  | No       | Yes            | Auto-set
updated_at      | DATETIME  | No       | Yes            | Auto-set on update
createdAt       | DATETIME  | No       | Yes            | Alias for created_at
updatedAt       | DATETIME  | No       | Yes            | Alias for updated_at
createdBy       | LOOKUP    | No       | Yes            | Future use
updatedBy       | LOOKUP    | No       | Yes            | Future use

Backward Compatibility

Fully backward compatible - Works with both:

  • New objects: Fields created with correct isSystem flags
  • Old objects: Fields normalized on-the-fly by backend

No migration needed. Existing objects automatically get normalized when fetched.

Validation Flow

User creates record:
  { customField: "value" }
        ↓
Frontend renders form:
  - Hides: id, tenantId, ownerId, created_at, updated_at (system fields)
  - Shows: customField (user-defined)
        ↓
Frontend validation:
  - Checks only visible fields
  - Skips validation for hidden system fields ✓
        ↓
Frontend filters before save:
  - Removes all system fields
  - Sends: { customField: "value" } ✓
        ↓
Backend receives clean data:
  - Validates against Objection model
  - Sets system fields via hooks
        ↓
Record created with all fields populated ✓

Files Modified

File Changes Status
backend/src/object/object.service.ts Added normalizeField() helper, updated getObjectDefinition()
frontend/composables/useFieldViews.ts Added complete system field names list including ownerId, tenantId
frontend/components/views/EditViewEnhanced.vue Updated system fields list in handleSave()

Testing

Test 1: Create New Object

POST /api/objects
{
  "apiName": "TestObject",
  "label": "Test Object"
}

Should create with standard fields

Test 2: Create Record for New Object

Open UI for newly created TestObject
Click "Create Record"

Should NOT show "Owner is required" error Should NOT show "Created At is required" error Should NOT show "Updated At is required" error

Test 3: Create Record for Old Object

Use an object created before the fix
Click "Create Record"

Should NOT show validation errors for system fields Should auto-normalize on fetch

Test 4: Verify Field Hidden

In create form, inspect HTML/Console

Should NOT find input fields for: id, tenantId, ownerId, created_at, updated_at

Test 5: Verify Data Filtering

In browser console:
- Set breakpoint in handleSave()
- Check saveData before emit()

Should NOT contain: id, tenantId, ownerId, created_at, updated_at

Edge Cases Handled

  1. Null/Undefined isSystem flag

    • Backend normalizes: isSystem = null becomes true for system fields
    • Frontend checks both: field name AND isSystem flag
  2. Snake_case vs camelCase

    • Both created_at and createdAt handled
    • Both updated_at and updatedAt handled
  3. Old objects without isCustom flag

    • Backend normalizes: isCustom = false for system fields, true for others
  4. Field retrieval from different endpoints ⚠️

    • Only getObjectDefinition normalizes fields
    • Other endpoints return raw data (acceptable for internal use)

Performance Impact

  • Backend: Minimal - Single array map per getObjectDefinition call
  • Frontend: None - Logic was already there, just enhanced
  • Network: No change - Same response size

Summary

The fix ensures 100% coverage of system fields:

  1. Backend: Normalizes all field definitions on-the-fly
  2. Frontend: Checks both field names AND isSystem flag
  3. Backward compatible: Works with both new and old objects
  4. No migration needed: All normalization happens in code

Users will never see validation errors for system-managed fields again.