diff --git a/vulnerabilities/pipelines/v2_improvers/compute_advisory_todo.py b/vulnerabilities/pipelines/v2_improvers/compute_advisory_todo.py index 3eac97ea9..fca2b81cf 100644 --- a/vulnerabilities/pipelines/v2_improvers/compute_advisory_todo.py +++ b/vulnerabilities/pipelines/v2_improvers/compute_advisory_todo.py @@ -848,17 +848,15 @@ def get_disagreement_message(fixed_disagreement, affected_disagreement): if affected_disagreement: affected = ", ".join(affected_disagreement) - noun = "version" if len(affected_disagreement) == 1 else "versions" - verb = "is" if len(affected_disagreement) == 1 else "are" - - messages.append(f"Advisories do not agree whether {noun} {affected} {verb} affected.") + messages.append( + f"Advisories disagree on whether the following version(s) are affected: {affected}." + ) if fixed_disagreement: fixed = ", ".join(fixed_disagreement) - noun = "version" if len(fixed_disagreement) == 1 else "versions" - verb = "contains" if len(fixed_disagreement) == 1 else "contain" - - messages.append(f"Advisories do not agree whether {noun} {fixed} {verb} the fix.") + messages.append( + f"Advisories disagree on whether the following version(s) contain the fix: {fixed}." + ) return "\n".join(messages) diff --git a/vulnerabilities/templates/package_curation.html b/vulnerabilities/templates/package_curation.html index ddbf08d63..36d3ce1f4 100644 --- a/vulnerabilities/templates/package_curation.html +++ b/vulnerabilities/templates/package_curation.html @@ -7,11 +7,11 @@ {% endblock %} {% block content %} -
+
-

Advisory Curation

+

Advisory Package Curation

{{ vulnerability_id }}

@@ -32,7 +32,7 @@

{{ vulnerability_id }}

Advisory Summaries

-
+
{% for avid, text in advisory_summaries.items %}

{{ avid }}

@@ -51,6 +51,7 @@

Advisory Summaries

@@ -97,5 +98,5 @@

