-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
kcidb/issue_editor: integrate
kcidb-triage
tool
Co-authored-by: Jeny Sadadia <[email protected]>
- Loading branch information
1 parent
0f82c9f
commit 1a8c876
Showing
3 changed files
with
351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |