Skip to main content

Jira Integration

This guide covers the integration between NopeSight and Atlassian Jira, enabling automatic issue creation from discoveries, linking configuration items to issues, and tracking infrastructure changes through Jira workflows.

Overview

The NopeSight-Jira integration provides:

  • Automatic Issue Creation - Create Jira issues from discovered problems
  • CI Linking - Link configuration items to Jira issues
  • Bidirectional Updates - Sync status between platforms
  • Custom Field Mapping - Map NopeSight data to Jira fields
  • Workflow Automation - Trigger actions based on issue transitions

Architecture

Installation

1. Install NopeSight App for Jira

# For Jira Cloud
1. Go to Jira Settings > Apps > Find new apps
2. Search for "NopeSight Integration"
3. Click Install
4. Grant required permissions

# For Jira Server/Data Center
1. Download the app from Atlassian Marketplace
2. Go to Jira Administration > Add-ons > Manage add-ons
3. Upload the .jar file
4. Configure the app

2. Configure API Access

# Generate API token in Jira
# Profile > Security > API tokens > Create API token

# Configure in NopeSight
config = {
'jira': {
'url': 'https://company.atlassian.net',
'username': 'integration@company.com',
'api_token': 'your-api-token-here',
'project_key': 'INF'
}
}

3. Set Up Webhooks

// Jira webhook configuration
const webhook = {
name: 'NopeSight Integration',
url: 'https://api.nopesight.com/webhooks/jira',
events: [
'jira:issue_created',
'jira:issue_updated',
'jira:issue_deleted',
'comment_created',
'issue_property_set'
],
filters: {
'issue-related-events-section': {
'project': ['INF', 'OPS']
}
},
excludeBody: false,
enabled: true
};

Configuration

Connection Settings

# NopeSight configuration
integrations:
jira:
enabled: true
url: ${JIRA_URL}
authentication:
method: api_token
username: ${JIRA_USERNAME}
api_token: ${JIRA_API_TOKEN}

project_mappings:
infrastructure: INF
security: SEC
operations: OPS

issue_type_mappings:
incident: Bug
problem: Task
change: Story
vulnerability: Security

custom_fields:
nopesight_id: customfield_10100
ci_link: customfield_10101
ai_analysis: customfield_10102
discovery_date: customfield_10103

sync_settings:
auto_create_issues: true
sync_interval: 300
batch_size: 50

Field Mapping Configuration

// Field mapping configuration
const fieldMappings = {
// NopeSight to Jira
toJira: {
'title': 'summary',
'description': 'description',
'severity': {
field: 'priority',
transform: (value) => {
const priorityMap = {
'critical': 'Highest',
'high': 'High',
'medium': 'Medium',
'low': 'Low'
};
return { name: priorityMap[value] || 'Medium' };
}
},
'affected_cis': {
field: 'customfield_10101',
transform: (cis) => cis.map(ci => ({
key: ci.id,
name: ci.name,
link: `https://nopesight.com/cmdb/ci/${ci.id}`
}))
},
'ai_analysis': 'customfield_10102',
'discovered_at': {
field: 'customfield_10103',
transform: (date) => new Date(date).toISOString()
}
},

// Jira to NopeSight
fromJira: {
'status.name': 'status',
'resolution.name': 'resolution',
'assignee.emailAddress': 'assigned_to',
'updated': 'last_updated'
}
};

Issue Creation

Automatic Issue Creation

from jira import JIRA
import json

class JiraIssueCreator:
def __init__(self, config):
self.jira = JIRA(
server=config['url'],
basic_auth=(config['username'], config['api_token'])
)
self.project = config['project_key']

def create_issue_from_discovery(self, discovery_data):
"""Create Jira issue from NopeSight discovery"""

# Prepare issue data
issue_dict = {
'project': {'key': self.project},
'summary': self.format_summary(discovery_data),
'description': self.format_description(discovery_data),
'issuetype': {'name': self.get_issue_type(discovery_data)},
'priority': {'name': self.map_priority(discovery_data['severity'])},
'labels': ['nopesight', 'automated', discovery_data['type']]
}

# Add custom fields
custom_fields = self.map_custom_fields(discovery_data)
issue_dict.update(custom_fields)

