- 10 minutes to read

Change Tracking and Audit Trail

This spoke covers the change tracking system and audit trail for Mapify's multi-user collaboration, answering who changed what, when, and why. For the full overview, see the Multi-User Collaboration hub.


Why Change Tracking?

Benefit Description
Compliance audit support SOX, GDPR, HIPAA require "who changed what" audit trails
Root-cause analysis Trace configuration changes that caused incidents
Accountability Clear ownership prevents finger-pointing during outages
Rollback capability Restore previous configurations if changes cause issues
Trend analysis Identify frequently changed entities (instability hotspots)
Knowledge retention Understand historical decisions when original team members leave

Note: Change tracking is enabled by default in Nodinite 7.x. Retention policies are configurable per environment. See Audit Reports and Retention for retention settings.


Entity Audit Metadata

Every Repository Model entity (Integration, System, Service, Resource) tracks these audit metadata fields:

Field Name Data Type Description Example Value
CreatedBy String (email/username) User who created the entity alice.johnson@contoso.com
CreatedDate DateTime (UTC) When entity was created 2025-12-15T09:30:00Z
LastModifiedBy String (email/username) User who last modified the entity bob.taylor@contoso.com
LastModifiedDate DateTime (UTC) Timestamp of most recent modification 2026-01-19T14:22:35Z
RowVersion Byte[] (concurrency token) Auto-incremented version for optimistic locking AAAAAAAAB9E= (Base64)
ModificationCount Integer Total number of modifications 42

Audit Metadata in Entity Detail Panel

<div class="entity-audit-metadata" aria-label="Entity audit information">
    <h3>
        <i class="fas fa-clock-rotate-left" aria-hidden="true"></i>
        Audit Information
    </h3>
    <dl class="metadata-list">
        <dt>Created By:</dt>
        <dd>
            <a href="mailto:alice.johnson@contoso.com">Alice Johnson</a>
            on <time datetime="2025-12-15T09:30:00Z">Dec 15, 2025 at 9:30 AM</time>
        </dd>
        <dt>Last Modified By:</dt>
        <dd>
            <a href="mailto:bob.taylor@contoso.com">Bob Taylor</a>
            on <time datetime="2026-01-19T14:22:35Z">Jan 19, 2026 at 2:22 PM</time>
            <span class="modification-count" title="Total modifications">(42 edits)</span>
        </dd>
        <dt>Actions:</dt>
        <dd>
            <button onclick="showChangeHistory()" class="btn-link"
                    aria-label="View full change history">
                <i class="fas fa-list" aria-hidden="true"></i>
                View Change History
            </button>
        </dd>
    </dl>
</div>
.entity-audit-metadata {
    background-color: #f8f9fa;
    border-left: 3px solid #0056b3;
    padding: 16px;
    margin-top: 24px;
    border-radius: 4px;
}

.metadata-list {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 8px 16px;
    font-size: 14px;
}

