ITroubleTicketManagementGET Apex Interface

The ITroubleTicketManagementGET Apex interface provides extensibility for TMF621 GET operations through Apex pre- and post-hooks and GraphQL query customization. Implementations can validate incoming requests, modify query structures, and refine the response to support business-specific retrieval rules while maintaining TMF-compliant behavior.

This interface supports the following hooks.

The configureDefaultValidations hook is not used for the Trouble Ticket Management API. If implemented, the method may be invoked, but its return values are ignored.

Note

Retrieval Lifecycle Use Cases and Hook Mapping 

This table lists common use cases and the hooks required for the GET operation.

Use CaseHook(s)Description
Example Scenarios
Benefit
Role-Based Data FilteringapplyCustomValidations, handlePostOperationEnforces access rules by validating user permissions before execution and filtering sensitive data after retrieval. Supports role-based visibility at field and record levels.• Restrict access to VIP or high-value customer profiles• Hide sensitive fields (e.g., credit score) for frontline agents• Enforce region-based access boundariesStrengthens data governance and ensures secure, compliant access to trouble tickets
Custom Field EnrichmenthandlePostOperationAdds calculated or derived fields to API responses without changing the base TMF schema, allowing customers to extend or modify as needed. Enables enrichment using internal business rules or external systems.• Add customer health score• Flag “preferred customer” status• Include aggregated financial or usage insightsDelivers richer, more contextual responses while preserving TMF schema integrity
Dynamic Query CustomizationcustomiseGraphQLQueryModifies GraphQL queries dynamically based on client or request context by adding fields, filters, or transformations.• Mobile app requests only essential fields• Portal requires additional entitlement or hierarchy fields• Apply filters automatically for partner channelsSupports diverse client requirements using a single API and reduces need for API versioning
Business Rule ValidationapplyCustomValidationsApplies business and eligibility rules before processing requests, stopping invalid operations early in the lifecycle.• Ensure customer is “active” before retrieving services• Reject unauthorized or unverified channel requests• Enforce lifecycle or dependency checksProvides strong business consistency and reduces downstream errors by blocking invalid requests early
CaseNumber to Id ResolutiontransformRequestResolve CaseNumber identifiers to Salesforce Ids for API compatibility.• An external system sends a CaseNumber (e.g., 00001027), which is resolved to a Salesforce record ID for API processingSupport external systems that use CaseNumber instead of Salesforce Id

transformRequest 

This hook transforms the request context before processing any operation. It is invoked early in the request lifecycle, allowing implementers to adjust the context for subsequent extensibility hooks.

Hook Method 

Map<String, Object> transformRequest(Map<String, Object> context)

Sample Apex Implementation 

1public Map<String, Object> transformRequest(Map<String, Object> context) {
2    if (context == null) {
3        return context;
4    }
5    Object idObj = context.get('id');
6    if (idObj == null || !(idObj instanceof String)) {
7        return context;
8    }
9    String idValue = (String)idObj;
10   // Check if the ID looks like a Salesforce ID (starts with 500, 00X, etc.)
11    // If not, assume it's a CaseNumber and resolve it
12    if (idValue.length() == 15 || idValue.length() == 18) {
13        // Looks like a Salesforce ID, return context as-is
14        return context;
15    }
16    // Query Case objects where CaseNumber matches the provided ID
17    List<Case> cases = [
18        SELECT Id, CaseNumber
19        FROM Case
20        WHERE CaseNumber = :idValue
21        LIMIT 1
22    ];
23    // If found, replace the ID in context with the Salesforce ID
24    if (!cases.isEmpty() && cases[0].CaseNumber != null) {
25        context.put('id', cases[0].Id);
26    }
27    return context;
28}

applyCustomValidations 

This hook validates custom business logic before retrieving trouble ticket data. If validation fails, it rejects the request and returns an error response.

The handler processes return values as follows.

Success:

  • Return a map with validationStatus set to “pass” (case-insensitive).
  • The API request continues normally.
  • Other fields in the map are logged but not used.

Failure:

  • Return a map with validationStatus set to “fail” (case-insensitive).
  • The API request is terminated immediately.
  • Raise a ValidationException containing
    • message: value from validationMessage key (or “Validation failed” by default)
    • details: value from validationDetails key (optional)
  • The client receives an HTTP 400 error response.

Hook Method 

Map<String, Object> applyCustomValidations(Map<String, Object> context)

Sample Apex Implementation 

