Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a47099d
:tada: Advance reimport to update fix_available field #12633
manuel-sommer Aug 4, 2025
dc64088
docs
manuel-sommer Aug 4, 2025
7c6cd77
update
manuel-sommer Aug 26, 2025
f81a6c1
Update using_reimport.md
manuel-sommer Aug 26, 2025
223ccce
implement a fixed version
manuel-sommer Aug 28, 2025
ba9cefd
rebase fix
manuel-sommer Aug 28, 2025
ba6e983
Update dojo/models.py
manuel-sommer Aug 28, 2025
54e8035
Update default_reimporter.py
manuel-sommer Aug 28, 2025
8a00dd6
add unittests and grype
manuel-sommer Aug 28, 2025
d56eabb
update
manuel-sommer Aug 28, 2025
b0eb9d9
add unittests
manuel-sommer Aug 28, 2025
f7c2ed8
ruff
manuel-sommer Aug 28, 2025
89e2fed
update
manuel-sommer Aug 30, 2025
5c64fa0
sync migration
valentijnscholten Sep 1, 2025
a8bd413
rebase
manuel-sommer Oct 5, 2025
95bf03a
update according to comment
manuel-sommer Oct 5, 2025
0b069af
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 13, 2025
5f60667
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 15, 2025
c7c9da2
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 20, 2025
ebb603a
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 23, 2025
77362de
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 27, 2025
a7f51f5
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 27, 2025
328dd8a
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 29, 2025
e7e12a4
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 30, 2025
93d91bb
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 31, 2025
2dbbd39
update according to rebase
manuel-sommer Oct 31, 2025
bfa1d59
Merge branch 'dev' into reimport_fix_available
manuel-sommer Oct 31, 2025
f75e0b7
update
manuel-sommer Nov 1, 2025
2d25f3d
update
manuel-sommer Nov 1, 2025
8950a4c
Clarify reimport behavior for findings update
manuel-sommer Nov 1, 2025
e336a64
Merge branch 'dev' into reimport_fix_available
manuel-sommer Nov 4, 2025
165e13c
update
manuel-sommer Nov 4, 2025
927dbc2
Merge branch 'dev' into reimport_fix_available
manuel-sommer Nov 4, 2025
d82dfdd
Merge branch 'dev' into reimport_fix_available
manuel-sommer Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Any vulnerabilities which were not contained in the previous import will be adde

If any incoming Findings match Findings that already exist, the incoming Findings will be discarded rather than recorded as Duplicates. These Findings have been recorded already \- no need to add a new Finding object. The Test page will show these Findings as **Left Untouched**.

### Fields fix_available and fix_version

If any incoming Findings match Findings that already exist, the incoming Finding is checked if the fields `fix_available` and `fix_version` differ and are updated if yes. These Findings have been recorded already \- no need to add a new Finding object. The Test page will show these Findings as **Left Untouched**.

### Close Findings

If there are any Findings that already exist in the Test but which are not present in the incoming report, you can choose to automatically set those Findings to Inactive and Mitigated (on the assumption that those vulnerabilities have been resolved since the previous import). The Test page will show these Findings as **Closed**.
Expand Down
3 changes: 3 additions & 0 deletions docs/content/en/open_source/upgrading/2.52.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,8 @@ There are other instructions for upgrading to 2.52.x. Check the [Release Notes](

Mobsfscan Scan" has been merged into the "MobSF Scan" parser. The "Mobsfscan Scan" scan_type has been retained to keep deduplication working for existing Tests, but users are encouraged to move to the "MobSF Scan" scan_type.

## Reimport updates fields fix_available and fix_version
If any incoming Findings match Findings that already exist, the incoming Finding is checked if the fields `fix_available` and `fix_version` differ and are updated if yes.

## Release notes
Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.52.0) for the contents of the release.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 5.1.13 on 2025-11-01 12:54

