Build Agents usecase #1 - Create GraphQL setup to read Vulnerable item data

VaranAwesomenow
Mega Sage

I used build agent to help with implementing following requirement.

Show a UI page that summarizes the VIT, State change approval and Survey questionnaire details.

Components used sys_ux_screen, Graphql data broker, Data broker transform, GraphQL API

Build agents used : I started with IBM Bob which built entire end to end read me file to implement step by step which I have mentioned below then I used Codex to review and validate the code help fix defects.

 

 

 

 

# ServiceNow GraphQL Setup Guide for Vulnerable Item Data

## Overview
This guide walks you through setting up a GraphQL API in ServiceNow to query vulnerable item data from the `sn_vul_vulnerable_item` table.

## Setup Workflow

```
┌─────────────────────────────────────────────────────────────┐
│ Step 1: Activate GraphQL Plugin                             │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 2: Create GraphQL API Schema                           │
│  - Define API name and namespace                            │
│  - Write GraphQL schema (types, queries)                    │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 3: Define GraphQL Schema (Types & Queries)             │
│  - VulnerableItem, Vulnerability, User, Group types         │
│  - Query operations                                          │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 4: Create Type Mappings ⚠️ CRITICAL                    │
│  - Map GraphQL types to ServiceNow tables                   │
│  - Map GraphQL fields to table columns                      │
│  - Configure reference field relationships                   │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 5: Create Scripted Resolvers                           │
│  - getVulnerableItem (single record)                        │
│  - getVulnerableItems (multiple with filters)               │
│  - getVulnerableItemsByVulnerability                        │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 6: Configure Security (ACLs)                           │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 7: Test in GraphQL Explorer                            │
└─────────────────────────────────────────────────────────────┘
```

## Prerequisites
- ServiceNow instance with Vulnerability Response application installed
- Admin or appropriate role to create GraphQL schemas
- GraphQL plugin activated (com.snc.graphql)

## Step 1: Activate GraphQL Plugin

1. Navigate to **System Definition > Plugins**
2. Search for "GraphQL" (Plugin ID: `com.snc.graphql`)
3. Click **Activate/Upgrade Related Plugins**
4. Wait for activation to complete

## Step 2: Create GraphQL Schema

### 2.1 Navigate to GraphQL Schema
1. Go to **System Web Services > GraphQL > GraphQL APIs**
2. Click **New**

### 2.2 Configure Schema
```
Name: Vulnerable Item API
API ID: vulnerable_item_api
Active: true
Namespace: sn_vul
```

## Step 3: Define GraphQL Schema Definition

### 3.1 Basic Schema for Vulnerable Items

```graphql
type VulnerableItem {
  sys_id: String!
  number: String
  vulnerability: Vulnerability
  vulnerable_item: String
  state: String
  priority: String
  short_description: String
  description: String
  work_notes: String
  close_notes: String
  assigned_to: User
  assignment_group: Group
  opened_at: String
  closed_at: String
  sys_created_on: String
  sys_updated_on: String
  # Related data
  state_change_approvals: [StateChangeApproval]
  assessment_responses: [AssessmentResponse]
}

type Vulnerability {
  sys_id: String!
  number: String
  short_description: String
  state: String
  priority: String
  # Related data
  state_change_approvals: [StateChangeApproval]
  assessment_responses: [AssessmentResponse]
}

type StateChangeApproval {
  sys_id: String!
  name: String
  state: String
  approval: String
  approver: User
  comments: String
  due_date: String
  sys_created_on: String
  sys_updated_on: String
  # Parent references
  vulnerable_item: String
  vulnerability: String
}

type ChangeApproval {
  sys_id: String!
  number: String
  name: String
  active: Boolean
  approval_state: String
  approval_state_display: String
  approval_rule: String
  description: String
  current_value: String
  desired_field: String
  desired_value: String
  desired_state: String
  desired_state_display: String
  desired_substate: String
  desired_substate_display: String
  desired_reason: String
  desired_ignore_date: String
  desired_validity_date: String
  compensating_controls: String
  rejected_reason: String
  rejected_by: User
  requested_by: User
  vul_record: String
  vul_table_name: String
  stage: String
  sys_created_on: String
  sys_updated_on: String
  sys_created_by: String
  # Related assessment data
  assessment_instance: AssessmentInstance
  approvers_list: String
}

type AssessmentInstance {
  sys_id: String!
  number: String
  state: String
  state_display: String
  metric_type: String
  metric_type_display: String
  due_date: String
  expiration_date: String
  taken_on: String
  percent_answered: Float
  user: User
  sys_created_on: String
  sys_updated_on: String
  # Related questions
  questions: [AssessmentQuestion]
}

type AssessmentQuestion {
  sys_id: String!
  metric: String
  metric_display: String
  category: String
  category_display: String
  string_value: String
  value: Float
  add_info: String
  is_hidden: Boolean
  source_id: String
  source_table: String
  sys_created_on: String
  sys_updated_on: String
}

type AssessmentResponse {
  sys_id: String!
  metric: String
  metric_type: String
  question: String
  response: String
  string_value: String
  numeric_value: Float
  date_value: String
  add_info: String
  sys_created_on: String
  sys_updated_on: String
  # Parent references
  vulnerable_item: String
  vulnerability: String
}

type User {
  sys_id: String!
  name: String
  email: String
  user_name: String
}

type Group {
  sys_id: String!
  name: String
}

type Query {
  # Get single vulnerable item by sys_id
  getVulnerableItem(sys_id: String!): VulnerableItem
  
  # Get multiple vulnerable items with filters
  getVulnerableItems(
    limit: Int = 10
    offset: Int = 0
    state: String
    priority: String
    assigned_to: String
  ): [VulnerableItem]
  
  # Get vulnerable items by vulnerability
  getVulnerableItemsByVulnerability(
    vulnerability_sys_id: String!
    limit: Int = 10
  ): [VulnerableItem]
  
  # Get state change approvals
  getStateChangeApprovals(
    vulnerable_item_sys_id: String
    vulnerability_sys_id: String
    state: String
    limit: Int = 10
  ): [StateChangeApproval]
  
  # Get assessment responses
  getAssessmentResponses(
    vulnerable_item_sys_id: String
    vulnerability_sys_id: String
    metric_type: String
    limit: Int = 10
  ): [AssessmentResponse]
  
  # Get change approvals (exception requests, waivers, etc.)
  getChangeApprovals(
    vul_record_sys_id: String
    vul_table_name: String
    approval_state: String
    active: Boolean
    limit: Int = 10
  ): [ChangeApproval]
  
  # Get single change approval by sys_id
  getChangeApproval(sys_id: String!): ChangeApproval
  
  # Get assessment instance by sys_id
  getAssessmentInstance(sys_id: String!): AssessmentInstance
  
  # Get assessment questions for an instance
  getAssessmentQuestions(
    instance_sys_id: String!
    limit: Int = 50
  ): [AssessmentQuestion]
}
```

## Step 4: Create Type Mappings

Type mappings are **critical** - they define how GraphQL types map to ServiceNow tables and fields. Without proper mappings, your queries won't return data correctly.

### 4.1 Navigate to Type Mappings
1. Go to **System Web Services > GraphQL > Type Mappings**
2. Click **New** to create mappings for each type

### 4.2 Create VulnerableItem Type Mapping

**Why This Matters:** This mapping tells ServiceNow how to translate GraphQL queries into database queries on the `sn_vul_vulnerable_item` table.

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** VulnerableItem
   - **GraphQL Type:** VulnerableItem
   - **Table:** sn_vul_vulnerable_item
   - **Active:** ✓ (checked)