1public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
2    // Input context:
3    // {
4    //   api: 'TroubleTicketManagement',
5    //   version: '5.0',
6    //   id: '00001027',
7    //   userId: '005xx000001Sv5',
8    //   fields: ['Id', 'Subject', 'Status']
9    // }
10
11    String troubleTicketId = (String) context.get('id');
12    String userId = (String) context.get('userId');
13
14    Map<String, Object> validationResult = new Map<String, Object>();
15
16    try {
17        // Check if user has access to this trouble ticket
18        User currentUser = [SELECT Id, Profile.Name FROM User WHERE Id = :userId LIMIT 1];
19        Case troubleTicket = [SELECT Id, Status, CaseNumber FROM Case WHERE CaseNumber = :troubleTicketId LIMIT 1];
20
21        if ('Customer Service Rep'.equals(currentUser.Profile.Name)) {
22            // Validate that Customer Service Reps cannot access closed tickets
23            if ('Closed'.equals(troubleTicket.Status)) {
24                // FAIL: User does not have access to closed tickets
25                validationResult.put('validationStatus', 'FAIL');
26                validationResult.put('validationMessage', 'User does not have access to closed trouble tickets');
27                validationResult.put('validationDetails', new Map<String, Object>{
28                    'denialReason' => 'STATUS_RESTRICTION',
29                    'userProfile' => 'Customer Service Rep',
30                    'ticketStatus' => troubleTicket.Status
31                });
32            } else {
33                // PASS: User has access to non-closed tickets
34                validationResult.put('validationStatus', 'PASS');
35                validationResult.put('validationMessage', 'User has access to this trouble ticket');
36            }
37        } else {
38            // PASS: Other profiles have unrestricted access
39            validationResult.put('validationStatus', 'PASS');
40            validationResult.put('validationMessage', 'User profile has unrestricted access');
41        }
42    } catch (Exception e) {
43        // FAIL: Error during validation
44        validationResult.put('validationStatus', 'FAIL');
45        validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
46        validationResult.put('validationDetails', new Map<String, Object>{
47            'errorType' => e.getTypeName()
48        });
49    }
50
51    // Return validation result directly (not wrapped in 'result' key)
52    return validationResult;
53}

customiseGraphQLQuery 

This hook modifies the GraphQL query before execution to add fields, filters, or transformations. Returns a map with a nodes key containing a list of QueryTransformationNode objects.

Hook Method 

Map<String, Object> customiseGraphQLQuery(Map<String, Object> graphQLAsMap, Map<String, Object> context)

Sample Apex Implementation 

1public Map<String, Object> customiseGraphQLQuery(
2    Map<String, Object> graphQLAsMap,
3    Map<String, Object> context
4) {
5    // Create transformation specification to add custom fields and filters
6    List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();
7
8    // Node 1: Add custom fields to the Case node
9    // Path: troubleTicket (the Case alias in the GraphQL query)
10    Map<String, Object> addFieldsNode = new Map<String, Object>{
11        'path' => 'troubleTicket',
12        'addFields' => new List<String>{'IsClosed', 'SlaStartDate', 'SlaExitDate'}
13    };
14    transformationNodes.add(addFieldsNode);
15
16    // Node 2: Add filters to the Case query
17    // Only retrieve open trouble tickets
18    Map<String, Object> filterNode = new Map<String, Object>{
19        'path' => 'troubleTicket',
20        'addFilters' => new Map<String, Object>{
21            'clearExistingFilters' => false,
22            'filterGraphQlSnippet' => '{ Status: { eq: "Open" } }'
23        }
24    };
25    transformationNodes.add(filterNode);
26
27    // Return transformation specification
28    return new Map<String, Object>{
29        'nodes' => transformationNodes
30    };
31}

handlePostOperation 

This hook post-processes and transforms the API response after data retrieval.

Hook Method 

Map<String, Object> handlePostOperation(Map<String, Object> graphQLQueryResultAsMap, Map<String, Object> constructedTMFResponse, Map<String, Object> context)

Sample Apex Implementation 