.metadata-list dt { font-weight: 600; color: #495057; }
.metadata-list dd { color: #212529; margin: 0; }
.modification-count { color: #6c757d; font-size: 13px; }

Change History Log Structure

Field-level change history is stored in the EntityChangeLog table:

Database Schema (SQL Server)

CREATE TABLE [dbo].[EntityChangeLog]
(
    [ChangeId]     UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    [EntityType]   NVARCHAR(50)  NOT NULL,  -- 'Integration','System','Service','Resource'
    [EntityId]     UNIQUEIDENTIFIER NOT NULL,
    [EntityName]   NVARCHAR(255) NULL,  -- Denormalized for reporting performance
    [ChangeType]   NVARCHAR(20)  NOT NULL,  -- 'CREATE','UPDATE','DELETE'
    [FieldName]    NVARCHAR(100) NULL,  -- NULL for CREATE/DELETE; specific field for UPDATE
    [OldValue]     NVARCHAR(MAX) NULL,  -- Previous value (NULL for CREATE)
    [NewValue]     NVARCHAR(MAX) NULL,  -- New value (NULL for DELETE)
    [ChangedBy]    NVARCHAR(255) NOT NULL,  -- Email or username
    [ChangedDate]  DATETIME2     NOT NULL DEFAULT GETUTCDATE(),
    [ChangeReason] NVARCHAR(500) NULL,  -- Optional commit message
    [SessionId]    NVARCHAR(100) NULL,  -- Groups related changes from same save
    [IPAddress]    NVARCHAR(45)  NULL,  -- IPv4/IPv6 for security audits
    [UserAgent]    NVARCHAR(500) NULL,  -- Browser/client information

    INDEX [IX_EntityChangeLog_EntityId]   ([EntityId], [ChangedDate] DESC),
    INDEX [IX_EntityChangeLog_ChangedBy]  ([ChangedBy], [ChangedDate] DESC),
    INDEX [IX_EntityChangeLog_ChangedDate]([ChangedDate] DESC),
    INDEX [IX_EntityChangeLog_EntityType] ([EntityType], [ChangedDate] DESC)
);

API Response (JSON)

{
    "changeId": "550e8400-e29b-41d4-a716-446655440000",
    "entityType": "Integration",
    "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "entityName": "SAP to Salesforce Sync",
    "changeType": "UPDATE",
    "fieldName": "Description",
    "oldValue": "Legacy description from 2024",
    "newValue": "Updated description with GDPR compliance notes",
    "changedBy": "alice.johnson@contoso.com",
    "changedDate": "2026-01-19T14:22:35.123Z",
    "changeReason": "Added GDPR compliance documentation per legal review",
    "sessionId": "session_abc123",
    "ipAddress": "192.168.1.100",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0"
}

Change History Timeline UI

<div class="change-history-panel" role="dialog" aria-labelledby="change-history-title">
    <h2 id="change-history-title">
        <i class="fas fa-clock-rotate-left" aria-hidden="true"></i>
        Change History: Integration #123
    </h2>

    <div class="history-filters">
        <label for="user-filter">Filter by User:</label>
        <select id="user-filter" aria-label="Filter changes by user">
            <option value="">All Users</option>
            <option value="alice.johnson@contoso.com">Alice Johnson</option>
            <option value="bob.taylor@contoso.com">Bob Taylor</option>
        </select>

        <label for="date-filter">Date Range:</label>
        <select id="date-filter" aria-label="Filter changes by date range">
            <option value="7d" selected>Last 7 days</option>
            <option value="30d">Last 30 days</option>
            <option value="90d">Last 90 days</option>
        </select>
    </div>

    <div class="change-timeline" role="list" aria-label="Change history timeline">
        <!-- Change entry -->
        <div class="change-entry" role="listitem">
            <img src="avatar-alice.jpg" alt="Alice Johnson"
                 class="change-avatar" style="z-index: 1;">
            <div class="change-content">
                <div class="change-header">
                    <strong>Alice Johnson</strong>
                    <span class="change-type change-type-update">UPDATE</span>
                    <time datetime="2026-01-19T14:22:35Z">Jan 19, 2026 at 2:22 PM</time>
                </div>
                <div class="change-detail">
                    <strong>Description</strong> changed:
                    <span class="diff-old">Legacy description from 2024</span>
                    →
                    <span class="diff-new">Updated description with GDPR notes</span>
                </div>
                <div class="change-reason">
                    <i class="fas fa-comment" aria-hidden="true"></i>
                    <em>Added GDPR compliance documentation per legal review</em>
                </div>
                <button class="btn-revert" onclick="revertChange('550e8400...')"
                        aria-label="Revert this change by Alice Johnson">
                    <i class="fas fa-rotate-left" aria-hidden="true"></i> Revert
                </button>
            </div>
        </div>
    </div>
</div>
.change-timeline { position: relative; padding-left: 48px; }
.change-timeline::before {
    content: '';
    position: absolute;
    left: 16px; top: 0; bottom: 0;
    width: 2px;
    background-color: #e0e0e0;
}

.change-entry { display: flex; gap: 16px; margin-bottom: 24px; }
.change-avatar { width: 40px; height: 40px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 2px 4px rgba(0,0,0,.2); }

.change-type { padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 700; text-transform: uppercase; }
.change-type-update { background: #fff3e0; color: #e65100; }
.change-type-create { background: #e8f5e9; color: #1b5e20; }
.change-type-delete { background: #ffebee; color: #b71c1c; }

.diff-old { background-color: #ffebee; padding: 2px 4px; border-radius: 2px; text-decoration: line-through; color: #b71c1c; }
.diff-new { background-color: #e8f5e9; padding: 2px 4px; border-radius: 2px; color: #1b5e20; }
.change-reason { border-left: 3px solid #0056b3; padding-left: 8px; color: #6c757d; font-size: 13px; margin: 8px 0; }

Filter Examples: Recent Modifications

Filter Description Query Syntax Use Case
All changes in last 7 days modified:7d Weekly review
Changes by specific user lastModifiedBy:alice@contoso.com Track individual contributions
Changes since specific date modified:>2026-01-15 Compliance audit for period
Frequently modified entities modificationCount:>10 Identify high-churn integrations
Entities created in last 30 days created:30d Track new integrations
Entities not modified in 90 days modified:>90d Find stale/abandoned integrations
Combined: Alice's changes this week lastModifiedBy:alice@contoso.com AND modified:7d Targeted team review
Others' changes since my last login lastModifiedBy:NOT {currentUser} Morning catch-up

Filter UI

<div class="recent-changes-filter" role="search" aria-label="Filter by recent modifications">
    <h3><i class="fas fa-filter" aria-hidden="true"></i> Filter by Recent Changes</h3>
    <form id="change-filter-form">
        <div class="form-row">
            <label for="date-range">Modified In:</label>
            <select id="date-range" name="dateRange">
                <option value="1d">Last 24 hours</option>
                <option value="7d" selected>Last 7 days</option>
                <option value="30d">Last 30 days</option>
                <option value="90d">Last 90 days</option>
                <option value="custom">Custom date range...</option>
            </select>
        </div>
        <div class="form-row">
            <label for="modified-by">Modified By:</label>
            <select id="modified-by" name="modifiedBy" multiple aria-label="Select users">
                <option value="">All users</option>
                <option value="alice.johnson@contoso.com">Alice Johnson</option>
                <option value="bob.taylor@contoso.com">Bob Taylor</option>
            </select>
        </div>
        <div class="form-row">
            <label for="modification-count">Modification Count:</label>
            <select id="modification-count" name="modificationCount">
                <option value="">Any</option>
                <option value=">5">More than 5 edits</option>
                <option value=">10">More than 10 edits</option>
                <option value=">20">More than 20 edits</option>
            </select>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn-primary">
                <i class="fas fa-search" aria-hidden="true"></i> Apply Filter
            </button>
            <button type="reset" class="btn-secondary">Clear Filters</button>
        </div>
    </form>
    <div class="filter-results" role="status" aria-live="polite">
        <p><strong>42 entities</strong> modified in last 7 days</p>
    </div>
</div>