Skip to content

Commit

Permalink
kcidb/issue_editor: integrate kcidb-triage tool
Browse files Browse the repository at this point in the history
Co-authored-by: Jeny Sadadia <[email protected]>
  • Loading branch information
helen-fornazier and Jeny Sadadia committed Sep 18, 2024
1 parent 0f82c9f commit 1a8c876
Show file tree
Hide file tree
Showing 3 changed files with 351 additions and 0 deletions.
219 changes: 219 additions & 0 deletions kcidb/issue_editor/kcidb_triage_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#!/usr/bin/env python3
"""Kernel triage tool"""

import os
import json
import configparser
import re
import hashlib
from datetime import datetime
from flask import Flask, request, jsonify, render_template
import kcidb

app = Flask(__name__)


def read_server_config():
"""Read server configuration"""
config_parser = configparser.ConfigParser()
config_path = os.path.expanduser(
os.path.join(os.path.dirname(__file__), "server_config.ini"))
if not os.path.exists(config_path):
raise FileNotFoundError(f"Configuration file not found: {config_path}")

config_parser.read(config_path)
return config_parser


# config = read_server_config()
# project_id = config.get("database", "project_id")
# topic_name = config.get("database", "topic_name")
# origin = config.get("submission", "origin")

PROJECT_ID = "kernelci-production"
TOPIC_NAME = "playground_kcidb_new"
ORIGIN = "maestro"

# client = kcidb.Client(project_id=project_id, topic_name=topic_name)
client = kcidb.Client(project_id=PROJECT_ID, topic_name=TOPIC_NAME)


@app.route('/')
def index():
"""Root endpoint"""
return render_template('index.html')


@app.route('/submit_issue', methods=['POST'])
def submit_issue(): # pylint: disable=too-many-locals
"""Endpoint for submitting new issues"""
def build_pattern_object(categories, fields, values):
if not categories and not fields and not values:
return {}
# Initialize the automatching structure
pattern_object = {}

# Organize the fields based on category
for category, field, value in zip(categories, fields, values):
category = category + 's'
if category not in pattern_object:
pattern_object[category] = [{}]
pattern_object[category][0][field] = value

return pattern_object

if request.is_json:
data = request.get_json()
else:
data = request.form.to_dict()

user_name = data.get('name')
user_email = data.get('email')
report_subject = data.get('report_subject')
culprit_type = data.get('culprit_type')
report_url = data.get('report_url')
comment = data.get('comment')
misc = data.get('misc')
categories = request.form.getlist('category[]')
fields = request.form.getlist('field[]')
values = request.form.getlist('value[]')
object_pattern = build_pattern_object(categories, fields, values)
dry_run = data.get('dry_run', 'false') == 'true' if isinstance(
data.get('dry_run'), str) else bool(data.get('dry_run'))

if (not user_name or not user_email or not report_subject or
not culprit_type):
return jsonify({"error": "Missing required fields"}), 400

unique_string = f"{report_subject}_\
{datetime.now().strftime('%Y%m%d%H%M%S')}"
issue_id = hashlib.sha1(unique_string.encode()).hexdigest()

# Additional misc information
misc_json = json.loads(misc) if misc else {}
misc_json.update({"author": {"name": user_name, "email": user_email}})
if object_pattern:
misc_json.update({"object_pattern": object_pattern})

# Structure the report data
issue = {
"id": ORIGIN + ":" + issue_id,
# "id": origin + ":" + issue_id,
"version": 0,
# "origin": origin,
"origin": ORIGIN,
"report_subject": report_subject,
"culprit": {
"code": culprit_type == "code",
"tool": culprit_type == "tool",
"harness": culprit_type == "harness"
},
"comment": comment,
"misc": misc_json,
}
if report_url:
issue["report_url"] = report_url

report = {
"version": {
"major": 4,
"minor": 3
},
"checkouts": [],
"builds": [],
"tests": [],
"issues": [issue],
"incidents": []
}

try:
kcidb.io.SCHEMA.validate(report)
except Exception as e: # pylint: disable=broad-exception-caught
return jsonify({"error": str(e)}), 400

if dry_run:
return jsonify(report), 200

submission_id = client.submit(report)
return jsonify({
"submission_id": submission_id, "issue_id": issue["id"],
"issue_version": issue["version"]}), 200


@app.route('/submit_incidents', methods=['POST'])
def submit_incidents(): # pylint: disable=too-many-locals
"""Endpoint for submitting new incidents"""
if request.is_json:
data = request.get_json()
else:
data = request.form.to_dict()

user_name = data.get('name')
user_email = data.get('email')
issue_id = data.get('issue_id')
issue_version = int(data.get('issue_version'))
incident_type = data.get('incident_type')
ids_list = data.get('ids_list')
comment = data.get('comment')
misc = data.get('misc')
dry_run = data.get('dry_run', 'false') == 'true' if isinstance(
data.get('dry_run'), str) else bool(data.get('dry_run'))

if (not user_name or # pylint: disable=too-many-boolean-expressions
not user_email or not issue_id or not incident_type or
not ids_list or issue_version is None):
return jsonify({"error": "Missing required fields"}), 400

# Extract and clean IDs from the provided list
ids = [id.strip() for id in re.findall(
r'^[a-z0-9_]+:.*$', ids_list, re.MULTILINE)]
incidents = []
for item_id in ids:
unique_string = f"{item_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}"
incident_id = hashlib.sha1(unique_string.encode()).hexdigest()