1public Map<String, Object> handlePostOperation(
2    Map<String, Object> graphQLQueryResultAsMap,
3    Map<String, Object> constructedTMFResponse,
4    Map<String, Object> context
5) {
6    // Input GraphQL result:
7    // {
8    //   items: [{ Id: '500xx000003ABC', Subject: 'Network Issue', Status: 'Open', CreatedDate: '2024-01-15T10:00:00Z' }],
9    //   count: 1
10    // }
11
12    // Input TMF response:
13    // {
14    //   recordList: [{ id: '00001027', name: 'Network Issue', status: 'Open', type: 'TroubleTicket' }]
15    // }
16
17    List<Object> troubleTicketList = (List<Object>) constructedTMFResponse.get('recordList');
18
19    if (troubleTicketList != null && !troubleTicketList.isEmpty()) {
20        for (Object ticketObj : troubleTicketList) {
21            Map<String, Object> ticket = (Map<String, Object>) ticketObj;
22            String ticketId = (String) ticket.get('id');
23
24            // Fetch additional data for enrichment
25            Case caseRecord = [SELECT Id, CaseNumber, CreatedDate, ClosedDate, Status FROM Case WHERE CaseNumber = :ticketId LIMIT 1];
26
27            // Add calculated fields
28            if (caseRecord.CreatedDate != null) {
29                DateTime createdDate = caseRecord.CreatedDate;
30                DateTime now = DateTime.now();
31                Long hoursOpen = (now.getTime() - createdDate.getTime()) / (1000 * 60 * 60);
32                ticket.put('hoursOpen', hoursOpen);
33            }
34
35            if (caseRecord.ClosedDate != null && caseRecord.CreatedDate != null) {
36                Long resolutionTime = (caseRecord.ClosedDate.getTime() - caseRecord.CreatedDate.getTime()) / (1000 * 60 * 60);
37                ticket.put('resolutionTimeHours', resolutionTime);
38            }
39
40            // Add SLA status
41            if ('Open'.equals(caseRecord.Status)) {
42                ticket.put('slaStatus', 'In Progress');
43            } else if ('Closed'.equals(caseRecord.Status)) {
44                ticket.put('slaStatus', 'Resolved');
45            }
46
47            // Add audit information
48            ticket.put('lastRetrievedAt', DateTime.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
49            ticket.put('retrievedBy', UserInfo.getName());
50        }
51    }
52
53    // Return enriched response
54    return constructedTMFResponse;
55
56    // Output response:
57    // {
58    //   recordList: [{
59    //     id: '00001027',
60    //     name: 'Network Issue',
61    //     status: 'Open',
62    //     type: 'TroubleTicket',
63    //     hoursOpen: 48,
64    //     slaStatus: 'In Progress',
65    //     lastRetrievedAt: '2024-01-17T10:30:00Z',
66    //     retrievedBy: 'John Smith'
67    //   }]
68    // }
69}

Full Implementation Example 

Here’s a complete sample Apex implementation using all supported hooks.

1/**
2 * Complete Trouble Ticket Management GET API Extension
3 * Demonstrates all applicable lifecycle hooks with business logic
4 */
5public class TroubleTicketManagementGETExtension implements comms_apex_ext.ITroubleTicketManagementGET {
6
7    /**
8     * Hook 1: Transform request
9     */
10    public Map<String, Object> transformRequest(Map<String, Object> context) {
11        if (context == null) {
12            return context;
13        }
14
15        Object idObj = context.get('id');
16        if (idObj != null && idObj instanceof String) {
17            String idValue = (String)idObj;
18
19            // If not a Salesforce ID, resolve CaseNumber to Id
20            if (idValue.length() != 15 && idValue.length() != 18) {
21                List<Case> cases = [
22                    SELECT Id, CaseNumber
23                    FROM Case
24                    WHERE CaseNumber = :idValue
25                    LIMIT 1
26                ];
27
28                if (!cases.isEmpty()) {
29                    context.put('id', cases[0].Id);
30                }
31            }
32        }
33
34        return context;
35    }
36
37    /**
38     * Hook 2: Apply custom validations
39     */
40    public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
41        String troubleTicketId = (String) context.get('id');
42        String userId = (String) context.get('userId');
43
44        Map<String, Object> validationResult = new Map<String, Object>();
45
46        try {
47            User currentUser = [SELECT Id, Profile.Name FROM User WHERE Id = :userId LIMIT 1];
48            Case troubleTicket = [SELECT Id, Status, CaseNumber FROM Case WHERE CaseNumber = :troubleTicketId LIMIT 1];
49
50            if ('Customer Service Rep'.equals(currentUser.Profile.Name)) {
51                if ('Closed'.equals(troubleTicket.Status)) {
52                    validationResult.put('validationStatus', 'FAIL');
53                    validationResult.put('validationMessage', 'Access denied - closed tickets not accessible');
54                    validationResult.put('validationDetails', new Map<String, Object>{
55                        'denialReason' => 'STATUS_RESTRICTION'
56                    });
57                } else {
58                    validationResult.put('validationStatus', 'PASS');
59                    validationResult.put('validationMessage', 'User has access to this trouble ticket');
60                }
61            } else {
62                validationResult.put('validationStatus', 'PASS');
63                validationResult.put('validationMessage', 'User profile has unrestricted access');
64            }
65        } catch (Exception e) {
66            validationResult.put('validationStatus', 'FAIL');
67            validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
68        }
69
70        return validationResult;
71    }
72
73    /**
74     * Hook 3: Customize GraphQL query
75     * Uses transformation syntax from GRAPHQL_QUERY_TRANSFORMATION.md
76     */
77    public Map<String, Object> customiseGraphQLQuery(
78        Map<String, Object> graphQLAsMap,
79        Map<String, Object> context
80    ) {
81        List<Map<String, Object>> transformationNodes = new List<Map<String, Object>>();
82
83        // Add custom fields to the Case node
84        // Path: troubleTicket (the Case alias in the GraphQL query)
85        transformationNodes.add(new Map<String, Object>{
86            'path' => 'troubleTicket',
87            'addFields' => new List<String>{'IsClosed', 'SlaStartDate', 'SlaExitDate'}
88        });
89
90        // Add filters to the Case query
91        // Only retrieve open trouble tickets
92        transformationNodes.add(new Map<String, Object>{
93            'path' => 'troubleTicket',
94            'addFilters' => new Map<String, Object>{
95                'clearExistingFilters' => false,
96                'filterGraphQlSnippet' => '{ Status: { eq: "Open" } }'
97            }
98        });
99
100        return new Map<String, Object>{ 'nodes' => transformationNodes };
101    }
102
103    /**
104     * Hook 4: Post-process response
105     */
106    public Map<String, Object> handlePostOperation(
107        Map<String, Object> graphQLQueryResultAsMap,
108        Map<String, Object> constructedTMFResponse,
109        Map<String, Object> context
110    ) {
111        List<Object> troubleTicketList = (List<Object>) constructedTMFResponse.get('recordList');
112
113        if (troubleTicketList != null) {
114            for (Object ticketObj : troubleTicketList) {
115                Map<String, Object> ticket = (Map<String, Object>) ticketObj;
116                String ticketId = (String) ticket.get('id');
117
118                try {
119                    Case caseRecord = [SELECT Id, CaseNumber, CreatedDate, Status FROM Case WHERE CaseNumber = :ticketId LIMIT 1];
120
121                    if (caseRecord.CreatedDate != null) {
122                        DateTime createdDate = caseRecord.CreatedDate;
123                        DateTime now = DateTime.now();
124                        Long hoursOpen = (now.getTime() - createdDate.getTime()) / (1000 * 60 * 60);
125                        ticket.put('hoursOpen', hoursOpen);
126                    }
127
128                    if ('Open'.equals(caseRecord.Status)) {
129                        ticket.put('slaStatus', 'In Progress');
130                    } else if ('Closed'.equals(caseRecord.Status)) {
131                        ticket.put('slaStatus', 'Resolved');
132                    }
133                } catch (Exception e) {
134                    System.debug('Error enriching trouble ticket data: ' + e.getMessage());
135                }
136            }
137        }
138
139        return constructedTMFResponse;
140    }
141
142    /**
143     * NOT APPLICABLE: configureDefaultValidations is not used for Trouble Ticket Management API
144     * If implemented, this method will be invoked but return values will be ignored.
145     */
146    public Map<String, Boolean> configureDefaultValidations(
147        Map<String, Boolean> defaultValidationConfiguration,
148        Map<String, Object> context
149    ) {
150        // This hook is not applicable for Trouble Ticket Management API
151        // Return the configuration unchanged
152        return defaultValidationConfiguration;
153    }
154}