# Create issue
issue = self.jira.create_issue(fields=issue_dict)

# Add attachments if any
if discovery_data.get('attachments'):
for attachment in discovery_data['attachments']:
self.jira.add_attachment(
issue=issue,
attachment=attachment['data'],
filename=attachment['name']
)

# Link to affected CIs
self.link_configuration_items(issue, discovery_data['affected_cis'])

return issue

def format_summary(self, data):
"""Format issue summary"""
if data['type'] == 'vulnerability':
return f"[{data['severity'].upper()}] {data['title']}"
elif data['type'] == 'performance':
return f"Performance Issue: {data['title']}"
else:
return data['title']

def format_description(self, data):
"""Format issue description with rich content"""

description = f"""
h3. Issue Summary
{data['description']}

h3. Affected Configuration Items
{self.format_ci_table(data['affected_cis'])}

h3. Discovery Details
* *Discovered By:* {data['discovered_by']}
* *Discovery Time:* {data['discovered_at']}
* *Severity:* {data['severity']}
* *Category:* {data['category']}

h3. Technical Details
{{code:json}}
{json.dumps(data['technical_details'], indent=2)}
{{code}}
"""

# Add AI analysis if available
if data.get('ai_analysis'):
description += f"""
h3. AI Analysis
{data['ai_analysis']['summary']}

*Root Cause:* {data['ai_analysis'].get('root_cause', 'Not determined')}

*Recommendations:*
{self.format_recommendations(data['ai_analysis'].get('recommendations', []))}

*Risk Assessment:* {data['ai_analysis'].get('risk_level', 'Unknown')}
"""

return description

def format_ci_table(self, cis):
"""Format CI list as Jira table"""
if not cis:
return "No CIs affected"

table = "||CI Name||Type||Status||Link||\n"
for ci in cis:
link = f"[{ci['name']}|https://nopesight.com/cmdb/ci/{ci['id']}]"
table += f"|{ci['name']}|{ci['type']}|{ci['status']}|{link}|\n"

return table

Issue Templates

// Jira issue templates
const issueTemplates = {
vulnerability: {
fields: {
project: { key: 'SEC' },
issuetype: { name: 'Security' },
components: [{ name: 'Infrastructure Security' }],
security: { name: 'Internal' },
customfield_10120: 'vulnerability', // Issue Category

// Epic link for vulnerability tracking
customfield_10014: 'SEC-100'
},

descriptionTemplate: `
h3. Vulnerability Details
* *CVE ID:* {cve_id}
* *CVSS Score:* {cvss_score}
* *Affected Systems:* {affected_count}

h3. Impact Assessment
{impact_description}

h3. Remediation Steps
{remediation_steps}

h3. References
{references}
`
},

performance: {
fields: {
project: { key: 'OPS' },
issuetype: { name: 'Task' },
components: [{ name: 'Performance' }],
labels: ['performance', 'sla-risk']
},

descriptionTemplate: `
h3. Performance Issue
* *Metric:* {metric_name}
* *Current Value:* {current_value}
* *Threshold:* {threshold}
* *Duration:* {duration}

h3. Impact
{impact_description}

h3. Historical Trend
!{trend_chart_url}!
`
}
};

Webhook Integration

Jira Webhook Handler

from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)

@app.route('/webhooks/jira', methods=['POST'])
def handle_jira_webhook():
# Verify webhook authenticity
if not verify_jira_webhook(request):
return jsonify({'error': 'Unauthorized'}), 401

# Parse webhook event
event = request.json
webhook_event = event['webhookEvent']

# Route based on event type
if webhook_event == 'jira:issue_created':
handle_issue_created(event['issue'])
elif webhook_event == 'jira:issue_updated':
handle_issue_updated(event['issue'], event['changelog'])
elif webhook_event == 'comment_created':
handle_comment_created(event['comment'], event['issue'])

return jsonify({'status': 'processed'}), 200

def handle_issue_updated(issue, changelog):
"""Handle Jira issue updates"""

# Check if status changed
status_change = next(
(item for item in changelog['items'] if item['field'] == 'status'),
None
)