# Additional misc information
misc_json = json.loads(misc) if misc else {}
misc_json.update({"author": {"name": user_name, "email": user_email}})

# Structure the incident data
incident = {
# "id": origin + ":" + incident_id,
# "origin": origin,
"id": ORIGIN + ":" + incident_id,
"origin": ORIGIN,
"issue_id": issue_id,
"issue_version": issue_version,
"present": True,
"comment": comment,
"misc": misc_json
}
if incident_type == "build":
incident["build_id"] = item_id
elif incident_type == "test":
incident["test_id"] = item_id

incidents.append(incident)

report = {
"version": {
"major": 4,
"minor": 3
},
"checkouts": [],
"builds": [],
"tests": [],
"issues": [],
"incidents": incidents
}

try:
kcidb.io.SCHEMA.validate(report)
except Exception as e: # pylint: disable=broad-exception-caught
return jsonify({"error": str(e)}), 400

if dry_run:
return jsonify(report), 200

submission_id = client.submit(report)
return jsonify({"submission_id": submission_id}), 200
6 changes: 6 additions & 0 deletions kcidb/issue_editor/server_config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[database]
project_id = kernelci-production
topic_name = playground_kcidb_new

[submission]
origin = kernelci_api
126 changes: 126 additions & 0 deletions kcidb/issue_editor/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>KCIDB Triage Tool</title>
<script>
const fieldsByCategory = {
"checkout": ["origin", "tree_name", "git_repository_url", "git_commit_hash", "git_commit_name", "git_repository_branch", "patchset_files", "patchset_hash", "message_id", "comment", "start_time", "contacts", "log_url", "log_excerpt", "valid", "misc"],
"build": ["log_content", "checkout_id", "origin", "comment", "start_time", "duration", "architecture", "command", "compiler", "input_files", "output_files", "config_name", "config_url", "log_url", "log_excerpt", "valid", "misc"],
"test": ["log_content", "build_id", "origin", "environment", "path", "comment", "log_url", "log_excerpt", "status", "waived", "start_time", "duration", "output_files", "misc"]
};

function addAutomatchingField() {
const container = document.getElementById('automatching_container');
const div = document.createElement('div');
div.className = 'automatching-field';
div.innerHTML = `
<label for="category">Category:</label>
<select name="category[]" required onchange="updateFieldOptions(this)">
<option value="checkout">Checkout</option>
<option value="test">Test</option>
<option value="build">Build</option>
</select>
<label for="field">Field:</label>
<select name="field[]" required>
${fieldsByCategory["checkout"].map(field => `<option value="${field}">${field}</option>`).join('')}
</select>
<label for="value">Regex:</label>
<input type="text" name="value[]" required>
<button type="button" onclick="removeAutomatchingField(this)">-</button>
<br><br>
`;
container.appendChild(div);
}

function removeAutomatchingField(button) {
button.parentElement.remove();
}

function updateFieldOptions(categorySelect) {
const selectedCategory = categorySelect.value;
const fieldSelect = categorySelect.nextElementSibling.nextElementSibling;
const fields = fieldsByCategory[selectedCategory];

fieldSelect.innerHTML = fields.map(field => `<option value="${field}">${field}</option>`).join('');
}
</script>
</head>
<body>
<h1>KCIDB Triage Tool</h1>
<h2>Submit Issue</h2>
<form action="/submit_issue" method="post">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required><br><br>

<label for="email">Email:</label>
<input type="email" id="email" name="email" required><br><br>

<label for="report_subject">Report Subject:</label>
<input type="text" id="report_subject" name="report_subject" required><br><br>

<label for="culprit_type">Culprit Type:</label>
<select id="culprit_type" name="culprit_type" required>
<option value="code">Code</option>
<option value="tool">Tool</option>
<option value="harness">Harness</option>
</select><br><br>

<label for="report_url">Report URL:</label>
<input type="url" id="report_url" name="report_url"><br><br>

<label for="comment">Comment:</label>
<textarea id="comment" name="comment"></textarea><br><br>

<label for="misc">Misc:</label>
<textarea id="misc" name="misc"></textarea><br><br>

<h3>Automatching</h3>
<div id="automatching_container"></div>
<button type="button" onclick="addAutomatchingField()">+</button><br><br>

<label for="dry_run">Dry Run:</label>
<input type="checkbox" id="dry_run" name="dry_run" value="true"><br><br>

<input type="submit" value="Submit">
</form>

<h2>Submit Incidents</h2>
<form action="/submit_incidents" method="post">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required><br><br>

<label for="email">Email:</label>
<input type="email" id="email" name="email" required><br><br>

<label for="issue_id">Issue ID:</label>
<input type="text" id="issue_id" name="issue_id" required><br><br>

<label for="issue_version">Issue Version:</label>
<input type="number" id="issue_version" name="issue_version" required><br><br>

<label for="incident_type">Incident Type:</label>
<select id="incident_type" name="incident_type" required>
<option value="build">Build</option>
<option value="test">Test</option>
</select><br><br>

<label for="ids_list">IDs List:</label>
<textarea id="ids_list" name="ids_list" required></textarea><br><br>

<label for="comment">Comment:</label>
<textarea id="comment" name="comment"></textarea><br><br>

<label for="misc">Misc:</label>
<textarea id="misc" name="misc"></textarea><br><br>

<label for="dry_run">Dry Run:</label>
<input type="checkbox" id="dry_run" name="dry_run" value="true"><br><br>

<input type="submit" value="Submit">
</form>
</body>
</html>

0 comments on commit 1a8c876

Please sign in to comment.