commit
4635e3dd42
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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')
|
td.agenda-table-cell-check(v-if='pickerModeActive')
|
||||||
|
@ -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(() => {
|
||||||
|
|
|
@ -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'
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
6
client/shared/create-pinia-singleton.js
Normal file
6
client/shared/create-pinia-singleton.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
export function createPiniaSingleton(){
|
||||||
|
window.pinia = window.pinia ?? createPinia()
|
||||||
|
return window.pinia
|
||||||
|
}
|
30
ietf/doc/migrations/0023_bofreqspamstate.py
Normal file
30
ietf/doc/migrations/0023_bofreqspamstate.py
Normal 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)]
|
|
@ -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,)]
|
||||||
|
|
29
ietf/doc/templatetags/document_type_badge.py
Normal file
29
ietf/doc/templatetags/document_type_badge.py
Normal 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 ""
|
|
@ -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(
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
16
ietf/templates/doc/badge/doc-badge-draft.html
Normal file
16
ietf/templates/doc/badge/doc-badge-draft.html
Normal 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 %}
|
13
ietf/templates/doc/badge/doc-badge-rfc.html
Normal file
13
ietf/templates/doc/badge/doc-badge-rfc.html
Normal 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>
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}">
|
||||||
|
|
|
@ -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 })
|
||||||
|
|
Loading…
Reference in a new issue