Response Structure 

Single Trouble Ticket Response (GraphQL) 

1{
2  "id": "00001027",
3  "name": "Network connectivity issue",
4  "type": "TroubleTicket",
5  "href": "/services/data/v67.0/connect/comms/troubleticket/v5/troubleticket/00001027",
6  "description": "Users reporting intermittent network connectivity",
7  "status": "Open",
8  "ticketType": "Network",
9  "priority": "High",
10  "creationDate": "2024-01-15T10:00:00Z",
11  "lastUpdate": "2024-01-16T14:30:00Z",
12  "resolutionDate": null,
13  "note": [
14    {
15      "id": "00a5f000001ABC",
16      "text": "Initial investigation started",
17      "date": "2024-01-15T10:15:00Z"
18    }
19  ],
20  "relatedParty": [
21    {
22      "id": "001xx000003ABC",
23      "name": "Acme Corporation",
24      "role": "Customer",
25      "href": "/services/data/v67.0/sobjects/Account/001xx000003ABC"
26    }
27  ]
28}

List Response (GraphQL) 

1{
2  "recordList": [
3    {
4      "id": "00001027",
5      "name": "Network connectivity issue",
6      "type": "TroubleTicket",
7      "href": "/services/data/v67.0/connect/comms/troubleticket/v5/troubleticket/00001027",
8      "status": "Open"
9    },
10    {
11      "id": "00001028",
12      "name": "Server performance degradation",
13      "type": "TroubleTicket",
14      "href": "/services/data/v67.0/connect/comms/troubleticket/v5/troubleticket/00001028",
15      "status": "Open"
16    }
17  ],
18  "pageInfo": {
19    "startCursor": "YXJyYXljb25uZWN0aW9uOjA=",
20    "endCursor": "YXJyYXljb25uZWN0aW9uOjE="
21  }
22}