if status_change:
# Update corresponding event/incident in NopeSight
nopesight_id = get_nopesight_id(issue)
if nopesight_id:
update_nopesight_status(
nopesight_id,
status_change['toString']
)

# Check for resolution
resolution_change = next(
(item for item in changelog['items'] if item['field'] == 'resolution'),
None
)

if resolution_change and resolution_change['toString']:
# Mark as resolved in NopeSight
handle_issue_resolution(issue, resolution_change['toString'])

def handle_comment_created(comment, issue):
"""Handle new comments on Jira issues"""

# Check if comment contains commands
if comment['body'].startswith('/nopesight'):
process_nopesight_command(comment['body'], issue)

# Sync comment to NopeSight
nopesight_id = get_nopesight_id(issue)
if nopesight_id:
add_nopesight_note(
nopesight_id,
comment['body'],
comment['author']['emailAddress']
)

Custom Jira Commands

// Jira issue commands processor
class NopeSightCommands {
constructor(jiraApi, nopesightApi) {
this.jira = jiraApi;
this.nopesight = nopesightApi;

this.commands = {
'/nopesight refresh': this.refreshCIData.bind(this),
'/nopesight analyze': this.runAIAnalysis.bind(this),
'/nopesight impact': this.getImpactAnalysis.bind(this),
'/nopesight history': this.getCIHistory.bind(this)
};
}

async processCommand(command, issue) {
const [cmd, ...args] = command.split(' ');
const handler = this.commands[`${cmd} ${args[0]}`];

if (handler) {
try {
const result = await handler(issue, args.slice(1));
await this.postComment(issue.key, result);
} catch (error) {
await this.postComment(
issue.key,
`Error executing command: ${error.message}`
);
}
}
}

async refreshCIData(issue) {
// Get linked CIs
const cis = await this.getLinkedCIs(issue);

// Refresh data from NopeSight
const updates = [];
for (const ci of cis) {
const latestData = await this.nopesight.getCI(ci.id);
updates.push({
ci: ci.name,
status: latestData.status,
lastScan: latestData.last_scan_date,
changes: this.detectChanges(ci, latestData)
});
}

// Format response
return this.formatCIUpdateTable(updates);
}

async runAIAnalysis(issue) {
// Get issue details
const issueData = await this.jira.getIssue(issue.key);

// Run AI analysis
const analysis = await this.nopesight.analyzeIssue({
title: issueData.fields.summary,
description: issueData.fields.description,
affected_cis: await this.getLinkedCIs(issue),
category: issueData.fields.issuetype.name
});

// Format AI response
return `
h3. AI Analysis Results

*Root Cause Analysis:*
${analysis.root_cause}

*Impact Assessment:*
${analysis.impact_assessment}

*Recommended Actions:*
${analysis.recommendations.map((r, i) => `${i+1}. ${r}`).join('\n')}

*Confidence Score:* ${(analysis.confidence * 100).toFixed(1)}%
`;
}
}

Automation Rules

Jira Automation Configuration

{
"name": "NopeSight CI Update Automation",
"trigger": {
"type": "field_value_changed",
"field": "customfield_10101",
"from": "empty",
"to": "any"
},
"conditions": [
{
"type": "issue_type",
"configuration": {
"issueTypes": ["Bug", "Task", "Security"]
}
}
],
"actions": [
{
"type": "web_request",
"configuration": {
"url": "https://api.nopesight.com/integrations/jira/ci-linked",
"method": "POST",
"headers": {
"Authorization": "Bearer {{nopesight.api.token}}",
"Content-Type": "application/json"
},
"body": {
"issue_key": "{{issue.key}}",
"ci_ids": "{{issue.customfield_10101}}",
"project": "{{issue.project.key}}"
}
}
},
{
"type": "add_comment",
"configuration": {
"body": "Configuration Items have been linked. NopeSight will monitor these CIs and update this issue with any relevant changes."
}
}
]
}

Workflow Post Functions

// Jira workflow post function script
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import groovyx.net.http.RESTClient
import groovy.json.JsonBuilder

def issue = issue as Issue
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def nopesightField = customFieldManager.getCustomFieldObject("customfield_10100")
def nopesightId = issue.getCustomFieldValue(nopesightField)

