Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ datatracker.sublime-workspace
/docker/docker-compose.extend-custom.yml
/env
/ghostdriver.log
/geckodriver.log
/htmlcov
/ietf/static/dist-neue
/latest-coverage.json
Expand Down
23 changes: 23 additions & 0 deletions ietf/doc/views_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,29 @@ def _state_to_doc_type(state):
)
ad.buckets = copy.deepcopy(bucket_template)

# https://github.com/ietf-tools/datatracker/issues/4577
docs_via_group_ad = Document.objects.exclude(
group__acronym="none"
).filter(
group__role__name="ad",
group__role__person=ad
).filter(
states__type="draft-stream-ietf",
states__slug__in=["wg-doc","wg-lc","waiting-for-implementation","chair-w","writeupw"]
)

doc_for_ad = Document.objects.filter(ad=ad)

ad.pre_pubreq = (docs_via_group_ad | doc_for_ad).filter(
type="draft"
).filter(
states__type="draft",
states__slug="active"
).filter(
states__type="draft-iesg",
states__slug="idexists"
).distinct().count()

for doc in Document.objects.exclude(type_id="rfc").filter(ad=ad):
dt = doc_type(doc)
state = doc_state(doc)
Expand Down
234 changes: 157 additions & 77 deletions ietf/templates/doc/ad_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ <h2 class="mt-5" id="{{ dt.type.0 }}">{{ dt.type.1 }} State Counts</h2>
<thead class="wrap-anywhere">
<tr>
<th scope="col" data-sort="name">Area Director</th>
{% if dt.type.1 == "Internet-Draft" %}
<th scope="col" data-sort="pre-pubreq">Pre pubreq</th>
{% endif %}
{% for state, state_name in dt.states %}
<th scope="col" class="col-1" title=""
data-sort="{{ state }}-num">
<th scope="col" class="col-1" data-sort="{{ state }}-num"
>
<a href="{% url 'ietf.doc.views_help.state_help' type='draft-iesg' %}#{{ state }}">
{{ state_name|split:'/'|join:'/<wbr>' }}
</a>
Expand All @@ -51,6 +54,15 @@ <h2 class="mt-5" id="{{ dt.type.0 }}">{{ dt.type.1 }} State Counts</h2>
<td>
<a href="{{ ad.dashboard }}">{{ ad.name }}</a>
</td>
{% if dt.type.1 == "Internet-Draft" %}
<td
class="col-1 align-bottom"
data-sum="pre-pubreq"
data-series-graph
>
{{ ad.pre_pubreq }}
</td>
{% endif %}
{% for state, state_name in dt.states %}
<td class="col-1 align-bottom"
id="{{ dt.type.0 }}-{{ ad|slugify }}-{{ state }}">
Expand All @@ -63,6 +75,11 @@ <h2 class="mt-5" id="{{ dt.type.0 }}">{{ dt.type.1 }} State Counts</h2>
<tfoot class="table-group-divider">
<tr>
<th scope="row">Sum</th>
{% if dt.type.1 == "Internet-Draft" %}
<td class="align-bottom">
<div data-sum-result="pre-pubreq" data-series-graph></div>
</td>
{% endif %}
{% for state, state_name in dt.states %}
<td class="align-bottom">
<div id="chart-{{ dt.type.0 }}-sum-{{ state }}"></div>
Expand All @@ -87,37 +104,151 @@ <h2 class="mt-5" id="{{ dt.type.0 }}">{{ dt.type.1 }} State Counts</h2>
</script>
<script src="{% static "ietf/js/highcharts.js" %}"></script>
{{ data|json_script:"data" }}

<script>
const data = JSON.parse(document.getElementById("data").textContent);
function highchartsConfigFactory({ element, ymax, series }){
return {
title: { text: undefined },
chart: {
type: "line",
animation: false,
renderTo: element,
panning: { enabled: false },
spacing: [4, 0, 5, 0],
height: "45%"
},
scrollbar: { enabled: false },
tooltip: { enabled: false },
navigator: { enabled: false },
exporting: { enabled: false },
legend: { enabled: false },
credits: { enabled: false },
xAxis: {
title: { text: undefined},
labels: { enabled: false },
zoomEnabled: false,
tickLength: 0
},
yAxis: {
title: { text: undefined},
zoomEnabled: false,
tickLength: 0,
labels: { x: -3 },
min: 0,
max: ymax,
tickInterval: ymax
},
plotOptions: {
series: {
animation: false,
dataLabels: {
enabled: true,
inside: true,
padding: 0,
formatter: function() {
// only label the last
// (= current-day) point in
// the curve with today's
// value
if (this.point.index + 1 ==
this.series.points.length) {
return this.y
}
return undefined
}
}
}
},
series: [{
name: undefined,
data: series,
enableMouseTracking: false
}]
}
}
</script>

<script>
const GRAPH_BUFFER = 2;

function safeParseFloat(text) {
const trimNumber = text.trim()
if(!trimNumber.match(/^[0-9.]+$/)) {
console.warn(`Unable to parse "${trimNumber}" as a number.`)
return Number.NaN
}
return parseFloat(text)
}