import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dojo', '0246_endpoint_idx_ep_product_lower_host_and_more'),
]

operations = [
pgtrigger.migrations.RemoveTrigger(
model_name='finding',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='finding',
name='update_update',
),
pgtrigger.migrations.RemoveTrigger(
model_name='finding',
name='delete_delete',
),
migrations.AddField(
model_name='finding',
name='fix_version',
field=models.CharField(blank=True, help_text='Version of the affected component in which the flaw is fixed.', max_length=100, null=True, verbose_name='Fix version'),
),
migrations.AddField(
model_name='findingevent',
name='fix_version',
field=models.CharField(blank=True, help_text='Version of the affected component in which the flaw is fixed.', max_length=100, null=True, verbose_name='Fix version'),
),
pgtrigger.migrations.AddTrigger(
model_name='finding',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "dojo_findingevent" ("active", "component_name", "component_version", "created", "cve", "cvssv3", "cvssv3_score", "cvssv4", "cvssv4_score", "cwe", "date", "defect_review_requested_by_id", "description", "duplicate", "duplicate_finding_id", "dynamic_finding", "effort_for_fixing", "epss_percentile", "epss_score", "false_p", "file_path", "fix_available", "fix_version", "hash_code", "id", "impact", "is_mitigated", "kev_date", "known_exploited", "last_reviewed", "last_reviewed_by_id", "last_status_update", "line", "mitigated", "mitigated_by_id", "mitigation", "nb_occurences", "numerical_severity", "out_of_scope", "param", "payload", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "planned_remediation_date", "planned_remediation_version", "publish_date", "ransomware_used", "refs", "reporter_id", "review_requested_by_id", "risk_accepted", "sast_sink_object", "sast_source_file_path", "sast_source_line", "sast_source_object", "scanner_confidence", "service", "severity", "severity_justification", "sla_expiration_date", "sla_start_date", "sonarqube_issue_id", "static_finding", "steps_to_reproduce", "test_id", "thread_id", "title", "under_defect_review", "under_review", "unique_id_from_tool", "url", "verified", "vuln_id_from_tool") VALUES (NEW."active", NEW."component_name", NEW."component_version", NEW."created", NEW."cve", NEW."cvssv3", NEW."cvssv3_score", NEW."cvssv4", NEW."cvssv4_score", NEW."cwe", NEW."date", NEW."defect_review_requested_by_id", NEW."description", NEW."duplicate", NEW."duplicate_finding_id", NEW."dynamic_finding", NEW."effort_for_fixing", NEW."epss_percentile", NEW."epss_score", NEW."false_p", NEW."file_path", NEW."fix_available", NEW."fix_version", NEW."hash_code", NEW."id", NEW."impact", NEW."is_mitigated", NEW."kev_date", NEW."known_exploited", NEW."last_reviewed", NEW."last_reviewed_by_id", NEW."last_status_update", NEW."line", NEW."mitigated", NEW."mitigated_by_id", NEW."mitigation", NEW."nb_occurences", NEW."numerical_severity", NEW."out_of_scope", NEW."param", NEW."payload", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."planned_remediation_date", NEW."planned_remediation_version", NEW."publish_date", NEW."ransomware_used", NEW."refs", NEW."reporter_id", NEW."review_requested_by_id", NEW."risk_accepted", NEW."sast_sink_object", NEW."sast_source_file_path", NEW."sast_source_line", NEW."sast_source_object", NEW."scanner_confidence", NEW."service", NEW."severity", NEW."severity_justification", NEW."sla_expiration_date", NEW."sla_start_date", NEW."sonarqube_issue_id", NEW."static_finding", NEW."steps_to_reproduce", NEW."test_id", NEW."thread_id", NEW."title", NEW."under_defect_review", NEW."under_review", NEW."unique_id_from_tool", NEW."url", NEW."verified", NEW."vuln_id_from_tool"); RETURN NULL;', hash='7420e87ec2d068d96796af35888c418c547b768a', operation='INSERT', pgid='pgtrigger_insert_insert_2fbbb', table='dojo_finding', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='finding',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD."active" IS DISTINCT FROM (NEW."active") OR OLD."component_name" IS DISTINCT FROM (NEW."component_name") OR OLD."component_version" IS DISTINCT FROM (NEW."component_version") OR OLD."cve" IS DISTINCT FROM (NEW."cve") OR OLD."cvssv3" IS DISTINCT FROM (NEW."cvssv3") OR OLD."cvssv3_score" IS DISTINCT FROM (NEW."cvssv3_score") OR OLD."cvssv4" IS DISTINCT FROM (NEW."cvssv4") OR OLD."cvssv4_score" IS DISTINCT FROM (NEW."cvssv4_score") OR OLD."cwe" IS DISTINCT FROM (NEW."cwe") OR OLD."date" IS DISTINCT FROM (NEW."date") OR OLD."defect_review_requested_by_id" IS DISTINCT FROM (NEW."defect_review_requested_by_id") OR OLD."description" IS DISTINCT FROM (NEW."description") OR OLD."duplicate" IS DISTINCT FROM (NEW."duplicate") OR OLD."duplicate_finding_id" IS DISTINCT FROM (NEW."duplicate_finding_id") OR OLD."dynamic_finding" IS DISTINCT FROM (NEW."dynamic_finding") OR OLD."effort_for_fixing" IS DISTINCT FROM (NEW."effort_for_fixing") OR OLD."epss_percentile" IS DISTINCT FROM (NEW."epss_percentile") OR OLD."epss_score" IS DISTINCT FROM (NEW."epss_score") OR OLD."false_p" IS DISTINCT FROM (NEW."false_p") OR OLD."file_path" IS DISTINCT FROM (NEW."file_path") OR OLD."fix_available" IS DISTINCT FROM (NEW."fix_available") OR OLD."fix_version" IS DISTINCT FROM (NEW."fix_version") OR OLD."hash_code" IS DISTINCT FROM (NEW."hash_code") OR OLD."id" IS DISTINCT FROM (NEW."id") OR OLD."impact" IS DISTINCT FROM (NEW."impact") OR OLD."is_mitigated" IS DISTINCT FROM (NEW."is_mitigated") OR OLD."kev_date" IS DISTINCT FROM (NEW."kev_date") OR OLD."known_exploited" IS DISTINCT FROM (NEW."known_exploited") OR OLD."last_reviewed" IS DISTINCT FROM (NEW."last_reviewed") OR OLD."last_reviewed_by_id" IS DISTINCT FROM (NEW."last_reviewed_by_id") OR OLD."line" IS DISTINCT FROM (NEW."line") OR OLD."mitigated" IS DISTINCT FROM (NEW."mitigated") OR OLD."mitigated_by_id" IS DISTINCT FROM (NEW."mitigated_by_id") OR OLD."mitigation" IS DISTINCT FROM (NEW."mitigation") OR OLD."nb_occurences" IS DISTINCT FROM (NEW."nb_occurences") OR OLD."numerical_severity" IS DISTINCT FROM (NEW."numerical_severity") OR OLD."out_of_scope" IS DISTINCT FROM (NEW."out_of_scope") OR OLD."param" IS DISTINCT FROM (NEW."param") OR OLD."payload" IS DISTINCT FROM (NEW."payload") OR OLD."planned_remediation_date" IS DISTINCT FROM (NEW."planned_remediation_date") OR OLD."planned_remediation_version" IS DISTINCT FROM (NEW."planned_remediation_version") OR OLD."publish_date" IS DISTINCT FROM (NEW."publish_date") OR OLD."ransomware_used" IS DISTINCT FROM (NEW."ransomware_used") OR OLD."refs" IS DISTINCT FROM (NEW."refs") OR OLD."reporter_id" IS DISTINCT FROM (NEW."reporter_id") OR OLD."review_requested_by_id" IS DISTINCT FROM (NEW."review_requested_by_id") OR OLD."risk_accepted" IS DISTINCT FROM (NEW."risk_accepted") OR OLD."sast_sink_object" IS DISTINCT FROM (NEW."sast_sink_object") OR OLD."sast_source_file_path" IS DISTINCT FROM (NEW."sast_source_file_path") OR OLD."sast_source_line" IS DISTINCT FROM (NEW."sast_source_line") OR OLD."sast_source_object" IS DISTINCT FROM (NEW."sast_source_object") OR OLD."scanner_confidence" IS DISTINCT FROM (NEW."scanner_confidence") OR OLD."service" IS DISTINCT FROM (NEW."service") OR OLD."severity" IS DISTINCT FROM (NEW."severity") OR OLD."severity_justification" IS DISTINCT FROM (NEW."severity_justification") OR OLD."sla_expiration_date" IS DISTINCT FROM (NEW."sla_expiration_date") OR OLD."sla_start_date" IS DISTINCT FROM (NEW."sla_start_date") OR OLD."sonarqube_issue_id" IS DISTINCT FROM (NEW."sonarqube_issue_id") OR OLD."static_finding" IS DISTINCT FROM (NEW."static_finding") OR OLD."steps_to_reproduce" IS DISTINCT FROM (NEW."steps_to_reproduce") OR OLD."test_id" IS DISTINCT FROM (NEW."test_id") OR OLD."thread_id" IS DISTINCT FROM (NEW."thread_id") OR OLD."title" IS DISTINCT FROM (NEW."title") OR OLD."under_defect_review" IS DISTINCT FROM (NEW."under_defect_review") OR OLD."under_review" IS DISTINCT FROM (NEW."under_review") OR OLD."unique_id_from_tool" IS DISTINCT FROM (NEW."unique_id_from_tool") OR OLD."url" IS DISTINCT FROM (NEW."url") OR OLD."verified" IS DISTINCT FROM (NEW."verified") OR OLD."vuln_id_from_tool" IS DISTINCT FROM (NEW."vuln_id_from_tool"))', func='INSERT INTO "dojo_findingevent" ("active", "component_name", "component_version", "created", "cve", "cvssv3", "cvssv3_score", "cvssv4", "cvssv4_score", "cwe", "date", "defect_review_requested_by_id", "description", "duplicate", "duplicate_finding_id", "dynamic_finding", "effort_for_fixing", "epss_percentile", "epss_score", "false_p", "file_path", "fix_available", "fix_version", "hash_code", "id", "impact", "is_mitigated", "kev_date", "known_exploited", "last_reviewed", "last_reviewed_by_id", "last_status_update", "line", "mitigated", "mitigated_by_id", "mitigation", "nb_occurences", "numerical_severity", "out_of_scope", "param", "payload", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "planned_remediation_date", "planned_remediation_version", "publish_date", "ransomware_used", "refs", "reporter_id", "review_requested_by_id", "risk_accepted", "sast_sink_object", "sast_source_file_path", "sast_source_line", "sast_source_object", "scanner_confidence", "service", "severity", "severity_justification", "sla_expiration_date", "sla_start_date", "sonarqube_issue_id", "static_finding", "steps_to_reproduce", "test_id", "thread_id", "title", "under_defect_review", "under_review", "unique_id_from_tool", "url", "verified", "vuln_id_from_tool") VALUES (NEW."active", NEW."component_name", NEW."component_version", NEW."created", NEW."cve", NEW."cvssv3", NEW."cvssv3_score", NEW."cvssv4", NEW."cvssv4_score", NEW."cwe", NEW."date", NEW."defect_review_requested_by_id", NEW."description", NEW."duplicate", NEW."duplicate_finding_id", NEW."dynamic_finding", NEW."effort_for_fixing", NEW."epss_percentile", NEW."epss_score", NEW."false_p", NEW."file_path", NEW."fix_available", NEW."fix_version", NEW."hash_code", NEW."id", NEW."impact", NEW."is_mitigated", NEW."kev_date", NEW."known_exploited", NEW."last_reviewed", NEW."last_reviewed_by_id", NEW."last_status_update", NEW."line", NEW."mitigated", NEW."mitigated_by_id", NEW."mitigation", NEW."nb_occurences", NEW."numerical_severity", NEW."out_of_scope", NEW."param", NEW."payload", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."planned_remediation_date", NEW."planned_remediation_version", NEW."publish_date", NEW."ransomware_used", NEW."refs", NEW."reporter_id", NEW."review_requested_by_id", NEW."risk_accepted", NEW."sast_sink_object", NEW."sast_source_file_path", NEW."sast_source_line", NEW."sast_source_object", NEW."scanner_confidence", NEW."service", NEW."severity", NEW."severity_justification", NEW."sla_expiration_date", NEW."sla_start_date", NEW."sonarqube_issue_id", NEW."static_finding", NEW."steps_to_reproduce", NEW."test_id", NEW."thread_id", NEW."title", NEW."under_defect_review", NEW."under_review", NEW."unique_id_from_tool", NEW."url", NEW."verified", NEW."vuln_id_from_tool"); RETURN NULL;', hash='d7e612a41414689328bb28abab60a073aa989fad', operation='UPDATE', pgid='pgtrigger_update_update_92175', table='dojo_finding', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='finding',
trigger=pgtrigger.compiler.Trigger(name='delete_delete', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "dojo_findingevent" ("active", "component_name", "component_version", "created", "cve", "cvssv3", "cvssv3_score", "cvssv4", "cvssv4_score", "cwe", "date", "defect_review_requested_by_id", "description", "duplicate", "duplicate_finding_id", "dynamic_finding", "effort_for_fixing", "epss_percentile", "epss_score", "false_p", "file_path", "fix_available", "fix_version", "hash_code", "id", "impact", "is_mitigated", "kev_date", "known_exploited", "last_reviewed", "last_reviewed_by_id", "last_status_update", "line", "mitigated", "mitigated_by_id", "mitigation", "nb_occurences", "numerical_severity", "out_of_scope", "param", "payload", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "planned_remediation_date", "planned_remediation_version", "publish_date", "ransomware_used", "refs", "reporter_id", "review_requested_by_id", "risk_accepted", "sast_sink_object", "sast_source_file_path", "sast_source_line", "sast_source_object", "scanner_confidence", "service", "severity", "severity_justification", "sla_expiration_date", "sla_start_date", "sonarqube_issue_id", "static_finding", "steps_to_reproduce", "test_id", "thread_id", "title", "under_defect_review", "under_review", "unique_id_from_tool", "url", "verified", "vuln_id_from_tool") VALUES (OLD."active", OLD."component_name", OLD."component_version", OLD."created", OLD."cve", OLD."cvssv3", OLD."cvssv3_score", OLD."cvssv4", OLD."cvssv4_score", OLD."cwe", OLD."date", OLD."defect_review_requested_by_id", OLD."description", OLD."duplicate", OLD."duplicate_finding_id", OLD."dynamic_finding", OLD."effort_for_fixing", OLD."epss_percentile", OLD."epss_score", OLD."false_p", OLD."file_path", OLD."fix_available", OLD."fix_version", OLD."hash_code", OLD."id", OLD."impact", OLD."is_mitigated", OLD."kev_date", OLD."known_exploited", OLD."last_reviewed", OLD."last_reviewed_by_id", OLD."last_status_update", OLD."line", OLD."mitigated", OLD."mitigated_by_id", OLD."mitigation", OLD."nb_occurences", OLD."numerical_severity", OLD."out_of_scope", OLD."param", OLD."payload", _pgh_attach_context(), NOW(), \'delete\', OLD."id", OLD."planned_remediation_date", OLD."planned_remediation_version", OLD."publish_date", OLD."ransomware_used", OLD."refs", OLD."reporter_id", OLD."review_requested_by_id", OLD."risk_accepted", OLD."sast_sink_object", OLD."sast_source_file_path", OLD."sast_source_line", OLD."sast_source_object", OLD."scanner_confidence", OLD."service", OLD."severity", OLD."severity_justification", OLD."sla_expiration_date", OLD."sla_start_date", OLD."sonarqube_issue_id", OLD."static_finding", OLD."steps_to_reproduce", OLD."test_id", OLD."thread_id", OLD."title", OLD."under_defect_review", OLD."under_review", OLD."unique_id_from_tool", OLD."url", OLD."verified", OLD."vuln_id_from_tool"); RETURN NULL;', hash='b78d66e2d4e1cb791b58b944a8b9204f13fe1552', operation='DELETE', pgid='pgtrigger_delete_delete_72933', table='dojo_finding', when='AFTER')),
),
]
7 changes: 7 additions & 0 deletions dojo/importers/default_reimporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ def process_matched_mitigated_finding(
to cover circumstances where mitigation timestamps are different, and
decide which one to honor
"""
if existing_finding.fix_available != unsaved_finding.fix_available:
existing_finding.fix_available = unsaved_finding.fix_available
existing_finding.fix_version = unsaved_finding.fix_version

# if the reimported item has a mitigation time, we can compare
if unsaved_finding.is_mitigated:
# The new finding is already mitigated, so nothing to change on the
Expand Down Expand Up @@ -592,6 +596,9 @@ def process_matched_active_finding(
# First check that the existing finding is definitely not mitigated
if not (existing_finding.mitigated and existing_finding.is_mitigated):
logger.debug("Reimported item matches a finding that is currently open.")
if existing_finding.fix_available != unsaved_finding.fix_available:
existing_finding.fix_available = unsaved_finding.fix_available
existing_finding.fix_version = unsaved_finding.fix_version
if unsaved_finding.is_mitigated:
logger.debug("Reimported mitigated item matches a finding that is currently open, closing.")
# TODO: Implement a date comparison for opened defectdojo findings before closing them by reimporting,
Expand Down
5 changes: 5 additions & 0 deletions dojo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2434,6 +2434,11 @@ class Finding(models.Model):
default=None,
verbose_name=_("Fix Available"),
help_text=_("Denotes if there is a fix available for this flaw."))
fix_version = models.CharField(null=True,
blank=True,
max_length=100,
verbose_name=_("Fix version"),
help_text=_("Version of the affected component in which the flaw is fixed."))
impact = models.TextField(verbose_name=_("Impact"),
null=True,
blank=True,
Expand Down
20 changes: 20 additions & 0 deletions dojo/templates/dojo/view_finding.html
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,12 @@ <h3 class="pull-left finding-title">
{% if finding.component_version %}
<th>Component Version</th>
{% endif %}
{% if finding.fix_available %}
<th>Fix Available</th>
{% endif %}
{% if finding.fix_version %}
<th>Fixed Version</th>
{% endif %}
{% if finding.has_jira_configured or finding.jira_issue %}
<th>JIRA</th>
<th>JIRA Change</th>
Expand Down Expand Up @@ -611,6 +617,20 @@ <h3 class="pull-left finding-title">
</span>
</td>
{% endif %}
{% if finding.fix_available %}
<td>
<span>
{{ finding.fix_available }}
</span>
</td>
{% endif %}
{% if finding.fix_version %}
<td>
<span>
{{ finding.fix_version }}
</span>
</td>
{% endif %}
{% if finding.has_jira_configured or finding.has_jira_issue or finding.has_jira_group_issue %}
<td id="jira">
{% if finding.has_jira_group_issue %}
Expand Down
Loading