3. Click **Submit**
4. Open the newly created mapping record
5. Scroll to **Field Mappings** related list
6. Click **New** to add each field mapping

**Field Mappings to Create:**

For each field below, click **New** in Field Mappings and fill:

| Order | GraphQL Field | Table Field | Field Type | Notes |
|-------|--------------|-------------|------------|-------|
| 100 | sys_id | sys_id | String | Primary key |
| 200 | number | number | String | Display value |
| 300 | vulnerable_item | vulnerable_item | String | Item reference |
| 400 | state | state | String | Workflow state |
| 500 | priority | priority | String | Priority level |
| 600 | short_description | short_description | String | Brief description |
| 700 | description | description | String | Full description |
| 800 | work_notes | work_notes | String | Work notes |
| 900 | close_notes | close_notes | String | Closure notes |
| 1000 | assigned_to | assigned_to | Reference | Links to User type |
| 1100 | assignment_group | assignment_group | Reference | Links to Group type |
| 1200 | vulnerability | vulnerability | Reference | Links to Vulnerability type |
| 1300 | opened_at | opened_at | String | Date/time opened |
| 1400 | closed_at | closed_at | String | Date/time closed |
| 1500 | sys_created_on | sys_created_on | String | Creation timestamp |
| 1600 | sys_updated_on | sys_updated_on | String | Update timestamp |

**Important Notes:**
- For **Reference** fields (assigned_to, assignment_group, vulnerability), you must also specify the **Referenced Type** in the field mapping
- The **Order** field determines the sequence of field resolution
- Reference fields will automatically resolve to their mapped types

### 4.3 Create Vulnerability Type Mapping

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** Vulnerability
   - **GraphQL Type:** Vulnerability
   - **Table:** sn_vul_vulnerability
   - **Active:** ✓ (checked)
3. Click **Submit** and open the record
4. Add Field Mappings:

| Order | GraphQL Field | Table Field | Field Type |
|-------|--------------|-------------|------------|
| 100 | sys_id | sys_id | String |
| 200 | number | number | String |
| 300 | short_description | short_description | String |
| 400 | state | state | String |
| 500 | priority | priority | String |

### 4.4 Create User Type Mapping

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** User
   - **GraphQL Type:** User
   - **Table:** sys_user
   - **Active:** ✓ (checked)
3. Click **Submit** and open the record
4. Add Field Mappings:

| Order | GraphQL Field | Table Field | Field Type |
|-------|--------------|-------------|------------|
| 100 | sys_id | sys_id | String |
| 200 | name | name | String |
| 300 | email | email | String |
| 400 | user_name | user_name | String |

### 4.5 Create Group Type Mapping

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** Group
   - **GraphQL Type:** Group
   - **Table:** sys_user_group
   - **Active:** ✓ (checked)
3. Click **Submit** and open the record
4. Add Field Mappings:

| Order | GraphQL Field | Table Field | Field Type |
|-------|--------------|-------------|------------|
| 100 | sys_id | sys_id | String |
| 200 | name | name | String |

### 4.6 Create StateChangeApproval Type Mapping

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** StateChangeApproval
   - **GraphQL Type:** StateChangeApproval
   - **Table:** sn_vul_state_change_appr (database view)
   - **Active:** ✓ (checked)
3. Click **Submit** and open the record
4. Add Field Mappings:

| Order | GraphQL Field | Table Field | Field Type | Notes |
|-------|--------------|-------------|------------|-------|
| 100 | sys_id | sys_id | String | Primary key |
| 200 | name | vsca_name | String | Approval name |
| 300 | state | sa_state | String | Approval state |
| 400 | approval | sa_approval_column | String | Approval decision |
| 500 | approver | sa_approver | Reference | Links to User type |
| 600 | comments | sa_comments | String | Approver comments |
| 700 | due_date | sa_due_date | String | Due date |
| 800 | sys_created_on | sys_created_on | String | Creation timestamp |
| 900 | sys_updated_on | sys_updated_on | String | Update timestamp |
| 1000 | vulnerable_item | vul_item_sys_id | String | Parent vulnerable item |
| 1100 | vulnerability | vul_sys_id | String | Parent vulnerability |

**Important Notes:**
- This uses the database view `sn_vul_state_change_appr` which joins approval data
- Field names use prefixes from the view (vsca_, sa_, vul_)
- The `approver` field references the User type mapping

### 4.7 Create AssessmentResponse Type Mapping

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** AssessmentResponse
   - **GraphQL Type:** AssessmentResponse
   - **Table:** asmt_assessment_instance_question
   - **Active:** ✓ (checked)
3. Click **Submit** and open the record
4. Add Field Mappings:

| Order | GraphQL Field | Table Field | Field Type | Notes |
|-------|--------------|-------------|------------|-------|

### 4.9 Create ChangeApproval Type Mapping

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** ChangeApproval
   - **GraphQL Type:** ChangeApproval
   - **Table:** sn_vul_change_approval
   - **Active:** ✓ (checked)
3. Click **Submit** and open the record
4. Add Field Mappings:

| Order | GraphQL Field | Table Field | Field Type | Notes |
|-------|--------------|-------------|------------|-------|
| 100 | sys_id | sys_id | String | Primary key |
| 200 | number | number | String | Change approval number (VCA#) |
| 300 | name | name | String | Approval name/title |
| 400 | active | active | Boolean | Active status |
| 500 | approval_state | approval_state | String | Approval workflow state |
| 600 | description | description | String | Full description |
| 700 | approval_state_display | approval_state | String | Approval state display value |
| 800 | approval_rule | approval_rule | String | Approval rule display value |
| 900 | current_value | current_value | String | Current field value |
| 1000 | desired_field | desired_field | String | Field to be changed |
| 1100 | desired_value | desired_value | String | Desired field value |
| 1200 | desired_state | desired_state | String | Requested state change |
| 1300 | desired_state_display | desired_state | String | Desired state display value |
| 1400 | desired_substate | desired_substate | String | Requested substate |
| 1500 | desired_substate_display | desired_substate | String | Desired substate display value |

### 4.11 Create AssessmentInstance Type Mapping

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** AssessmentInstance
   - **GraphQL Type:** AssessmentInstance
   - **Table:** asmt_assessment_instance
   - **Active:** ✓ (checked)
3. Click **Submit** and open the record
4. Add Field Mappings:

| Order | GraphQL Field | Table Field | Field Type | Notes |
|-------|--------------|-------------|------------|-------|
| 100 | sys_id | sys_id | String | Primary key |
| 200 | number | number | String | Assessment instance number (AINST#) |
| 300 | state | state | String | Assessment state value |
| 400 | state_display | state | String | Assessment state display |
| 500 | metric_type | metric_type | String | Metric type reference |
| 600 | metric_type_display | metric_type | String | Metric type display |
| 700 | due_date | due_date | String | Due date |
| 800 | expiration_date | expiration_date | String | Expiration date |
| 900 | taken_on | taken_on | String | Completion timestamp |
| 1000 | percent_answered | percent_answered | Float | Percentage complete |
| 1100 | user | user | Reference | Links to User type |
| 1200 | sys_created_on | sys_created_on | String | Creation timestamp |
| 1300 | sys_updated_on | sys_updated_on | String | Update timestamp |

**Important Notes:**
- This table stores assessment/questionnaire instances
- Links to asmt_assessment_instance_question for individual responses
- The `user` field references who took the assessment

### 4.12 Create AssessmentQuestion Type Mapping

**Step-by-Step Configuration:**

1. Click **New** on Type Mappings list
2. Fill in the form:
   - **Name:** AssessmentQuestion
   - **GraphQL Type:** AssessmentQuestion
   - **Table:** asmt_assessment_instance_question
   - **Active:** ✓ (checked)
3. Click **Submit** and open the record
4. Add Field Mappings:

| Order | GraphQL Field | Table Field | Field Type | Notes |
|-------|--------------|-------------|------------|-------|
| 100 | sys_id | sys_id | String | Primary key |
| 200 | metric | metric | String | Metric reference |
| 300 | metric_display | metric | String | Metric display value |
| 400 | category | category | String | Category reference |
| 500 | category_display | category | String | Category display value |
| 600 | string_value | string_value | String | String response |
| 700 | value | value | Float | Numeric response |
| 800 | add_info | add_info | String | Additional information |
| 900 | is_hidden | is_hidden | Boolean | Hidden flag |
| 1000 | source_id | source_id | String | Source record sys_id |
| 1100 | source_table | source_table | String | Source table name |
| 1200 | sys_created_on | sys_created_on | String | Creation timestamp |
| 1300 | sys_updated_on | sys_updated_on | String | Update timestamp |

**Important Notes:**
- This table stores individual question responses
- Links back to asmt_assessment_instance via `instance` field
- The `source_id` and `source_table` link to the parent record

### 4.13 Update ChangeApproval Type Mapping

Add these additional field mappings to the existing ChangeApproval type:

| Order | GraphQL Field | Table Field | Field Type | Notes |
|-------|--------------|-------------|------------|-------|
| 3000 | assessment_instance | assessment_instance | Reference | Links to AssessmentInstance type |
| 3100 | approvers_list | approvers_list | String | List of approvers |

| 1600 | desired_reason | desired_reason | String | Reason for change |
| 1000 | desired_ignore_date | desired_ignore_date | String | Requested ignore until date |
| 1100 | desired_validity_date | desired_validity_date | String | Validity date |
| 1200 | compensating_controls | compensating_controls | String | Compensating controls |
| 1300 | rejected_reason | rejected_reason | String | Rejection reason |
| 1400 | rejected_by | rejected_by | Reference | Links to User type |
| 1500 | requested_by | requested_by | Reference | Links to User type |
| 1600 | vul_record | vul_record | String | Related vulnerability record sys_id |
| 1700 | vul_table_name | vul_table_name | String | Table name (sn_vul_vulnerable_item or sn_vul_vulnerability) |
| 1800 | stage | stage | String | Current stage |
| 1900 | sys_created_on | sys_created_on | String | Creation timestamp |
| 2000 | sys_updated_on | sys_updated_on | String | Update timestamp |
| 2100 | sys_created_by | sys_created_by | String | Created by user |
| 2200 | approval_rule | approval_rule | String | Approval rule reference |
| 2300 | assessment_instance | assessment_instance | String | Assessment instance reference |

**Important Notes:**
- This table handles exception requests, waivers, and other approval workflows
- The `vul_record` field links to either vulnerable items or vulnerabilities
- The `vul_table_name` field indicates which table the record belongs to
- Both `rejected_by` and `requested_by` reference the User type mapping
- The `approval_state` field tracks the workflow: requested, approved, rejected, etc.

| 100 | sys_id | sys_id | String | Primary key |
| 200 | metric | metric | String | Assessment metric |
| 300 | metric_type | metric_type | String | Type of metric |
| 400 | question | question | String | Question text |
| 500 | response | response | String | Response value |
| 600 | string_value | string_value | String | String response |
| 700 | numeric_value | numeric_value | Float | Numeric response |
| 800 | date_value | date_value | String | Date response |
| 900 | add_info | add_info | String | Additional information |
| 1000 | sys_created_on | sys_created_on | String | Creation timestamp |
| 1100 | sys_updated_on | sys_updated_on | String | Update timestamp |
| 1200 | vulnerable_item | source_id | String | Parent vulnerable item |
| 1300 | vulnerability | source_id | String | Parent vulnerability |

**Important Notes:**
- This table stores assessment/questionnaire responses
- The `source_id` field can reference either vulnerable_item or vulnerability
- Multiple response value fields (string, numeric, date) for different question types

### 4.10 Verify Type Mappings

After creating all type mappings, verify:

1. Go to **System Web Services > GraphQL > Type Mappings**
2. You should see 7 active mappings:
   - VulnerableItem (sn_vul_vulnerable_item)
   - Vulnerability (sn_vul_vulnerability)
   - User (sys_user)
   - Group (sys_user_group)
   - StateChangeApproval (sn_vul_state_change_appr)
   - AssessmentResponse (asmt_assessment_instance_question)
   - ChangeApproval (sn_vul_change_approval)
3. Open each mapping and verify all field mappings are present
4. Check that reference fields point to correct types

**Common Mapping Issues:**
- ❌ Missing field mappings → Queries return null for those fields
- ❌ Wrong table name → No data returned
- ❌ Reference fields without target type → Nested objects fail
- ✅ All fields mapped correctly → Full data resolution

## Step 5: Create Scripted Resolvers

### 5.1 Navigate to Scripted Resolvers
1. Go to **System Web Services > GraphQL > Scripted Resolvers**
2. Create resolvers for each query

### 5.2 Link Resolvers to Schema
For each resolver, you need to:
- Select the GraphQL API: "Vulnerable Item API"
- Select the Query type
- Map to the appropriate query field

### 4.2 Resolver: getVulnerableItem

```javascript
(function process(/*ResolverEnvironment*/ env) {
    var sys_id = env.getArguments().sys_id;
    
    var gr = new GlideRecord('sn_vul_vulnerable_item');
    if (gr.get(sys_id)) {
        return {
            sys_id: gr.getValue('sys_id'),
            number: gr.getValue('number'),
            vulnerable_item: gr.getValue('vulnerable_item'),
            state: gr.getValue('state'),
            priority: gr.getValue('priority'),
            short_description: gr.getValue('short_description'),
            description: gr.getValue('description'),
            work_notes: gr.getValue('work_notes'),
            close_notes: gr.getValue('close_notes'),
            assigned_to: resolveUser(gr.getValue('assigned_to')),
            assignment_group: resolveGroup(gr.getValue('assignment_group')),
            vulnerability: resolveVulnerability(gr.getValue('vulnerability')),
            opened_at: gr.getValue('opened_at'),
            closed_at: gr.getValue('closed_at'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on')
        };
    }
    return null;
    
    function resolveUser(userSysId) {
        if (!userSysId) return null;
        var userGr = new GlideRecord('sys_user');
        if (userGr.get(userSysId)) {
            return {
                sys_id: userGr.getValue('sys_id'),
                name: userGr.getValue('name'),
                email: userGr.getValue('email'),
                user_name: userGr.getValue('user_name')
            };
        }
        return null;
    }
    
    function resolveGroup(groupSysId) {
        if (!groupSysId) return null;
        var groupGr = new GlideRecord('sys_user_group');
        if (groupGr.get(groupSysId)) {
            return {
                sys_id: groupGr.getValue('sys_id'),
                name: groupGr.getValue('name')
            };
        }
        return null;
    }
    
    function resolveVulnerability(vulnSysId) {
        if (!vulnSysId) return null;
        var vulnGr = new GlideRecord('sn_vul_vulnerability');
        if (vulnGr.get(vulnSysId)) {
            return {
                sys_id: vulnGr.getValue('sys_id'),
                number: vulnGr.getValue('number'),
                short_description: vulnGr.getValue('short_description'),
                state: vulnGr.getValue('state'),
                priority: vulnGr.getValue('priority')
            };
        }
        return null;
    }
})(env);
```

### 4.3 Resolver: getVulnerableItems

```javascript
(function process(/*ResolverEnvironment*/ env) {
    var args = env.getArguments();
    var limit = args.limit || 10;
    var offset = args.offset || 0;
    
    var gr = new GlideRecord('sn_vul_vulnerable_item');
    
    // Apply filters
    if (args.state) {
        gr.addQuery('state', args.state);
    }
    if (args.priority) {
        gr.addQuery('priority', args.priority);
    }
    if (args.assigned_to) {
        gr.addQuery('assigned_to', args.assigned_to);
    }
    
    gr.orderByDesc('sys_created_on');
    gr.setLimit(limit);
    gr.chooseWindow(offset, offset + limit);
    gr.query();
    
    var results = [];
    while (gr.next()) {
        results.push({
            sys_id: gr.getValue('sys_id'),
            number: gr.getValue('number'),
            vulnerable_item: gr.getValue('vulnerable_item'),
            state: gr.getValue('state'),
            priority: gr.getValue('priority'),
            short_description: gr.getValue('short_description'),
            description: gr.getValue('description'),
            assigned_to: resolveUser(gr.getValue('assigned_to')),
            assignment_group: resolveGroup(gr.getValue('assignment_group')),
            vulnerability: resolveVulnerability(gr.getValue('vulnerability')),
            opened_at: gr.getValue('opened_at'),
            closed_at: gr.getValue('closed_at'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on')
        });
    }
    
    return results;
    
    function resolveUser(userSysId) {
        if (!userSysId) return null;
        var userGr = new GlideRecord('sys_user');
        if (userGr.get(userSysId)) {
            return {
                sys_id: userGr.getValue('sys_id'),
                name: userGr.getValue('name'),
                email: userGr.getValue('email'),
                user_name: userGr.getValue('user_name')
            };
        }
        return null;
    }
    
    function resolveGroup(groupSysId) {
        if (!groupSysId) return null;
        var groupGr = new GlideRecord('sys_user_group');
        if (groupGr.get(groupSysId)) {
            return {
                sys_id: groupGr.getValue('sys_id'),
                name: groupGr.getValue('name')
            };
        }
        return null;
    }
    
    function resolveVulnerability(vulnSysId) {
        if (!vulnSysId) return null;
        var vulnGr = new GlideRecord('sn_vul_vulnerability');
        if (vulnGr.get(vulnSysId)) {
            return {
                sys_id: vulnGr.getValue('sys_id'),
                number: vulnGr.getValue('number'),
                short_description: vulnGr.getValue('short_description'),
                state: vulnGr.getValue('state'),

### 5.6 Resolver: getStateChangeApprovals

**Resolver Configuration:**
```
Name: getStateChangeApprovals
GraphQL API: Vulnerable Item API
Type: Query
Field: getStateChangeApprovals
Active: true
```

**Script:**
```javascript
(function process(/*ResolverEnvironment*/ env) {
    var args = env.getArguments();
    var limit = args.limit || 10;
    
    var gr = new GlideRecord('sn_vul_state_change_appr');
    
    // Apply filters
    if (args.vulnerable_item_sys_id) {
        gr.addQuery('vul_item_sys_id', args.vulnerable_item_sys_id);
    }
    if (args.vulnerability_sys_id) {
        gr.addQuery('vul_sys_id', args.vulnerability_sys_id);
    }
    if (args.state) {
        gr.addQuery('sa_state', args.state);
    }
    
    gr.orderByDesc('sys_created_on');
    gr.setLimit(limit);
    gr.query();
    
    var results = [];
    while (gr.next()) {
        results.push({
            sys_id: gr.getValue('sys_id'),
            name: gr.getValue('vsca_name'),
            state: gr.getValue('sa_state'),
            approval: gr.getValue('sa_approval_column'),
            approver: resolveUser(gr.getValue('sa_approver')),
            comments: gr.getValue('sa_comments'),
            due_date: gr.getValue('sa_due_date'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on'),
            vulnerable_item: gr.getValue('vul_item_sys_id'),
            vulnerability: gr.getValue('vul_sys_id')
        });
    }
    
    return results;
    
    function resolveUser(userSysId) {
        if (!userSysId) return null;
        var userGr = new GlideRecord('sys_user');
        if (userGr.get(userSysId)) {
            return {
                sys_id: userGr.getValue('sys_id'),
                name: userGr.getValue('name'),
                email: userGr.getValue('email'),
                user_name: userGr.getValue('user_name')
            };
        }
        return null;
    }
})(env);
```

### 5.7 Resolver: getAssessmentResponses

**Resolver Configuration:**
```
Name: getAssessmentResponses
GraphQL API: Vulnerable Item API
Type: Query
Field: getAssessmentResponses
Active: true
```

**Script:**
```javascript
(function process(/*ResolverEnvironment*/ env) {
    var args = env.getArguments();
    var limit = args.limit || 10;
    
    var gr = new GlideRecord('asmt_assessment_instance_question');
    
    // Apply filters
    if (args.vulnerable_item_sys_id) {
        gr.addQuery('source_id', args.vulnerable_item_sys_id);
        gr.addQuery('source_table', 'sn_vul_vulnerable_item');
    }
    if (args.vulnerability_sys_id) {
        gr.addQuery('source_id', args.vulnerability_sys_id);
        gr.addQuery('source_table', 'sn_vul_vulnerability');
    }
    if (args.metric_type) {
        gr.addQuery('metric_type', args.metric_type);
    }
    
    gr.orderByDesc('sys_created_on');
    gr.setLimit(limit);
    gr.query();
    
    var results = [];
    while (gr.next()) {
        results.push({
            sys_id: gr.getValue('sys_id'),
            metric: gr.getDisplayValue('metric'),
            metric_type: gr.getDisplayValue('metric_type'),
            question: gr.getValue('question'),
            response: gr.getValue('response'),
            string_value: gr.getValue('string_value'),
            numeric_value: parseFloat(gr.getValue('numeric_value')) || null,
            date_value: gr.getValue('date_value'),
            add_info: gr.getValue('add_info'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on'),
            vulnerable_item: gr.getValue('source_table') === 'sn_vul_vulnerable_item' ? gr.getValue('source_id') : null,
            vulnerability: gr.getValue('source_table') === 'sn_vul_vulnerability' ? gr.getValue('source_id') : null
        });
    }
    
    return results;
})(env);
```

### 5.8 Update getVulnerableItem Resolver to Include Related Data

Update the existing `getVulnerableItem` resolver to include state change approvals and assessment responses:

**Updated Script:**
```javascript
(function process(/*ResolverEnvironment*/ env) {
    var sys_id = env.getArguments().sys_id;
    
    var gr = new GlideRecord('sn_vul_vulnerable_item');
    if (gr.get(sys_id)) {
        return {
            sys_id: gr.getValue('sys_id'),
            number: gr.getValue('number'),
            vulnerable_item: gr.getValue('vulnerable_item'),
            state: gr.getValue('state'),
            priority: gr.getValue('priority'),
            short_description: gr.getValue('short_description'),
            description: gr.getValue('description'),
            work_notes: gr.getValue('work_notes'),
            close_notes: gr.getValue('close_notes'),
            assigned_to: resolveUser(gr.getValue('assigned_to')),
            assignment_group: resolveGroup(gr.getValue('assignment_group')),
            vulnerability: resolveVulnerability(gr.getValue('vulnerability')),
            opened_at: gr.getValue('opened_at'),
            closed_at: gr.getValue('closed_at'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on'),
            state_change_approvals: getStateChangeApprovals(sys_id),
            assessment_responses: getAssessmentResponses(sys_id, 'sn_vul_vulnerable_item')
        };
    }
    return null;
    
    function resolveUser(userSysId) {
        if (!userSysId) return null;
        var userGr = new GlideRecord('sys_user');
        if (userGr.get(userSysId)) {
            return {
                sys_id: userGr.getValue('sys_id'),
                name: userGr.getValue('name'),
                email: userGr.getValue('email'),
                user_name: userGr.getValue('user_name')
            };
        }
        return null;
    }
    
    function resolveGroup(groupSysId) {
        if (!groupSysId) return null;
        var groupGr = new GlideRecord('sys_user_group');
        if (groupGr.get(groupSysId)) {
            return {
                sys_id: groupGr.getValue('sys_id'),
                name: groupGr.getValue('name')
            };
        }

### 5.9 Resolver: getChangeApprovals

**Resolver Configuration:**
```
Name: getChangeApprovals
GraphQL API: Vulnerable Item API
Type: Query
Field: getChangeApprovals
Active: true
```

**Script:**
```javascript
(function process(/*ResolverEnvironment*/ env) {
    var args = env.getArguments();
    
    // Validate and cap limit for performance
    var limit = parseInt(args.limit, 10) || 10;
    limit = Math.min(limit, 100); // Cap at 100 to prevent performance issues
    
    // Use GlideRecordSecure for automatic ACL enforcement
    var gr = new GlideRecordSecure('sn_vul_change_approval');
    
    // Apply filters
    if (args.vul_record_sys_id) {
        gr.addQuery('vul_record', args.vul_record_sys_id);
    }
    if (args.vul_table_name) {
        gr.addQuery('vul_table_name', args.vul_table_name);
    }
    if (args.approval_state) {
        gr.addQuery('approval_state', args.approval_state);
    }
    if (args.active !== undefined && args.active !== null) {
        gr.addQuery('active', args.active);
    }
    
    gr.orderByDesc('sys_created_on');
    gr.setLimit(limit);
    gr.query();
    
    var results = [];
    while (gr.next()) {
        results.push({
            sys_id: gr.getValue('sys_id'),
            number: gr.getValue('number'),
            name: gr.getValue('name'),
            active: gr.getValue('active') === 'true' || gr.getValue('active') === '1',
            approval_state: gr.getValue('approval_state'),
            approval_state_display: gr.getDisplayValue('approval_state'), // Human-readable state
            approval_rule: gr.getDisplayValue('approval_rule'), // Display value for reference
            description: gr.getValue('description'),
            current_value: gr.getValue('current_value'),
            desired_field: gr.getValue('desired_field'),
            desired_value: gr.getValue('desired_value'),
            desired_state: gr.getValue('desired_state'),
            desired_state_display: gr.getDisplayValue('desired_state'),
            desired_substate: gr.getValue('desired_substate'),
            desired_substate_display: gr.getDisplayValue('desired_substate'),
            desired_reason: gr.getValue('desired_reason'),
            desired_ignore_date: gr.getValue('desired_ignore_date'),
            desired_validity_date: gr.getValue('desired_validity_date'),
            compensating_controls: gr.getValue('compensating_controls'),
            rejected_reason: gr.getValue('rejected_reason'),
            rejected_by: resolveUser(gr.getValue('rejected_by')),
            requested_by: resolveUser(gr.getValue('requested_by')),
            vul_record: gr.getValue('vul_record'),
            vul_table_name: gr.getValue('vul_table_name'),
            stage: gr.getValue('stage'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on'),
            sys_created_by: gr.getValue('sys_created_by'),
            approval_rule: gr.getValue('approval_rule'),
            assessment_instance: gr.getValue('assessment_instance')
        });
    }
    
    return results;
    
    function resolveUser(userSysId) {
        if (!userSysId) return null;
        var userGr = new GlideRecordSecure('sys_user');
        if (userGr.get(userSysId)) {
            return {
                sys_id: userGr.getValue('sys_id'),
                name: userGr.getValue('name'),
                email: userGr.getValue('email'),
                user_name: userGr.getValue('user_name')
            };
        }
        return null;
    }
})(env);
```

### 5.10 Resolver: getChangeApproval

**Resolver Configuration:**
```
Name: getChangeApproval
GraphQL API: Vulnerable Item API
Type: Query
Field: getChangeApproval
Active: true
```

**Script:**
```javascript
(function process(/*ResolverEnvironment*/ env) {
    var sys_id = env.getArguments().sys_id;
    
    var gr = new GlideRecord('sn_vul_change_approval');
    if (gr.get(sys_id)) {
        return {
            sys_id: gr.getValue('sys_id'),
            number: gr.getValue('number'),
            name: gr.getValue('name'),
            active: gr.getValue('active') === 'true' || gr.getValue('active') === '1',
            approval_state: gr.getValue('approval_state'),
            description: gr.getValue('description'),
            desired_state: gr.getValue('desired_state'),
            desired_substate: gr.getValue('desired_substate'),
            desired_reason: gr.getValue('desired_reason'),
            desired_ignore_date: gr.getValue('desired_ignore_date'),
            desired_validity_date: gr.getValue('desired_validity_date'),
            compensating_controls: gr.getValue('compensating_controls'),

### 5.11 Resolver: getAssessmentInstance

**Resolver Configuration:**
```
Name: getAssessmentInstance
GraphQL API: Vulnerable Item API
Type: Query
Field: getAssessmentInstance
Active: true
```

**Script:**
```javascript
(function process(/* ResolverEnvironment */ env) {
    var sys_id = env.getArguments().sys_id;
    
    if (!sys_id) {
        return null;
    }
    
    var gr = new GlideRecordSecure('asmt_assessment_instance');
    if (gr.get(sys_id)) {
        return {
            sys_id: gr.getValue('sys_id'),
            number: gr.getValue('number'),
            state: gr.getValue('state'),
            state_display: gr.getDisplayValue('state'),
            metric_type: gr.getValue('metric_type'),
            metric_type_display: gr.getDisplayValue('metric_type'),
            due_date: gr.getValue('due_date'),
            expiration_date: gr.getValue('expiration_date'),
            taken_on: gr.getValue('taken_on'),
            percent_answered: parseFloat(gr.getValue('percent_answered')) || 0,
            user: resolveUser(gr.getValue('user')),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on'),
            questions: getQuestions(sys_id)
        };
    }
    return null;
    
    function resolveUser(userSysId) {
        if (!userSysId) return null;
        var userGr = new GlideRecordSecure('sys_user');
        if (userGr.get(userSysId)) {
            return {
                sys_id: userGr.getValue('sys_id'),
                name: userGr.getValue('name'),
                user_name: userGr.getValue('user_name'),
                email: userGr.getValue('email')
            };
        }
        return null;
    }
    
    function getQuestions(instanceSysId) {
        var questions = [];
        var qGr = new GlideRecordSecure('asmt_assessment_instance_question');
        qGr.addQuery('instance', instanceSysId);
        qGr.orderBy('sys_created_on');
        qGr.query();
        
        while (qGr.next()) {
            questions.push({
                sys_id: qGr.getValue('sys_id'),
                metric: qGr.getValue('metric'),
                metric_display: qGr.getDisplayValue('metric'),
                category: qGr.getValue('category'),
                category_display: qGr.getDisplayValue('category'),
                string_value: qGr.getValue('string_value'),
                value: parseFloat(qGr.getValue('value')) || 0,
                add_info: qGr.getValue('add_info'),
                is_hidden: qGr.getValue('is_hidden') === 'true' || qGr.getValue('is_hidden') === '1',
                source_id: qGr.getValue('source_id'),
                source_table: qGr.getValue('source_table'),
                sys_created_on: qGr.getValue('sys_created_on'),
                sys_updated_on: qGr.getValue('sys_updated_on')
            });
        }
        return questions;
    }
})(env);
```

### 5.12 Resolver: getAssessmentQuestions

**Resolver Configuration:**
```
Name: getAssessmentQuestions
GraphQL API: Vulnerable Item API
Type: Query
Field: getAssessmentQuestions
Active: true
```

**Script:**
```javascript
(function process(/* ResolverEnvironment */ env) {
    var args = env.getArguments();
    
    if (!args.instance_sys_id) {
        return [];
    }
    
    var limit = parseInt(args.limit, 10) || 50;
    limit = Math.min(limit, 100);
    
    var gr = new GlideRecordSecure('asmt_assessment_instance_question');
    gr.addQuery('instance', args.instance_sys_id);
    gr.orderBy('sys_created_on');
    gr.setLimit(limit);
    gr.query();
    
    var results = [];
    while (gr.next()) {
        results.push({
            sys_id: gr.getValue('sys_id'),
            metric: gr.getValue('metric'),
            metric_display: gr.getDisplayValue('metric'),
            category: gr.getValue('category'),
            category_display: gr.getDisplayValue('category'),
            string_value: gr.getValue('string_value'),
            value: parseFloat(gr.getValue('value')) || 0,
            add_info: gr.getValue('add_info'),
            is_hidden: gr.getValue('is_hidden') === 'true' || qGr.getValue('is_hidden') === '1',
            source_id: gr.getValue('source_id'),
            source_table: gr.getValue('source_table'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on')
        });
    }
    
    return results;
})(env);
```

### 5.13 Update getChangeApproval Resolver to Include Assessment Instance

Update the existing `getChangeApproval` resolver to include the assessment instance:

**Add this function inside the resolver:**
```javascript
function resolveAssessmentInstance(instanceSysId) {
    if (!instanceSysId) return null;
    
    var instGr = new GlideRecordSecure('asmt_assessment_instance');
    if (instGr.get(instanceSysId)) {
        return {
            sys_id: instGr.getValue('sys_id'),
            number: instGr.getValue('number'),
            state: instGr.getValue('state'),
            state_display: instGr.getDisplayValue('state'),
            metric_type_display: instGr.getDisplayValue('metric_type'),
            due_date: instGr.getValue('due_date'),
            percent_answered: parseFloat(instGr.getValue('percent_answered')) || 0,
            user: resolveUser(instGr.getValue('user'))
        };
    }
    return null;
}
```

**Update the return statement to include:**
```javascript
assessment_instance: resolveAssessmentInstance(gr.getValue('assessment_instance')),
approvers_list: gr.getValue('approvers_list')
```

            rejected_reason: gr.getValue('rejected_reason'),
            rejected_by: resolveUser(gr.getValue('rejected_by')),
            requested_by: resolveUser(gr.getValue('requested_by')),
            vul_record: gr.getValue('vul_record'),
            vul_table_name: gr.getValue('vul_table_name'),
            stage: gr.getValue('stage'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on'),
            sys_created_by: gr.getValue('sys_created_by'),
            approval_rule: gr.getValue('approval_rule'),
            assessment_instance: gr.getValue('assessment_instance')
        };
    }
    return null;
    
    function resolveUser(userSysId) {
        if (!userSysId) return null;
        var userGr = new GlideRecord('sys_user');
        if (userGr.get(userSysId)) {
            return {
                sys_id: userGr.getValue('sys_id'),
                name: userGr.getValue('name'),
                email: userGr.getValue('email'),
                user_name: userGr.getValue('user_name')
            };
        }
        return null;
    }
})(env);
```

        return null;
    }
    
    function resolveVulnerability(vulnSysId) {
        if (!vulnSysId) return null;
        var vulnGr = new GlideRecord('sn_vul_vulnerability');
        if (vulnGr.get(vulnSysId)) {
            return {
                sys_id: vulnGr.getValue('sys_id'),
                number: vulnGr.getValue('number'),
                short_description: vulnGr.getValue('short_description'),
                state: vulnGr.getValue('state'),
                priority: vulnGr.getValue('priority')
            };
        }
        return null;
    }
    
    function getStateChangeApprovals(itemSysId) {
        var approvals = [];
        var apprGr = new GlideRecord('sn_vul_state_change_appr');
        apprGr.addQuery('vul_item_sys_id', itemSysId);
        apprGr.orderByDesc('sys_created_on');
        apprGr.query();
        
        while (apprGr.next()) {
            approvals.push({
                sys_id: apprGr.getValue('sys_id'),
                name: apprGr.getValue('vsca_name'),
                state: apprGr.getValue('sa_state'),
                approval: apprGr.getValue('sa_approval_column'),
                approver: resolveUser(apprGr.getValue('sa_approver')),
                comments: apprGr.getValue('sa_comments'),
                due_date: apprGr.getValue('sa_due_date'),
                sys_created_on: apprGr.getValue('sys_created_on'),
                sys_updated_on: apprGr.getValue('sys_updated_on')
            });
        }
        return approvals;
    }
    
    function getAssessmentResponses(sourceSysId, sourceTable) {
        var responses = [];
        var respGr = new GlideRecord('asmt_assessment_instance_question');
        respGr.addQuery('source_id', sourceSysId);
        respGr.addQuery('source_table', sourceTable);
        respGr.orderByDesc('sys_created_on');
        respGr.query();
        
        while (respGr.next()) {
            responses.push({
                sys_id: respGr.getValue('sys_id'),
                metric: respGr.getDisplayValue('metric'),
                metric_type: respGr.getDisplayValue('metric_type'),
                question: respGr.getValue('question'),
                response: respGr.getValue('response'),
                string_value: respGr.getValue('string_value'),
                numeric_value: parseFloat(respGr.getValue('numeric_value')) || null,
                date_value: respGr.getValue('date_value'),
                add_info: respGr.getValue('add_info'),
                sys_created_on: respGr.getValue('sys_created_on'),
                sys_updated_on: respGr.getValue('sys_updated_on')
            });
        }
        return responses;
    }
})(env);
```


#### Query 4: Get Vulnerable Item with State Change Approvals and Assessment Responses
```graphql
query GetVulnerableItemWithRelatedData {
  getVulnerableItem(sys_id: "YOUR_SYS_ID_HERE") {
    sys_id
    number
    short_description
    state
    priority
    vulnerability {

#### Query 12: Get Change Approval with Complete Assessment Data
```graphql
query GetChangeApprovalWithAssessment {
  getChangeApproval(sys_id: "CHANGE_APPROVAL_SYS_ID_HERE") {
    sys_id
    number
    name
    approval_state_display
    description
    desired_state_display
    desired_substate_display
    desired_reason
    desired_ignore_date
    
    requested_by {
      name
      email
    }
    
    # Assessment instance with questions
    assessment_instance {
      sys_id
      number
      state_display
      metric_type_display
      due_date
      percent_answered
      taken_on
      user {
        name
        email
      }
      questions {
        metric_display
        category_display
        string_value
        value
        add_info
      }
    }
    
    sys_created_on
  }
}
```

#### Query 13: Get Assessment Instance with All Questions
```graphql
query GetAssessmentInstanceDetails {
  getAssessmentInstance(sys_id: "ASSESSMENT_INSTANCE_SYS_ID_HERE") {
    sys_id
    number
    state_display
    metric_type_display
    due_date
    expiration_date
    taken_on
    percent_answered
    
    user {
      name
      user_name
      email
    }
    
    questions {
      sys_id
      metric_display
      category_display
      string_value
      value
      add_info
      is_hidden
      source_id
      source_table
      sys_created_on
    }
    
    sys_created_on
    sys_updated_on
  }
}
```

#### Query 14: Complete Approval Workflow Chain
```graphql
query GetCompleteApprovalWorkflow {
  # Get the change approval
  getChangeApproval(sys_id: "CHANGE_APPROVAL_SYS_ID_HERE") {
    sys_id
    number
    name
    approval_state_display
    description
    
    # Who requested it
    requested_by {
      name
      email
    }
    
    # What they want to change
    desired_state_display
    desired_substate_display
    desired_reason
    desired_ignore_date
    compensating_controls
    
    # The assessment they filled out
    assessment_instance {
      number
      state_display
      percent_answered
      taken_on
      
      # All the questions and answers
      questions {
        metric_display
        category_display
        string_value
        add_info
      }
    }
    
    # Link to the vulnerable item
    vul_record
    vul_table_name
    
    sys_created_on
  }
}
```

#### Query 15: Get All Assessment Questions for an Instance
```graphql
query GetAssessmentQuestions {
  getAssessmentQuestions(
    instance_sys_id: "ASSESSMENT_INSTANCE_SYS_ID_HERE"
    limit: 50
  ) {
    sys_id
    metric_display
    category_display
    string_value
    value
    add_info
    is_hidden
    source_id
    source_table
    sys_created_on
  }
}
```
      number
      short_description
    }
    assigned_to {
      name
      email
    }
    state_change_approvals {
      sys_id
      name
      state
      approval
      approver {
        name
        email
      }
      comments
      due_date
      sys_created_on
    }
    assessment_responses {
      sys_id
      metric
      metric_type
      question
      response
      string_value
      numeric_value
      add_info
      sys_created_on
    }
  }
}
```

#### Query 5: Get State Change Approvals for a Vulnerable Item
```graphql
query GetStateChangeApprovals {
  getStateChangeApprovals(

#### Query 8: Get Change Approvals for a Vulnerable Item
```graphql
query GetChangeApprovals {
  getChangeApprovals(
    vul_record_sys_id: "VULNERABLE_ITEM_SYS_ID_HERE"
    vul_table_name: "sn_vul_vulnerable_item"
    active: true
    limit: 20
  ) {
    sys_id
    number
    name
    active
    approval_state
    description
    desired_state
    desired_substate
    desired_reason
    desired_ignore_date
    compensating_controls
    rejected_reason
    requested_by {
      name
      email
      user_name
    }
    rejected_by {
      name
    }
    sys_created_on
    sys_updated_on
  }
}
```

#### Query 9: Get Single Change Approval with Full Details
```graphql
query GetChangeApprovalDetails {
  getChangeApproval(sys_id: "CHANGE_APPROVAL_SYS_ID_HERE") {
    sys_id
    number
    name
    active
    approval_state
    description
    desired_state
    desired_substate
    desired_reason
    desired_ignore_date
    desired_validity_date
    compensating_controls
    rejected_reason
    requested_by {
      sys_id
      name
      email
      user_name
    }
    rejected_by {
      sys_id
      name
      email
    }
    vul_record
    vul_table_name
    stage
    approval_rule
    assessment_instance
    sys_created_on
    sys_updated_on
    sys_created_by
  }
}
```

#### Query 10: Get All Pending Change Approvals
```graphql
query GetPendingChangeApprovals {
  getChangeApprovals(
    approval_state: "requested"
    active: true
    limit: 50
  ) {
    sys_id
    number
    name
    description
    desired_state
    desired_reason
    requested_by {
      name
      email
    }
    vul_record
    vul_table_name
    sys_created_on
  }
}
```

#### Query 11: Complete Vulnerable Item with All Approval Types
```graphql
query GetVulnerableItemWithAllApprovals {
  getVulnerableItem(sys_id: "YOUR_SYS_ID_HERE") {
    sys_id
    number
    short_description
    state
    priority
    
    # Basic info
    vulnerability {
      number
      short_description
    }
    assigned_to {
      name
      email
    }
    
    # State change approvals (workflow approvals)
    state_change_approvals {
      name
      state
      approval
      approver {
        name
      }
      comments
      due_date
    }
    
    # Assessment responses (questionnaires)
    assessment_responses {
      metric
      question
      response
      string_value
      add_info
    }
  }
  
  # Change approvals (exception requests, waivers)
  getChangeApprovals(
    vul_record_sys_id: "YOUR_SYS_ID_HERE"
    vul_table_name: "sn_vul_vulnerable_item"
  ) {
    number
    name
    approval_state
    desired_state
    desired_reason
    requested_by {
      name
    }
    sys_created_on
  }
}
```
    vulnerable_item_sys_id: "VULNERABLE_ITEM_SYS_ID_HERE"
    state: "requested"
    limit: 20
  ) {
    sys_id
    name
    state
    approval
    approver {
      name
      email
      user_name
    }
    comments
    due_date
    sys_created_on
  }
}
```

#### Query 6: Get Assessment Responses for a Vulnerability
```graphql
query GetAssessmentResponses {
  getAssessmentResponses(
    vulnerability_sys_id: "VULNERABILITY_SYS_ID_HERE"
    metric_type: "waiver"
    limit: 50
  ) {
    sys_id
    metric
    metric_type
    question
    response
    string_value
    numeric_value
    date_value
    add_info
    sys_created_on
  }
}
```

#### Query 7: Complex Query with All Related Data
```graphql
query GetCompleteVulnerableItemData {
  getVulnerableItems(
    limit: 5
    state: "open"
    priority: "1"
  ) {
    sys_id
    number
    short_description
    state
    priority
    vulnerability {
      sys_id
      number
      short_description
      state
    }
    assigned_to {
      name
      email
    }
    assignment_group {
      name
    }
    state_change_approvals {
      name
      state
      approval
      approver {
        name
      }
      due_date
    }
    assessment_responses {
      metric
      question
      response
      string_value
    }
    opened_at
    sys_created_on
  }
}
```
                priority: vulnGr.getValue('priority')
            };
        }
        return null;
    }
})(env);
```

### 4.4 Resolver: getVulnerableItemsByVulnerability

```javascript
(function process(/*ResolverEnvironment*/ env) {
    var args = env.getArguments();
    var vulnerability_sys_id = args.vulnerability_sys_id;
    var limit = args.limit || 10;
    
    var gr = new GlideRecord('sn_vul_vulnerable_item');
    gr.addQuery('vulnerability', vulnerability_sys_id);
    gr.orderByDesc('sys_created_on');
    gr.setLimit(limit);
    gr.query();
    
    var results = [];
    while (gr.next()) {
        results.push({
            sys_id: gr.getValue('sys_id'),
            number: gr.getValue('number'),
            vulnerable_item: gr.getValue('vulnerable_item'),
            state: gr.getValue('state'),
            priority: gr.getValue('priority'),
            short_description: gr.getValue('short_description'),
            description: gr.getValue('description'),
            assigned_to: resolveUser(gr.getValue('assigned_to')),
            assignment_group: resolveGroup(gr.getValue('assignment_group')),
            vulnerability: resolveVulnerability(gr.getValue('vulnerability')),
            opened_at: gr.getValue('opened_at'),
            closed_at: gr.getValue('closed_at'),
            sys_created_on: gr.getValue('sys_created_on'),
            sys_updated_on: gr.getValue('sys_updated_on')
        });
    }
    
    return results;
    
    function resolveUser(userSysId) {
        if (!userSysId) return null;
        var userGr = new GlideRecord('sys_user');
        if (userGr.get(userSysId)) {
            return {
                sys_id: userGr.getValue('sys_id'),
                name: userGr.getValue('name'),
                email: userGr.getValue('email'),
                user_name: userGr.getValue('user_name')
            };
        }
        return null;
    }
    
    function resolveGroup(groupSysId) {
        if (!groupSysId) return null;
        var groupGr = new GlideRecord('sys_user_group');
        if (groupGr.get(groupSysId)) {
            return {
                sys_id: groupGr.getValue('sys_id'),
                name: groupGr.getValue('name')
            };
        }
        return null;
    }
    
    function resolveVulnerability(vulnSysId) {
        if (!vulnSysId) return null;
        var vulnGr = new GlideRecord('sn_vul_vulnerability');
        if (vulnGr.get(vulnSysId)) {
            return {
                sys_id: vulnGr.getValue('sys_id'),
                number: vulnGr.getValue('number'),
                short_description: vulnGr.getValue('short_description'),
                state: vulnGr.getValue('state'),
                priority: vulnGr.getValue('priority')
            };
        }
        return null;
    }
})(env);
```

## Step 6: Configure Security

### 6.1 Create ACL Rules
1. Navigate to **System Security > Access Control (ACL)**
2. Create ACL for GraphQL API access

### 6.2 Example ACL Script
```javascript
// Table: sn_vul_vulnerable_item
// Operation: read
// Role: sn_vul.user or admin

answer = gs.hasRole('sn_vul.user') || gs.hasRole('admin');
```

## Step 7: Test GraphQL API

### 7.1 Access GraphQL Explorer
1. Navigate to **System Web Services > GraphQL > GraphQL Explorer**
2. Select your API: "Vulnerable Item API"

### 7.2 Example Queries

#### Query 1: Get Single Vulnerable Item
```graphql
query GetVulnerableItem {
  getVulnerableItem(sys_id: "YOUR_SYS_ID_HERE") {
    sys_id
    number
    short_description
    state
    priority
    vulnerability {
      number
      short_description
    }
    assigned_to {
      name
      email
    }
  }
}
```

#### Query 2: Get Multiple Vulnerable Items
```graphql
query GetVulnerableItems {
  getVulnerableItems(
    limit: 20
    offset: 0
    state: "open"
    priority: "1"
  ) {
    sys_id
    number
    short_description
    state
    priority
    vulnerability {
      number
      short_description
    }
    assigned_to {
      name
    }
    sys_created_on
  }
}
```

#### Query 3: Get Vulnerable Items by Vulnerability
```graphql
query GetItemsByVulnerability {
  getVulnerableItemsByVulnerability(
    vulnerability_sys_id: "VULNERABILITY_SYS_ID_HERE"
    limit: 10
  ) {
    sys_id
    number
    short_description
    state
    assigned_to {
      name
    }
  }
}
```

## Step 8: API Endpoint

### 8.1 REST Endpoint
```
POST https://YOUR_INSTANCE.service-now.com/api/now/graphql
```

### 8.2 Authentication
Use Basic Auth or OAuth 2.0:
```
Authorization: Basic base64(username:password)
```

### 8.3 Example cURL Request
```bash
curl -X POST \
  'https://YOUR_INSTANCE.service-now.com/api/now/graphql' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Basic YOUR_BASE64_CREDENTIALS' \
  -d '{
    "query": "query { getVulnerableItems(limit: 10) { sys_id number short_description state } }"
  }'
```

## Step 9: Advanced Features

### 9.1 Add Pagination
```graphql
type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  totalCount: Int!
}

type VulnerableItemConnection {
  edges: [VulnerableItem]
  pageInfo: PageInfo
}

type Query {
  getVulnerableItemsPaginated(
    first: Int
    after: String
    state: String
  ): VulnerableItemConnection
}
```

### 9.2 Add Mutations (if needed)
```graphql
type Mutation {
  updateVulnerableItemState(
    sys_id: String!
    state: String!
    work_notes: String
  ): VulnerableItem
}
```

## Step 10: Performance Optimization

### 10.1 Enable Query Caching
- Configure cache duration in GraphQL API settings
- Use DataLoader pattern for batch loading

### 10.2 Limit Query Depth
- Set max query depth to prevent complex nested queries
- Configure in GraphQL API properties

### 10.3 Add Indexes
```sql
-- Add indexes on frequently queried fields
CREATE INDEX idx_vul_item_state ON sn_vul_vulnerable_item(state);
CREATE INDEX idx_vul_item_priority ON sn_vul_vulnerable_item(priority);
CREATE INDEX idx_vul_item_vulnerability ON sn_vul_vulnerable_item(vulnerability);
```

## Troubleshooting

### Common Issues

1. **GraphQL Plugin Not Found**
   - Ensure you have the correct license
   - Check plugin activation status
   - Navigate to **System Definition > Plugins** and search for "GraphQL"

2. **Query Returns Null or Empty Data**
   - ⚠️ **Most Common Cause: Missing or Incorrect Type Mappings**
   - Verify type mappings exist: **System Web Services > GraphQL > Type Mappings**
   - Check that table names match exactly (case-sensitive)
   - Ensure all fields are mapped in Field Mappings related list
   - Verify reference fields have correct target types specified

3. **Nested Objects Return Null**
   - Check reference field mappings (assigned_to, vulnerability, etc.)
   - Ensure target type mappings exist (User, Vulnerability, Group)
   - Verify the reference field has "Referenced Type" set correctly
   - Example: `assigned_to` field must reference `User` type mapping

4. **Field Returns Null But Data Exists in Table**
   - Field mapping might be missing
   - Field name in mapping might not match GraphQL schema
   - Check field mapping order (lower numbers resolve first)
   - Verify field exists in the table

5. **Permission Denied**
   - Verify ACL rules for the table
   - Check user roles (need sn_vul.user or admin)
   - Ensure GraphQL API is active
   - Check if user has read access to sn_vul_vulnerable_item table

6. **Resolver Errors**
   - Check system logs: **System Logs > System Log > All**
   - Enable debug logging for GraphQL
   - Look for JavaScript errors in resolver scripts
   - Verify GlideRecord queries are correct

7. **Performance Issues**
   - Add appropriate indexes on frequently queried fields
   - Limit query depth and complexity
   - Use pagination for large result sets
   - Avoid N+1 queries in resolvers

8. **Type Mapping Not Found Error**
   - Ensure type mapping name matches GraphQL type exactly
   - Check that mapping is active
   - Verify GraphQL API is associated with correct mappings

### Debugging Type Mappings

**Quick Checklist:**
```
□ Type mapping exists for each GraphQL type
□ Table name is correct and matches ServiceNow table
□ All fields in GraphQL schema have corresponding field mappings
□ Reference fields specify target type
□ Field mapping order is logical (100, 200, 300...)
□ Type mapping is Active
□ GraphQL API references correct type mappings
```

**How to Debug:**
1. Test query in GraphQL Explorer
2. If field returns null, check type mapping for that type
3. Verify field mapping exists for that specific field
4. For reference fields, check target type mapping exists
5. Review system logs for mapping errors

## Best Practices

1. **Security**
   - Always validate input parameters
   - Implement proper ACLs
   - Use field-level security

2. **Performance**
   - Use pagination for large datasets
   - Implement caching where appropriate
   - Avoid N+1 query problems

3. **Maintainability**
   - Document your schema
   - Version your API
   - Use meaningful field names

4. **Testing**
   - Test with various user roles
   - Validate error handling
   - Performance test with production-like data

## Additional Resources

- ServiceNow GraphQL Documentation: [ServiceNow Docs](https://docs.servicenow.com)
- GraphQL Best Practices: [GraphQL.org](https://graphql.org/learn/best-practices/)
- Vulnerability Response Module: ServiceNow Store

## Next Steps

1. Extend schema with additional fields from custom tables
2. Add mutations for updating vulnerable items
3. Implement subscriptions for real-time updates
4. Create client applications using the GraphQL API
5. Set up monitoring and analytics

 

0 REPLIES 0