if (nopesightId) {
// Update NopeSight when issue is resolved
if (issue.resolution) {
def client = new RESTClient('https://api.nopesight.com/')
client.headers['Authorization'] = 'Bearer ' + System.getenv('NOPESIGHT_TOKEN')

def response = client.patch(
path: "/events/${nopesightId}/status",
body: new JsonBuilder([
status: 'resolved',
resolution: issue.resolution.name,
resolved_by: issue.assignee?.emailAddress,
resolution_notes: "Resolved in Jira: ${issue.key}"
]).toString(),
contentType: 'application/json'
)
}
}

Reporting and Dashboards

Jira Dashboard Gadgets

// NopeSight gadget for Jira dashboard
const NopeSightGadget = {
descriptor: {
titleUrl: 'https://nopesight.com',
thumbnailUrl: 'https://nopesight.com/images/gadget-thumb.png',
categories: ['Charts', 'Other'],
vendor: {
name: 'NopeSight',
url: 'https://nopesight.com'
}
},

config: {
descriptor: {
fields: [
{
id: 'project',
type: 'project',
label: 'Project'
},
{
id: 'timeRange',
type: 'select',
label: 'Time Range',
options: [
{ label: 'Last 7 days', value: '7d' },
{ label: 'Last 30 days', value: '30d' },
{ label: 'Last 90 days', value: '90d' }
]
}
]
}
},

view: {
template: function(args) {
const stats = fetchNopeSightStats(args.project, args.timeRange);

return `
<div class="nopesight-gadget">
<div class="stats-grid">
<div class="stat-card">
<h3>${stats.total_discoveries}</h3>
<p>Discoveries</p>
</div>
<div class="stat-card">
<h3>${stats.auto_resolved}</h3>
<p>Auto-Resolved</p>
</div>
<div class="stat-card">
<h3>${stats.avg_resolution_time}</h3>
<p>Avg Resolution Time</p>
</div>
</div>
<div class="chart-container">
<canvas id="discovery-trend"></canvas>
</div>
</div>
`;
}
}
};

JQL Functions

// Custom JQL function for NopeSight data
public class NopeSightJQLFunction extends AbstractJqlFunction {

@Override
public MessageSet validate(User user, FunctionOperand operand,
TerminalClause terminalClause) {
return new MessageSetImpl();
}

@Override
public List<QueryLiteral> getValues(QueryCreationContext context,
FunctionOperand operand,
TerminalClause terminalClause) {

String functionName = operand.getName();
List<String> args = operand.getArgs();

if ("hasNopeSightAlert".equals(functionName)) {
return getIssuesWithActiveAlerts();
} else if ("affectedCI".equals(functionName)) {
return getIssuesByAffectedCI(args.get(0));
}

return Collections.emptyList();
}

private List<QueryLiteral> getIssuesWithActiveAlerts() {
// Query NopeSight API for active alerts
NopeSightClient client = new NopeSightClient();
List<String> issueKeys = client.getIssuesWithActiveAlerts();

return issueKeys.stream()
.map(key -> new QueryLiteral(operand, key))
.collect(Collectors.toList());
}
}

Best Practices

1. Issue Management

  • Use consistent naming conventions
  • Set up issue type mappings correctly
  • Configure appropriate workflows

2. Performance

  • Batch issue operations
  • Use JQL efficiently
  • Cache frequently accessed data

3. Security

  • Use API tokens, not passwords
  • Restrict integration user permissions
  • Audit integration activities

4. Data Quality

  • Validate data before creating issues
  • Handle special characters properly
  • Maintain field mapping documentation

Troubleshooting

Common Issues

troubleshooting:
authentication_errors:
symptom: "401 Unauthorized"
solutions:
- Verify API token is valid
- Check user has required permissions
- Ensure URL includes /rest/api/3/

field_mapping_errors:
symptom: "Field 'customfield_xxxxx' cannot be set"
solutions:
- Verify custom field exists
- Check field is on the create/edit screen
- Ensure correct field type

webhook_failures:
symptom: "Webhooks not triggering"
solutions:
- Verify webhook URL is accessible
- Check webhook filters
- Review Jira webhook logs

performance_issues:
symptom: "Slow issue creation"
solutions:
- Implement request batching
- Use async processing
- Optimize JQL queries