Array.from(document.querySelectorAll("table"))
.filter(table => Boolean(table.querySelector("[data-sum]")))
.forEach(table => {
const sums = Array.from(table.querySelectorAll("[data-sum]")).reduce((
sumsAccumulator,
cell
) => {
const key = cell.dataset.sum
const value = safeParseFloat(cell.textContent)
if(key && !isNaN(value)) {
sumsAccumulator[key] = (sumsAccumulator[key] || 0) + (value || 0)
}
return sumsAccumulator
}, {})

Array.from(table.querySelectorAll('[data-sum-result]')).forEach(result => {
const key = result.dataset.sumResult
const value = sums[key]

if(value) {
result.innerText = value
}
})

Array.from(table.querySelectorAll('[data-series-graph]')).forEach(element => {
const endValue = safeParseFloat(element.innerText)
if(isNaN(endValue)) throw Error("Can't render Highcharts chart with non-numerical " + element.innerText)

const ymax = Math.max(1, endValue + GRAPH_BUFFER)

Highcharts.chart(
highchartsConfigFactory({
element,
ymax,
series: [endValue]
})
)
})
})
</script>
<script>
const data = JSON.parse(document.getElementById("data").textContent)

Object.entries(data).forEach(([dt, ads]) => {
max = {};
max = {}
Object.entries(ads).forEach(([ad, states]) => {
Object.entries(states).forEach(([state, buckets]) => {
buckets.series = buckets.map((x) => x.length);
buckets.series = buckets.map((x) => x.length)
if (ad != "sum") {
max[state] = Math.max(...buckets.series,
max[state] ? max[state] : 0);
max[state] = Math.max(...buckets.series, max[state] ? max[state] : 0)
}
});
});
})
})

Object.entries(ads).forEach(([ad, states]) => {
Object.entries(states).forEach(([state, buckets]) => {
const cell = `chart-${dt}-${ad}-${state}`;
const cell = `chart-${dt}-${ad}-${state}`

// if there is only a single datapoint in the
// bucket, display it without a graph
if (buckets.series.length == 1) {
document.getElementById(cell).innerHTML =
buckets.series[0];
return;
document.getElementById(cell).innerHTML = buckets.series[0]
return
}

// if a bucket has all zeroes, fake a Highcharts
// plot with HTML, to reduce the number of plot
// objects on the page
if (buckets.series.every((x) => x == 0)) {
if (buckets.series.every((x) => x === 0)) {
// document.getElementById(cell).innerHTML = `
// <div class="position-relative">
// <div class="position-absolute bottom-0 start-0">
Expand All @@ -128,71 +259,20 @@ <h2 class="mt-5" id="{{ dt.type.0 }}">{{ dt.type.1 }} State Counts</h2>
// </div>
// </div>
// `;
return;
return
}

// else actually create a graph
const ymax = Math.max(1, ad != "sum" ? max[state] : Math.max(...buckets.series));
Highcharts.chart({
title: { text: undefined },
chart: {
type: "line",
animation: false,
renderTo: cell,
panning: { enabled: false },
spacing: [4, 0, 5, 0],
height: "45%",
},
scrollbar: { enabled: false },
tooltip: { enabled: false },
navigator: { enabled: false },
exporting: { enabled: false },
legend: { enabled: false },
credits: { enabled: false },
xAxis: {
title: { text: undefined},
labels: { enabled: false },
zoomEnabled: false,
tickLength: 0,
},
yAxis: {
title: { text: undefined},
zoomEnabled: false,
tickLength: 0,
labels: { x: -3 },
min: 0,
max: ymax,
tickInterval: ymax,
},
plotOptions: {
series: {
animation: false,
dataLabels: {
enabled: true,
inside: true,
padding: 0,
formatter: function() {
// only label the last
// (= current-day) point in
// the curve with today's
// value
if (this.point.index + 1 ==
this.series.points.length) {
return this.y;
}
return undefined;
}
}
}
},
series: [{
name: undefined,
data: buckets.series,
enableMouseTracking: false,
}],
});
});
});
});
const ymax = Math.max(1, ad !== "sum" ? max[state] : Math.max(...buckets.series))
Highcharts.chart(
highchartsConfigFactory({
element: cell,
ymax,
series: buckets.series
})
)
})
})
})
</script>
{% endblock %}
26 changes: 26 additions & 0 deletions playwright/tests-legacy/docs/ad.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { test, expect } = require('@playwright/test')
const viewports = require('../../helpers/viewports')

// ====================================================================
// IESG Dashboard
// ====================================================================

test.describe('/doc/ad/', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({
width: viewports.desktop[0],
height: viewports.desktop[1]
})

await page.goto('/doc/ad/')
})

test('Pre pubreq', async ({ page }) => {
const tablesLocator = page.locator('table')
const tablesCount = await tablesLocator.count()
expect(tablesCount).toBeGreaterThan(0)
const firstTable = tablesLocator.nth(0)
const theadTexts = await firstTable.locator('thead').allInnerTexts()
expect(theadTexts.join('')).toContain('Pre pubreq')
})
})