const baseAdvisoryUrl = "{% url 'advisory_details' 0 %}"; const curationItems = {{ curation_items|safe }}; - + {% endblock %} diff --git a/vulnerablecode/static/css/package_curation.css b/vulnerablecode/static/css/package_curation.css index c1b2988dc..00af968bf 100644 --- a/vulnerablecode/static/css/package_curation.css +++ b/vulnerablecode/static/css/package_curation.css @@ -8,6 +8,12 @@ color: #fff !important; } + .state-empty { + background-color: #ffdd57 !important; + color: #000 !important; + box-shadow: inset 0 0 0 1px #dbdbdb; + } + .table { table-layout: fixed !important; width: 100% !important; @@ -34,21 +40,20 @@ } #table-header th:first-child { - width: 100px !important; + width: 150px !important; } #table-header th:nth-child(2) { - width: 100px !important; + width: 150px !important; } #table-header th:nth-child(n+3) { - width: 100px !important; + width: 120px !important; } .curation-cell { cursor: pointer; text-align: center !important; - font-weight: 700; user-select: none; transition: background-color 0.1s ease; border: 1px solid #dbdbdb !important; @@ -59,9 +64,9 @@ box-shadow: inset 0 0 0 1px #3273dc; } - .advisory-cell, - .advisory-cell * { + .advisory-cell { cursor: not-allowed; + word-break: break-all; } .folded-row-marker, @@ -130,3 +135,10 @@ white-space: normal; flex-wrap: wrap; } + +.truncate-conflict-summary { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} \ No newline at end of file diff --git a/vulnerablecode/static/js/package_curation.js b/vulnerablecode/static/js/package_curation.js index 724bf2ad5..3b8db149d 100644 --- a/vulnerablecode/static/js/package_curation.js +++ b/vulnerablecode/static/js/package_curation.js @@ -23,24 +23,46 @@ const app = { document.getElementById('progress').value = progPercentage; document.getElementById('progress-text').innerText = `${this.currentIndex + 1} / ${total}`; document.getElementById('current-purl').innerText = item.purl; - document.getElementById('conflict-reason').innerText = item.conflict_reason; + if (!this.userStates[this.currentIndex]) { this.userStates[this.currentIndex] = {}; versions.forEach(v => { if (item.partial_curation.affected.includes(v)) this.userStates[this.currentIndex][v] = 'affected'; else if (item.partial_curation.fixing.includes(v)) this.userStates[this.currentIndex][v] = 'fixed'; - else this.userStates[this.currentIndex][v] = '?'; + else this.userStates[this.currentIndex][v] = 'empty'; }); } + this.renderConflictSummary(item); this.renderHeader(item); this.renderBody(item, versions); this.updateNavButtons(); }, + renderConflictSummary(item){ + const el = document.getElementById("conflict-reason"); + const btn = document.getElementById("toggle-conflict"); + + el.innerText = item.conflict_reason; + el.classList.add("truncate-conflict-summary"); + const hasOverflow = el.scrollHeight > el.clientHeight; + + if (hasOverflow) { + btn.style.display = "inline"; + + btn.onclick = () => { + const isTruncated = el.classList.toggle("truncate-conflict-summary"); + btn.innerText = isTruncated ? "Show more" : "Show less"; + }; + } else { + btn.style.display = "none"; + el.classList.remove("truncate-conflict-summary"); + } + }, + renderHeader(item) { const header = document.getElementById('table-header'); header.innerHTML = ` - Version + Package Versions
@@ -101,7 +123,9 @@ const app = { secTh.innerHTML = `
- Similar +
+ Similar +
${sec.advisory_uid} @@ -205,7 +229,7 @@ const app = { } else if (item.partial_curation.fixing.includes(v)) { this.userStates[this.currentIndex][v] = 'fixed'; } else { - this.userStates[this.currentIndex][v] = '?'; + this.userStates[this.currentIndex][v] = 'empty'; } }); this.renderBody(item, versions); @@ -216,8 +240,8 @@ const app = { const state = this.userStates[this.currentIndex][v]; tr.innerHTML = `${v}`; const userTd = document.createElement('td'); - userTd.className = `curation-cell state-${state} `; - userTd.innerText = state.toUpperCase(); + userTd.className = `curation-cell state-${state}`; + userTd.innerText = state === "empty"? "Select value": state.toUpperCase(); userTd.onclick = () => this.cycleState(v); tr.appendChild(userTd); @@ -227,7 +251,7 @@ const app = { const primaryState = advGroup.affected.includes(v) ? 'affected' : (advGroup.fixing.includes(v) ? 'fixed' : 'unaffected'); const td = document.createElement('td'); - td.className = `state-${primaryState} has-text-centered`; + td.className = `state-${primaryState} has-text-centered advisory-cell`; td.innerText = primaryState.toUpperCase(); tr.appendChild(td); @@ -238,7 +262,7 @@ const app = { const secState = secAffected.includes(v) ? 'affected' : (secFixing.includes(v) ? 'fixed' : 'unaffected'); const secTd = document.createElement('td'); - secTd.className = `state-${secState} has-text-centered`; + secTd.className = `state-${secState} has-text-centered advisory-cell`; secTd.style.borderLeft = "1px dashed #dbdbdb"; secTd.innerText = secState.toUpperCase(); tr.appendChild(secTd); diff --git a/vulnerablecode/static/js/package_curation.min.js b/vulnerablecode/static/js/package_curation.min.js deleted file mode 100644 index 4c2040e20..000000000 --- a/vulnerablecode/static/js/package_curation.min.js +++ /dev/null @@ -1,48 +0,0 @@ -const app={currentIndex:0,userStates:{},expandedFolds:new Set,showRanges:!1,foldAgreementBlocks:!0,init(){this.renderPackageCuration(),document.querySelector(".summary-text")?.addEventListener("click",e=>{e.target.closest("a")&&(e.target.target="_blank")})},renderPackageCuration(){const t=curationItems[this.currentIndex];var e=t.all_versions||t.all_version,s=curationItems.length,n=(this.currentIndex+1)/s*100;document.getElementById("progress").value=n,document.getElementById("progress-text").innerText=this.currentIndex+1+" / "+s,document.getElementById("current-purl").innerText=t.purl,document.getElementById("conflict-reason").innerText=t.conflict_reason,this.userStates[this.currentIndex]||(this.userStates[this.currentIndex]={},e.forEach(e=>{t.partial_curation.affected.includes(e)?this.userStates[this.currentIndex][e]="affected":t.partial_curation.fixing.includes(e)?this.userStates[this.currentIndex][e]="fixed":this.userStates[this.currentIndex][e]="?"})),this.renderHeader(t),this.renderBody(t,e),this.updateNavButtons()},renderHeader(e){const d=document.getElementById("table-header");d.innerHTML=` - Version - -
-
-
Curation
-
- -
- `,e.advisories.forEach((e,a)=>{var t=e.secondaries||[],s=0 -
- ${e.primary.advisory_uid} - - -
- `;let i="";s&&(i=` - - `),r.innerHTML=` -
-
- ${i} - ${e} -
- -
- `,d.appendChild(r),s&&n&&t.forEach((e,t)=>{var s=document.createElement("th"),n=(s.className="has-text-centered",s.style.backgroundColor="#fafafa",baseAdvisoryUrl.replace("0",e.advisory_uid));s.innerHTML=` -
- - -
- `,d.appendChild(s)})})},renderBody(e,s){var a=document.getElementById("curation-body");a.innerHTML="";let n=2;e.advisories.forEach((e,t)=>{n+=1,t=this.currentIndex+"-col-"+t,this.expandedFolds.has(t)&&(n+=(e.secondaries||[]).length)});var t=document.createElement("tr");if(t.innerHTML=` - - - ${this.showRanges?"Hide":"Show"} Version Ranges - `,a.appendChild(t),this.showRanges){t=document.createElement("tr");let n="";e.advisories.forEach((e,t)=>{t=this.currentIndex+"-col-"+t,t=this.expandedFolds.has(t);const s=e=>e.map(e=>{var t=[];return e.affected_vers&&""!==e.affected_vers.trim()&&t.push(`
Affected: ${e.affected_vers}
`),e.fixing_vers&&""!==e.fixing_vers.trim()&&t.push(`
Fixing: ${e.fixing_vers}
`),0No range specified
"}).join('
');n+=`${s(e.primary.vers_ranges)}`,t&&e.secondaries&&e.secondaries.forEach(e=>{n+=`${s(e.vers_ranges)}`})}),t.innerHTML=n,a.appendChild(t)}var r=this.getFoldableRanges(e,s);for(let t=0;tt>=e.start&&t<=e.end);if(i){var d=this.currentIndex+"-"+i.start;let e=this.expandedFolds.has(d);if(this.foldAgreementBlocks||(e=!this.expandedFolds.has(`${this.currentIndex}-${i.start}-collapsed`)),t===i.start&&((d=document.createElement("tr")).innerHTML=` - - ${e?"Hide":"Show"} Consensus Range (${i.end-i.start+1} versions) - `,a.appendChild(d)),!e){if(t===i.end)continue;t=i.end;continue}}a.appendChild(this.createRow(s[t],e))}},resetCurrentCuration(){const t=curationItems[this.currentIndex];var e=t.all_versions||t.all_version;e.forEach(e=>{t.partial_curation.affected.includes(e)?this.userStates[this.currentIndex][e]="affected":t.partial_curation.fixing.includes(e)?this.userStates[this.currentIndex][e]="fixed":this.userStates[this.currentIndex][e]="?"}),this.renderBody(t,e)},createRow(a,e){const r=document.createElement("tr");var t=this.userStates[this.currentIndex][a],s=(r.innerHTML=`${a}`,document.createElement("td"));return s.className=`curation-cell state-${t} `,s.innerText=t.toUpperCase(),s.onclick=()=>this.cycleState(a),r.appendChild(s),e.advisories.forEach((s,e)=>{var e=this.currentIndex+"-col-"+e,e=this.expandedFolds.has(e),t=s.affected.includes(a)?"affected":s.fixing.includes(a)?"fixed":"unaffected",n=document.createElement("td");n.className=`state-${t} has-text-centered`,n.innerText=t.toUpperCase(),r.appendChild(n),e&&s.secondaries&&s.secondaries.forEach(e=>{var t=e.affected||s.affected,e=e.fixing||s.fixing,t=t.includes(a)?"affected":e.includes(a)?"fixed":"unaffected";(e=document.createElement("td")).className=`state-${t} has-text-centered`,e.style.borderLeft="1px dashed #dbdbdb",e.innerText=t.toUpperCase(),r.appendChild(e)})}),r},getFoldableRanges(t,s){var n=[];let a=-1;for(let e=0;ee.affected.includes(r)?"affected":e.fixing.includes(r)?"fixed":"unaffected");i.every(e=>e===i[0])?-1===a&&(a=e):(-1!==a&&3<=e-a&&n.push({start:a,end:e-1}),a=-1)}return-1!==a&&3<=s.length-a&&n.push({start:a,end:s.length-1}),n},toggleFold(e){var t=this.currentIndex+"-"+e,e=this.currentIndex+`-${e}-collapsed`;this.foldAgreementBlocks?this.expandedFolds.has(t)?this.expandedFolds.delete(t):this.expandedFolds.add(t):this.expandedFolds.has(e)?this.expandedFolds.delete(e):this.expandedFolds.add(e);e=(t=curationItems[this.currentIndex]).all_versions||t.all_version;this.renderBody(t,e)},toggleColumnFold(e){e=this.currentIndex+"-col-"+e;this.expandedFolds.has(e)?this.expandedFolds.delete(e):this.expandedFolds.add(e);var t=(e=curationItems[this.currentIndex]).all_versions||e.all_version;this.renderHeader(e),this.renderBody(e,t)},toggleRanges(){this.showRanges=!this.showRanges;var e=curationItems[this.currentIndex],t=e.all_versions||e.all_version;this.renderBody(e,t)},cycleState(e){var t=["unaffected","affected","fixed"],s=this.userStates[this.currentIndex][e];this.userStates[this.currentIndex][e]=t[(t.indexOf(s)+1)%3];t=(e=curationItems[this.currentIndex]).all_versions||e.all_version;this.renderBody(e,t)},pickAdvisory(e,t,s){var n=curationItems[this.currentIndex];const a=n.advisories[e];(e=n.all_versions||n.all_version).forEach(e=>{a.affected.includes(e)?this.userStates[this.currentIndex][e]="affected":a.fixing.includes(e)?this.userStates[this.currentIndex][e]="fixed":this.userStates[this.currentIndex][e]="unaffected"}),this.renderBody(n,e)},navigate(e){this.currentIndex+=e,this.renderPackageCuration()},updateNavButtons(){document.getElementById("prev-btn").disabled=0===this.currentIndex;var e=this.currentIndex===curationItems.length-1;document.getElementById("next-btn").classList.toggle("is-hidden",e),document.getElementById("finish-btn").classList.toggle("is-hidden",!e)}};document.addEventListener("DOMContentLoaded",()=>app.init()); \ No newline at end of file