GraphQL Query Transformations 

Query transformations provide the ability to enhance GraphQL queries by adding fields, applying filters, or modifying the sort order, while preserving the original query definition.

For the Customer Management API, use the uiapi.query.Account path to reference the primary Account field.

Note

Query Enhancements: Fields and Filters 

Original Query (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5}
6}

Transformation Instructions (JSON):

1{
2"nodes": [
3{
4"path": "myAccounts",
5"addFields": ["email", "phone"],
6"addFilters": {
7"clearExistingFilters": true,
8"filterGraphQlSnippet": "{ status: { eq: \"ACTIVE\" } }"
9}
10}
11]
12}

Result (GraphQL):

1query {
2myAccounts: accounts(where: { status: { eq: "ACTIVE" } }) {
3id
4name
5email
6phone
7}
8}

Query Enhancements: Fields, Filters, and Ordering 

Original Query (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5}
6}

Transformation Instructions (JSON):

1{
2"nodes": [
3{
4"path": "myAccounts",
5"addFields": ["revenue", "industry"],
6"addFilters": {
7"clearExistingFilters": true,
8"filterGraphQlSnippet": "{
9and: [
10{ status: { eq: \"ACTIVE\" } },
11{ revenue: { gte: 1000000 } }
12]
13}"
14},
15"orderBy": {
16"clearExistingOrderBy": true,
17"orderGraphQlSnippet": "{ revenue: { order: DESC } }"
18}
19}
20]
21}

Result (GraphQL):

1query {
2myAccounts: accounts(
3where: {
4and: [
5{ status: { eq: "ACTIVE" } },
6{ revenue: { gte: 1000000 } }
7]
8},
9orderBy: { revenue: { order: DESC } }
10) {
11id
12name
13revenue
14industry
15}
16}

Query Enhancements: Multiple Query Paths with Different Transformations 

Original Query (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5myContacts: contacts {
6id
7name
8}
9}
10}

Transformation Instructions (JSON):

1{
2"nodes": [
3{
4"path": "myAccounts",
5"addFields": ["email"],
6"addFilters": {
7"clearExistingFilters": true,
8"filterGraphQlSnippet": "{ status: { eq: \"ACTIVE\" } }"
9}
10},
11{
12"path": "myAccounts.myContacts",
13"addFields": ["email", "phone"],
14"orderBy": {
15"clearExistingOrderBy": true,
16"orderGraphQlSnippet": "{ name: { order: ASC } }"
17}
18}
19]
20}

Result (GraphQL):

1query {
2myAccounts: accounts(where: { status: { eq: "ACTIVE" } }) {
3id
4name
5email
6myContacts: contacts(orderBy: { name: { order: ASC } }) {
7id
8name
9email
10phone
11}
12}
13}

Query Enhancements: Fields and Snippets 

Original Query (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5}
6}

Transformation Instructions (JSON):

1{
2"nodes": [
3{
4"path": "myAccounts",
5"addFields": ["email"]
6},
7{
8"path": "metrics",
9"insertGraphQlSnippet": "{
10metrics: systemMetrics {
11totalCount
12activeCount
13lastSyncTime
14}
15}"
16}
17]
18}

Result (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5email
6}
7metrics: systemMetrics {
8totalCount
9activeCount
10lastSyncTime
11}
12}