ci: merge main to release (#7871)

ci: merge main to release
This commit is contained in:
Robert Sparks 2024-08-28 15:47:18 -05:00 committed by GitHub
commit 4635e3dd42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 297 additions and 98 deletions

View file

@ -434,7 +434,7 @@ function reconnectScrollObservers () {
scrollObserver.disconnect() scrollObserver.disconnect()
visibleDays.length = 0 visibleDays.length = 0
for (const mDay of agendaStore.meetingDays) { for (const mDay of agendaStore.meetingDays) {
const el = document.getElementById(`agenda-day-${mDay.slug}`) const el = document.getElementById(mDay.slug)
el.dataset.dayId = mDay.slug.toString() el.dataset.dayId = mDay.slug.toString()
el.dataset.dayTs = mDay.ts el.dataset.dayTs = mDay.ts
scrollObserver.observe(el) scrollObserver.observe(el)

View file

@ -29,7 +29,6 @@
<script setup> <script setup>
import { computed, h } from 'vue' import { computed, h } from 'vue'
import { import {
NBadge, NBadge,
NDropdown, NDropdown,
@ -51,21 +50,48 @@ const siteStore = useSiteStore()
// Meeting Days // Meeting Days
function optionToLink(opts){
const { key, label, icon } = opts
return {
...opts,
type: 'render',
render: () => h(
'a',
{
class: 'dropdown-link',
'data-testid': 'mobile-link',
href: `#${key}`
},
[
h(
'span',
icon()
),
h(
'span',
label
)
]
)
}
}
const jumpToDayOptions = computed(() => { const jumpToDayOptions = computed(() => {
const days = [] const days = []
if (agendaStore.isMeetingLive) { if (agendaStore.isMeetingLive) {
days.push({ days.push(optionToLink({
label: 'Jump to Now', label: 'Jump to Now',
key: 'now', key: 'now',
icon: () => h('i', { class: 'bi bi-arrow-down-right-square text-red' }) icon: () => h('i', { class: 'bi bi-arrow-down-right-square text-red' })
}) }))
} }
for (const day of agendaStore.meetingDays) { for (const day of agendaStore.meetingDays) {
days.push({ days.push(optionToLink({
label: `Jump to ${day.label}`, label: `Jump to ${day.label}`,
key: day.slug, key: day.slug,
icon: () => h('i', { class: 'bi bi-arrow-down-right-square' }) icon: () => h('i', { class: 'bi bi-arrow-down-right-square' })
}) }))
} }
return days return days
}) })
@ -90,14 +116,13 @@ const downloadIcsOptions = [
function jumpToDay (dayId) { function jumpToDay (dayId) {
if (dayId === 'now') { if (dayId === 'now') {
const lastEventId = agendaStore.findCurrentEventId() const lastEventId = agendaStore.findCurrentEventId()
if (lastEventId) { if (lastEventId) {
document.getElementById(`agenda-rowid-${lastEventId}`)?.scrollIntoView(true) document.getElementById(`agenda-rowid-${lastEventId}`)?.scrollIntoView(true)
} else { } else {
message.warning('There is no event happening right now.') message.warning('There is no event happening right now.')
} }
} else { } else {
document.getElementById(`agenda-day-${dayId}`)?.scrollIntoView(true) document.getElementById(dayId)?.scrollIntoView(true)
} }
} }
@ -162,4 +187,19 @@ function downloadIcs (key) {
} }
} }
} }
.dropdown-link {
display: flex;
text-decoration:none;
gap: 0.2rem 0.5rem;
padding: 0.5em;
color: var(--bs-body-color);
&:hover,
&:focus {
background-color: var(--bs-dark-bg-subtle);
text-decoration: underline;
}
}
</style> </style>

View file

@ -99,7 +99,7 @@
li.nav-item(v-for='day of agendaStore.meetingDays') li.nav-item(v-for='day of agendaStore.meetingDays')
a.nav-link( a.nav-link(
:class='agendaStore.dayIntersectId === day.slug ? `active` : ``' :class='agendaStore.dayIntersectId === day.slug ? `active` : ``'
:href='`#slot-` + day.slug' :href='`#${day.slug}`'
@click='scrollToDay(day.slug, $event)' @click='scrollToDay(day.slug, $event)'
) )
i.bi.bi-arrow-right-short.d-none.d-xxl-inline.me-2 i.bi.bi-arrow-right-short.d-none.d-xxl-inline.me-2
@ -109,7 +109,6 @@
<script setup> <script setup>
import { computed, h } from 'vue' import { computed, h } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { DateTime } from 'luxon'
import { import {
NAffix, NAffix,
NBadge, NBadge,
@ -200,14 +199,11 @@ function pickerDiscard () {
} }
} }
function scrollToDay (dayId, ev) { function scrollToDay (daySlug, ev) {
ev.preventDefault() document.getElementById(daySlug)?.scrollIntoView(true)
document.getElementById(`agenda-day-${dayId}`)?.scrollIntoView(true)
} }
function scrollToNow (ev) { function scrollToNow (ev) {
ev.preventDefault()
const lastEventId = agendaStore.findCurrentEventId() const lastEventId = agendaStore.findCurrentEventId()
if (lastEventId) { if (lastEventId) {

View file

@ -24,7 +24,7 @@
) )
//- ROW - DAY HEADING ----------------------- //- ROW - DAY HEADING -----------------------
template(v-if='item.displayType === `day`') template(v-if='item.displayType === `day`')
td(:id='`agenda-day-` + item.id', :colspan='pickerModeActive ? 6 : 5') {{item.date}} td(:id='item.slug', :colspan='pickerModeActive ? 6 : 5') {{item.date}}
//- ROW - SESSION HEADING ------------------- //- ROW - SESSION HEADING -------------------
template(v-else-if='item.displayType === `session-head`') template(v-else-if='item.displayType === `session-head`')
td.agenda-table-cell-check(v-if='pickerModeActive') &nbsp; td.agenda-table-cell-check(v-if='pickerModeActive') &nbsp;
@ -200,7 +200,7 @@ import {
import AgendaDetailsModal from './AgendaDetailsModal.vue' import AgendaDetailsModal from './AgendaDetailsModal.vue'
import { useAgendaStore } from './store' import { useAgendaStore, daySlugPrefix, daySlug } from './store'
import { useSiteStore } from '../shared/store' import { useSiteStore } from '../shared/store'
import { getUrl } from '../shared/urls' import { getUrl } from '../shared/urls'
@ -248,6 +248,7 @@ const meetingEvents = computed(() => {
if (itemDate.toISODate() !== acc.lastDate) { if (itemDate.toISODate() !== acc.lastDate) {
acc.result.push({ acc.result.push({
id: item.id, id: item.id,
slug: daySlug(item),
key: `day-${itemDate.toISODate()}`, key: `day-${itemDate.toISODate()}`,
displayType: 'day', displayType: 'day',
date: itemDate.toLocaleString(DateTime.DATE_HUGE), date: itemDate.toLocaleString(DateTime.DATE_HUGE),
@ -575,6 +576,30 @@ function recalculateRedLine () {
} }
} }
/**
* On page load when browser location hash contains '#now' or '#agenda-day-*' then scroll accordingly
*/
;(function scrollToHashInit() {
if (!window.location.hash) {
return
}
if (!(window.location.hash === "#now" || window.location.hash.startsWith(`#${daySlugPrefix}`))) {
return
}
const unsubscribe = agendaStore.$subscribe((_mutation, agendaStoreState) => {
if (agendaStoreState.schedule.length === 0) {
return
}
unsubscribe() // we only need to scroll once, so unsubscribe from future updates
if(window.location.hash === "#now") {
const lastEventId = agendaStore.findCurrentEventId()
document.getElementById(`agenda-rowid-${lastEventId}`)?.scrollIntoView(true)
} else if(window.location.hash.startsWith(`#${daySlugPrefix}`)) {
document.getElementById(window.location.hash.substring(1))?.scrollIntoView(true)
}
})
})()
// MOUNTED // MOUNTED
onMounted(() => { onMounted(() => {

View file

@ -121,7 +121,7 @@ export const useAgendaStore = defineStore('agenda', {
meetingDays () { meetingDays () {
const siteStore = useSiteStore() const siteStore = useSiteStore()
return uniqBy(this.scheduleAdjusted, 'adjustedStartDate').sort().map(s => ({ return uniqBy(this.scheduleAdjusted, 'adjustedStartDate').sort().map(s => ({
slug: s.id.toString(), slug: daySlug(s),
ts: s.adjustedStartDate, ts: s.adjustedStartDate,
label: siteStore.viewport < 1350 ? DateTime.fromISO(s.adjustedStartDate).toFormat('ccc LLL d') : DateTime.fromISO(s.adjustedStartDate).toLocaleString(DateTime.DATE_HUGE) label: siteStore.viewport < 1350 ? DateTime.fromISO(s.adjustedStartDate).toFormat('ccc LLL d') : DateTime.fromISO(s.adjustedStartDate).toLocaleString(DateTime.DATE_HUGE)
})) }))
@ -292,3 +292,8 @@ function findFirstConferenceUrl (txt) {
} catch (err) { } } catch (err) { }
return null return null
} }
export const daySlugPrefix = 'agenda-day-'
export function daySlug(s) {
return `${daySlugPrefix}${s.adjustedStartDate}` // eg 'agenda-day-2024-08-13'
}

View file

@ -1,11 +1,11 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist' import piniaPersist from 'pinia-plugin-persist'
import Embedded from './Embedded.vue' import Embedded from './Embedded.vue'
import { createPiniaSingleton } from './shared/create-pinia-singleton'
// Initialize store (Pinia) // Initialize store (Pinia)
const pinia = createPinia() const pinia = createPiniaSingleton()
pinia.use(piniaPersist) pinia.use(piniaPersist)
// Mount App // Mount App

View file

@ -1,14 +1,14 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist' import piniaPersist from 'pinia-plugin-persist'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { createPiniaSingleton } from './shared/create-pinia-singleton'
const app = createApp(App, {}) const app = createApp(App, {})
// Initialize store (Pinia) // Initialize store (Pinia)
const pinia = createPinia() const pinia = createPiniaSingleton()
pinia.use(piniaPersist) pinia.use(piniaPersist)
app.use(pinia) app.use(pinia)

View file

@ -0,0 +1,6 @@
import { createPinia } from 'pinia'
export function createPiniaSingleton(){
window.pinia = window.pinia ?? createPinia()
return window.pinia
}

View file

@ -0,0 +1,30 @@
# Copyright The IETF Trust 2024, All Rights Reserved
from django.db import migrations
def forward(apps, schema_editor):
State = apps.get_model("doc", "State")
State.objects.get_or_create(
type_id="bofreq",
slug="spam",
defaults={"name": "Spam", "desc": "The BOF request is spam", "order": 5},
)
def reverse(apps, schema_editor):
State = apps.get_model("doc", "State")
Document = apps.get_model("doc", "Document")
assert not Document.objects.filter(
states__type="bofreq", states__slug="spam"
).exists()
State.objects.filter(type_id="bofreq", slug="spam").delete()
class Migration(migrations.Migration):
dependencies = [
("doc", "0022_remove_dochistory_internal_comments_and_more"),
]
operations = [migrations.RunPython(forward, reverse)]

View file

@ -96,9 +96,14 @@ def ballot_icon(context, doc):
positions = list(ballot.active_balloter_positions().items()) positions = list(ballot.active_balloter_positions().items())
positions.sort(key=sort_key) positions.sort(key=sort_key)
request = context.get("request")
ballot_edit_return_point_param = f"ballot_edit_return_point={request.path}"
right_click_string = '' right_click_string = ''
if has_role(user, "Area Director"): if has_role(user, "Area Director"):
right_click_string = 'oncontextmenu="window.location.href=\'%s\';return false;"' % urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk)) right_click_string = 'oncontextmenu="window.location.href=\'{}?{}\';return false;"'.format(
urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk)),
ballot_edit_return_point_param)
my_blocking = False my_blocking = False
for i, (balloter, pos) in enumerate(positions): for i, (balloter, pos) in enumerate(positions):
@ -113,10 +118,14 @@ def ballot_icon(context, doc):
typename = "RSAB" typename = "RSAB"
else: else:
typename = "IESG" typename = "IESG"
modal_url = "{}?{}".format(
urlreverse("ietf.doc.views_doc.ballot_popup", kwargs=dict(name=doc.name, ballot_id=ballot.pk)),
ballot_edit_return_point_param)
res = ['<a %s href="%s" data-bs-toggle="modal" data-bs-target="#modal-%d" aria-label="%s positions" title="%s positions (click to show more)" class="ballot-icon"><table' % ( res = ['<a %s href="%s" data-bs-toggle="modal" data-bs-target="#modal-%d" aria-label="%s positions" title="%s positions (click to show more)" class="ballot-icon"><table' % (
right_click_string, right_click_string,
urlreverse("ietf.doc.views_doc.ballot_popup", kwargs=dict(name=doc.name, ballot_id=ballot.pk)), modal_url,
ballot.pk, ballot.pk,
typename, typename,
typename,)] typename,)]

View file

@ -0,0 +1,29 @@
# Copyright The IETF Trust 2015-2020, All Rights Reserved
from django import template
from django.conf import settings
from django.template.loader import render_to_string
from ietf.utils.log import log
register = template.Library()
@register.simple_tag
def document_type_badge(doc, snapshot, submission, resurrected_by):
context = {"doc": doc, "snapshot": snapshot, "submission": submission, "resurrected_by": resurrected_by}
if doc.type_id == "rfc":
return render_to_string(
"doc/badge/doc-badge-rfc.html",
context,
)
elif doc.type_id == "draft":
return render_to_string(
"doc/badge/doc-badge-draft.html",
context,
)
else:
error_message = f"Unsupported document type {doc.type_id}."
if settings.SERVER_MODE != 'production':
raise ValueError(error_message)
else:
log(error_message)
return ""

View file

@ -230,6 +230,9 @@ class EditPositionTests(TestCase):
r = self.client.post(url, dict(position="discuss", discuss="Test discuss text")) r = self.client.post(url, dict(position="discuss", discuss="Test discuss text"))
self.assertEqual(r.status_code, 403) self.assertEqual(r.status_code, 403)
# N.B. This test needs to be rewritten to exercise all types of ballots (iesg, irsg, rsab)
# and test against the output of the mailtriggers instead of looking for hardcoded values
# in the To and CC results. See #7864
def test_send_ballot_comment(self): def test_send_ballot_comment(self):
ad = Person.objects.get(user__username="ad") ad = Person.objects.get(user__username="ad")
draft = WgDraftFactory(ad=ad,group__acronym='mars') draft = WgDraftFactory(ad=ad,group__acronym='mars')
@ -1455,18 +1458,14 @@ class BallotContentTests(TestCase):
class ReturnToUrlTests(TestCase): class ReturnToUrlTests(TestCase):
def test_invalid_return_to_url(self): def test_invalid_return_to_url(self):
self.assertRaises( with self.assertRaises(ValueError):
Exception, parse_ballot_edit_return_point('/', 'draft-ietf-opsawg-ipfix-tcpo-v6eh', '998718')
lambda: parse_ballot_edit_return_point('/doc/', 'draft-ietf-opsawg-ipfix-tcpo-v6eh', '998718'),
) with self.assertRaises(ValueError):
self.assertRaises( parse_ballot_edit_return_point('/a-route-that-does-not-exist/', 'draft-ietf-opsawg-ipfix-tcpo-v6eh', '998718')
Exception,
lambda: parse_ballot_edit_return_point('/a-route-that-does-not-exist/', 'draft-ietf-opsawg-ipfix-tcpo-v6eh', '998718'), with self.assertRaises(ValueError):
) parse_ballot_edit_return_point('https://example.com/phishing', 'draft-ietf-opsawg-ipfix-tcpo-v6eh', '998718')
self.assertRaises(
Exception,
lambda: parse_ballot_edit_return_point('https://example.com/phishing', 'draft-ietf-opsawg-ipfix-tcpo-v6eh', '998718'),
)
def test_valid_default_return_to_url(self): def test_valid_default_return_to_url(self):
self.assertEqual(parse_ballot_edit_return_point( self.assertEqual(parse_ballot_edit_return_point(

View file

@ -54,8 +54,8 @@ This test section has some text.
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
q = PyQuery(r.content) q = PyQuery(r.content)
for state in states: for state in states:
self.assertEqual(len(q(f'#bofreqs-{state.slug}')), 1) self.assertEqual(len(q(f'#bofreqs-{state.slug}')), 1 if state.slug!="spam" else 0)
self.assertEqual(len(q(f'#bofreqs-{state.slug} tbody tr')), 3) self.assertEqual(len(q(f'#bofreqs-{state.slug} tbody tr')), 3 if state.slug!="spam" else 0)
self.assertFalse(q('#start_button')) self.assertFalse(q('#start_button'))
PersonFactory(user__username='nobody') PersonFactory(user__username='nobody')
self.client.login(username='nobody', password='nobody+password') self.client.login(username='nobody', password='nobody+password')
@ -63,6 +63,13 @@ This test section has some text.
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
q = PyQuery(r.content) q = PyQuery(r.content)
self.assertTrue(q('#start_button')) self.assertTrue(q('#start_button'))
self.client.logout()
self.client.login(username='secretary', password='secretary+password')
r = self.client.get(url)
q = PyQuery(r.content)
for state in states:
self.assertEqual(len(q(f'#bofreqs-{state.slug}')), 1)
self.assertEqual(len(q(f'#bofreqs-{state.slug} tbody tr')), 3)
def test_bofreq_main_page(self): def test_bofreq_main_page(self):

View file

@ -323,6 +323,8 @@ def build_position_email(balloter, doc, pos):
if doc.stream_id == "irtf": if doc.stream_id == "irtf":
addrs = gather_address_lists('irsg_ballot_saved',doc=doc) addrs = gather_address_lists('irsg_ballot_saved',doc=doc)
elif doc.stream_id == "editorial":
addrs = gather_address_lists('rsab_ballot_saved',doc=doc)
else: else:
addrs = gather_address_lists('iesg_ballot_saved',doc=doc) addrs = gather_address_lists('iesg_ballot_saved',doc=doc)
@ -1314,10 +1316,23 @@ def rsab_ballot_status(request):
def parse_ballot_edit_return_point(path, doc_name, ballot_id): def parse_ballot_edit_return_point(path, doc_name, ballot_id):
get_default_path = lambda: urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc_name, ballot_id=ballot_id)) get_default_path = lambda: urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc_name, ballot_id=ballot_id))
allowed_path_handlers = { allowed_path_handlers = {
"ietf.community.views.view_list",
"ietf.doc.views_doc.document_ballot", "ietf.doc.views_doc.document_ballot",
"ietf.doc.views_doc.document_irsg_ballot", "ietf.doc.views_doc.document_irsg_ballot",
"ietf.doc.views_doc.document_rsab_ballot", "ietf.doc.views_doc.document_rsab_ballot",
"ietf.doc.views_ballot.irsg_ballot_status",
"ietf.doc.views_ballot.rsab_ballot_status",
"ietf.doc.views_search.search",
"ietf.doc.views_search.docs_for_ad",
"ietf.doc.views_search.drafts_in_last_call",
"ietf.doc.views_search.recent_drafts",
"ietf.group.views.chartering_groups",
"ietf.group.views.group_documents",
"ietf.group.views.stream_documents",
"ietf.iesg.views.agenda", "ietf.iesg.views.agenda",
"ietf.iesg.views.agenda_documents", "ietf.iesg.views.agenda_documents",
"ietf.iesg.views.discusses",
"ietf.iesg.views.past_documents",
} }
return validate_return_to_path(path, get_default_path, allowed_path_handlers) return validate_return_to_path(path, get_default_path, allowed_path_handlers)

View file

@ -43,7 +43,7 @@ from pathlib import Path
from django.core.cache import caches from django.core.cache import caches
from django.db.models import Max from django.db.models import Max
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404, HttpResponseBadRequest
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse as urlreverse from django.urls import reverse as urlreverse
@ -73,6 +73,7 @@ from ietf.ietfauth.utils import ( has_role, is_authorized_in_doc_stream, user_is
role_required, is_individual_draft_author, can_request_rfc_publication) role_required, is_individual_draft_author, can_request_rfc_publication)
from ietf.name.models import StreamName, BallotPositionName from ietf.name.models import StreamName, BallotPositionName
from ietf.utils.history import find_history_active_at from ietf.utils.history import find_history_active_at
from ietf.doc.views_ballot import parse_ballot_edit_return_point
from ietf.doc.forms import InvestigateForm, TelechatForm, NotifyForm, ActionHoldersForm, DocAuthorForm, DocAuthorChangeBasisForm from ietf.doc.forms import InvestigateForm, TelechatForm, NotifyForm, ActionHoldersForm, DocAuthorForm, DocAuthorChangeBasisForm
from ietf.doc.mails import email_comment, email_remind_action_holders from ietf.doc.mails import email_comment, email_remind_action_holders
from ietf.mailtrigger.utils import gather_relevant_expansions from ietf.mailtrigger.utils import gather_relevant_expansions
@ -1586,11 +1587,18 @@ def ballot_popup(request, name, ballot_id):
doc = get_object_or_404(Document, name=name) doc = get_object_or_404(Document, name=name)
c = document_ballot_content(request, doc, ballot_id=ballot_id, editable=False) c = document_ballot_content(request, doc, ballot_id=ballot_id, editable=False)
ballot = get_object_or_404(BallotDocEvent,id=ballot_id) ballot = get_object_or_404(BallotDocEvent,id=ballot_id)
try:
return_to_url = parse_ballot_edit_return_point(request.GET.get('ballot_edit_return_point'), name, ballot_id)
except ValueError:
return HttpResponseBadRequest('ballot_edit_return_point is invalid')
return render(request, "doc/ballot_popup.html", return render(request, "doc/ballot_popup.html",
dict(doc=doc, dict(doc=doc,
ballot_content=c, ballot_content=c,
ballot_id=ballot_id, ballot_id=ballot_id,
ballot_type_slug=ballot.ballot_type.slug, ballot_type_slug=ballot.ballot_type.slug,
ballot_edit_return_point=return_to_url,
editable=True, editable=True,
)) ))

View file

@ -2617,6 +2617,19 @@
"model": "doc.state", "model": "doc.state",
"pk": 180 "pk": 180
}, },
{
"fields": {
"desc": "The BOF request is spam",
"name": "Spam",
"next_states": [],
"order": 5,
"slug": "spam",
"type": "bofreq",
"used": true
},
"model": "doc.state",
"pk": 182
},
{ {
"fields": { "fields": {
"label": "State" "label": "State"

View file

@ -320,6 +320,11 @@ tbody.meta tr {
background-color: $danger; background-color: $danger;
} }
.badge-generic {
color: white;
background-color: $danger;
}
#toc-nav { #toc-nav {
width: inherit; width: inherit;
overscroll-behavior-y: none; // Prevent overscrolling from scrolling the main content overscroll-behavior-y: none; // Prevent overscrolling from scrolling the main content

View file

@ -0,0 +1,16 @@
{% load origin %}
{% load static %}
{% load ietf_filters %}
{% load person_filters %}
{% origin %}
{# Non-RFC #}
{% if doc.became_rfc %}
<div{% if document_html %} class="alert alert-warning small"{% endif %}>This is an older version of an Internet-Draft that was ultimately published as <a href="{% if document_html %}{% url 'ietf.doc.views_doc.document_html' name=doc.became_rfc.name %}{% else %}{% url 'ietf.doc.views_doc.document_main' name=doc.became_rfc.name %}{% endif %}">{{doc.became_rfc.name|prettystdname}}</a>.</div>
{% elif snapshot and doc.rev != latest_rev %}
<div{% if document_html %} class="alert alert-warning small p-2 mt-2"{% endif %}>This is an older version of an Internet-Draft whose latest revision state is "{{ doc.doc.get_state }}".</div>
{% else %}
<span class="{% if doc.get_state_slug == 'active' %}text-success{% elif doc.get_state_slug == 'expired' or doc.get_state_slug == 'repl' %}text-danger{% endif %}">{% if snapshot and doc.rev == latest_rev %}{{ doc.doc.get_state }}{% else %}{{ doc.get_state }}{% endif %} Internet-Draft</span>
{% if submission %}({{ submission|safe }}){% endif %}
{% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %}
{% endif %}

View file

@ -0,0 +1,13 @@
{% load origin %}
{% load static %}
{% load ietf_filters %}
{% load person_filters %}
{% origin %}
<span class="text-success">RFC
{% if not document_html %}
- {{ doc.std_level }}
{% else %}
<span class="badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}generic{% endif %}">{{ doc.std_level }}</span>
{% endif %}
</span>

View file

@ -27,7 +27,7 @@
{% if editable and user|has_role:"Area Director,Secretariat,IRSG Member,RSAB Member" %} {% if editable and user|has_role:"Area Director,Secretariat,IRSG Member,RSAB Member" %}
{% if user|can_ballot:doc %} {% if user|can_ballot:doc %}
<a class="btn btn-primary" <a class="btn btn-primary"
href="{% url "ietf.doc.views_ballot.edit_position" name=doc.name ballot_id=ballot_id %}?ballot_edit_return_point={{ request.path|urlencode }}"> href="{% url "ietf.doc.views_ballot.edit_position" name=doc.name ballot_id=ballot_id %}?ballot_edit_return_point={{ ballot_edit_return_point|urlencode }}">
Edit position Edit position
</a> </a>
{% endif %} {% endif %}

View file

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{# Copyright The IETF Trust 2021 All Rights Reserved #} {# Copyright The IETF Trust 2021 All Rights Reserved #}
{% load origin %} {% load origin %}
{% load person_filters %} {% load person_filters ietf_filters %}
{% load static %} {% load static %}
{% block pagehead %} {% block pagehead %}
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}"> <link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
@ -26,40 +26,42 @@
{% else %} {% else %}
{% regroup reqs by get_state_slug as grouped_reqs %} {% regroup reqs by get_state_slug as grouped_reqs %}
{% for req_group in grouped_reqs %} {% for req_group in grouped_reqs %}
<h2 class="mt-5">{{ req_group.grouper|capfirst }} BOF Requests</h2> {% if req_group.grouper != "spam" or request.user|has_role:"Secretariat" %}
<table id="bofreqs-{{ req_group.grouper }}" <h2 class="mt-5">{{ req_group.grouper|capfirst }} BOF Requests</h2>
class="table table-sm table-striped tablesorter"> <table id="bofreqs-{{ req_group.grouper }}"
<thead> class="table table-sm table-striped tablesorter">
<tr> <thead>
<th scope="col" data-sort="name">Name</th>
<th scope="col" class="d-none d-sm-table-cell" data-sort="date">Date</th>
<th scope="col" data-sort="title">Title</th>
<th scope="col" data-sort="responsible">Responsible</th>
<th scope="col" data-sort="editors">Editors</th>
</tr>
</thead>
<tbody>
{% for req in req_group.list %}
<tr> <tr>
<td> <th scope="col" data-sort="name">Name</th>
<a href="{% url 'ietf.doc.views_doc.document_main' name=req.name %}">{{ req.name }}-{{ req.rev }}</a> <th scope="col" class="d-none d-sm-table-cell" data-sort="date">Date</th>
</td> <th scope="col" data-sort="title">Title</th>
<td class="d-none d-sm-table-cell">{{ req.latest_revision_event.time|date:"Y-m-d" }}</td> <th scope="col" data-sort="responsible">Responsible</th>
<td>{{ req.title }}</td> <th scope="col" data-sort="editors">Editors</th>
<td>
{% for person in req.responsible %}
{% person_link person %}{% if not forloop.last %},{% endif %}
{% endfor %}
</td>
<td>
{% for person in req.editors %}
{% person_link person %}{% if not forloop.last %},{% endif %}
{% endfor %}
</td>
</tr> </tr>
{% endfor %} </thead>
</tbody> <tbody>
</table> {% for req in req_group.list %}
<tr>
<td>
<a href="{% url 'ietf.doc.views_doc.document_main' name=req.name %}">{{ req.name }}-{{ req.rev }}</a>
</td>
<td class="d-none d-sm-table-cell">{{ req.latest_revision_event.time|date:"Y-m-d" }}</td>
<td>{{ req.title }}</td>
<td>
{% for person in req.responsible %}
{% person_link person %}{% if not forloop.last %},{% endif %}
{% endfor %}
</td>
<td>
{% for person in req.editors %}
{% person_link person %}{% if not forloop.last %},{% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -4,6 +4,7 @@
{% load origin %} {% load origin %}
{% load static %} {% load static %}
{% load ietf_filters textfilters %} {% load ietf_filters textfilters %}
{% load document_type_badge %}
{% load django_vite %} {% load django_vite %}
{% origin %} {% origin %}
<html data-bs-theme="auto" lang="en"> <html data-bs-theme="auto" lang="en">
@ -107,6 +108,7 @@
{{ doc.name }}-{{ doc.rev }} {{ doc.name }}-{{ doc.rev }}
{% endif %} {% endif %}
<br class="d-sm-none"> <br class="d-sm-none">
<span class="ms-sm-3 badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}"> <span class="ms-sm-3 badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}">
{% if not snapshot %} {% if not snapshot %}
{{ doc.std_level }} {{ doc.std_level }}
@ -185,13 +187,7 @@
{{ doc.name }}-{{ doc.rev }} {{ doc.name }}-{{ doc.rev }}
{% endif %} {% endif %}
<br> <br>
<span class="badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}"> {% document_type_badge doc snapshot submission resurrected_by %}
{% if not snapshot %}
{{ doc.std_level }}
{% else %}
Internet-Draft
{% endif %}
</span>
</p> </p>
</div> </div>
{% if request.COOKIES.htmlconf and request.COOKIES.htmlconf != 'html' and html %} {% if request.COOKIES.htmlconf and request.COOKIES.htmlconf != 'html' and html %}

View file

@ -3,6 +3,7 @@
{% load static %} {% load static %}
{% load ietf_filters %} {% load ietf_filters %}
{% load person_filters %} {% load person_filters %}
{% load document_type_badge %}
{% origin %} {% origin %}
<tbody class="meta align-top {% if not document_html %} border-top{% endif %}"> <tbody class="meta align-top {% if not document_html %} border-top{% endif %}">
@ -11,14 +12,8 @@
<th scope="row">{% if document_html %}Document type{% else %}Type{% endif %}</th> <th scope="row">{% if document_html %}Document type{% else %}Type{% endif %}</th>
<td class="edit"></td> <td class="edit"></td>
<td> <td>
{% document_type_badge doc snapshot submission resurrected_by %}
{% if doc.type_id == "rfc" %} {% if doc.type_id == "rfc" %}
<span class="text-success">RFC
{% if not document_html %}
- {{ doc.std_level }}
{% else %}
<span class="badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}">{{ doc.std_level }}</span>
{% endif %}
</span>
{% if doc.pub_date %} {% if doc.pub_date %}
{% if document_html %}<br>{% else %}({% endif %}{{ doc.pub_date|date:"F Y" }}{% if not document_html %}){% endif %} {% if document_html %}<br>{% else %}({% endif %}{{ doc.pub_date|date:"F Y" }}{% if not document_html %}){% endif %}
{% else %} {% else %}
@ -59,16 +54,6 @@
{% if submission %}({{ submission|safe }}){% endif %} {% if submission %}({{ submission|safe }}){% endif %}
</div> </div>
{% endif %} {% endif %}
{% else %}
{% if doc.became_rfc %}
<div{% if document_html %} class="alert alert-warning small"{% endif %}>This is an older version of an Internet-Draft that was ultimately published as <a href="{% if document_html %}{% url 'ietf.doc.views_doc.document_html' name=doc.became_rfc.name %}{% else %}{% url 'ietf.doc.views_doc.document_main' name=doc.became_rfc.name %}{% endif %}">{{doc.became_rfc.name|prettystdname}}</a>.</div>
{% elif snapshot and doc.rev != latest_rev %}
<div{% if document_html %} class="alert alert-warning small p-2 mt-2"{% endif %}>This is an older version of an Internet-Draft whose latest revision state is "{{ doc.doc.get_state }}".</div>
{% else %}
<span class="{% if doc.get_state_slug == 'active' %}text-success{% elif doc.get_state_slug == 'expired' or doc.get_state_slug == 'repl' %}text-danger{% endif %}">{% if snapshot and doc.rev == latest_rev %}{{ doc.doc.get_state }}{% else %}{{ doc.get_state }}{% endif %} Internet-Draft</span>
{% if submission %}({{ submission|safe }}){% endif %}
{% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %}
{% endif %}
{% endif %} {% endif %}
{% if doc.get_state_slug != "active" and doc.get_state_slug != "rfc" and doc.type_id != "rfc" %} {% if doc.get_state_slug != "active" and doc.get_state_slug != "rfc" and doc.type_id != "rfc" %}
<div class="badge rounded-pill text-bg-warning{% if not document_html %} float-end{% endif %}"> <div class="badge rounded-pill text-bg-warning{% if not document_html %} float-end{% endif %}">

View file

@ -1431,7 +1431,7 @@ test.describe('past - small screens', () => {
// can open the jump to day dropdown // can open the jump to day dropdown
await barBtnLocator.first().click() await barBtnLocator.first().click()
const jumpDayDdnLocator = page.locator('.n-dropdown-menu > .n-dropdown-option') const jumpDayDdnLocator = page.locator('.n-dropdown-menu [data-testid=mobile-link]')
await expect(jumpDayDdnLocator).toHaveCount(7) await expect(jumpDayDdnLocator).toHaveCount(7)
for (let idx = 0; idx < 7; idx++) { for (let idx = 0; idx < 7; idx++) {
const localDateTime = DateTime.fromISO(meetingData.meeting.startDate, { zone: meetingData.meeting.timezone }) const localDateTime = DateTime.fromISO(meetingData.meeting.startDate, { zone: meetingData.meeting.timezone })