feat: add slides / session materials links to session details modal (#4535)
* feat: add propose/upload slides button to session details modal * refactor: remove unneeded chaining operator * test: fix quotes around template string * feat: link to meeting materials page from AgendaDetailsModal * refactor: compute session details URL in JS instead of view * chore: restyle materials page link * test: fix test case to match changes to session modal
This commit is contained in:
parent
6f2114fb0c
commit
0ad293d6e9
|
@ -55,6 +55,17 @@ n-modal(v-model:show='modalShown')
|
||||||
)
|
)
|
||||||
i.bi.bi-journal-text.me-2
|
i.bi.bi-journal-text.me-2
|
||||||
span Notepad
|
span Notepad
|
||||||
|
n-button.float-end(
|
||||||
|
ghost
|
||||||
|
color='gray'
|
||||||
|
strong
|
||||||
|
tag='a'
|
||||||
|
:href='eventDetails.detailsUrl'
|
||||||
|
target='_blank'
|
||||||
|
aria-label='Materials page'
|
||||||
|
)
|
||||||
|
span.me-2 {{props.event.groupAcronym}} materials page
|
||||||
|
i.bi.bi-box-arrow-up-right
|
||||||
.detail-content
|
.detail-content
|
||||||
.detail-title
|
.detail-title
|
||||||
h6
|
h6
|
||||||
|
@ -95,19 +106,29 @@ n-modal(v-model:show='modalShown')
|
||||||
:src='eventDetails.materialsUrl'
|
:src='eventDetails.materialsUrl'
|
||||||
)
|
)
|
||||||
template(v-else-if='state.tab === `slides`')
|
template(v-else-if='state.tab === `slides`')
|
||||||
.text-center(v-if='state.isLoading')
|
n-card(
|
||||||
n-spin(description='Loading slides...')
|
:bordered='false'
|
||||||
.text-center.p-3(v-else-if='!state.materials || !state.materials.slides || state.materials.slides.length < 1')
|
size='small'
|
||||||
span No slides submitted for this session.
|
)
|
||||||
.list-group(v-else)
|
.text-center(v-if='state.isLoading')
|
||||||
a.list-group-item(
|
n-spin(description='Loading slides...')
|
||||||
v-for='slide of state.materials.slides'
|
.text-center.p-3(v-else-if='!state.materials || !state.materials.slides || !state.materials.slides.decks || state.materials.slides.decks.length < 1')
|
||||||
:key='slide.id'
|
span No slides submitted for this session.
|
||||||
:href='slide.url'
|
.list-group(v-else)
|
||||||
target='_blank'
|
a.list-group-item(
|
||||||
)
|
v-for='deck of state.materials.slides.decks'
|
||||||
i.bi.me-2(:class='`bi-filetype-` + slide.ext')
|
:key='deck.id'
|
||||||
span {{slide.title}}
|
:href='deck.url'
|
||||||
|
target='_blank'
|
||||||
|
)
|
||||||
|
i.bi.me-2(:class='`bi-filetype-` + deck.ext')
|
||||||
|
span {{deck.title}}
|
||||||
|
template(#action, v-if='state.materials.slides.actions')
|
||||||
|
n-button(
|
||||||
|
v-for='action of state.materials.slides.actions'
|
||||||
|
tag='a'
|
||||||
|
:href='action.url'
|
||||||
|
) {{action.label}}
|
||||||
template(v-else)
|
template(v-else)
|
||||||
.text-center(v-if='state.isLoading')
|
.text-center(v-if='state.isLoading')
|
||||||
n-spin(description='Loading minutes...')
|
n-spin(description='Loading minutes...')
|
||||||
|
@ -184,9 +205,10 @@ const eventDetails = computed(() => {
|
||||||
title: props.event.type === 'regular' ? `${props.event.groupName} (${props.event.acronym})` : props.event.name,
|
title: props.event.type === 'regular' ? `${props.event.groupName} (${props.event.acronym})` : props.event.name,
|
||||||
showAgenda: props.event.flags.showAgenda,
|
showAgenda: props.event.flags.showAgenda,
|
||||||
materialsUrl: materialsUrl,
|
materialsUrl: materialsUrl,
|
||||||
|
detailsUrl: `/meeting/${agendaStore.meeting.number}/session/${props.event.acronym}/`,
|
||||||
tarUrl: `/meeting/${agendaStore.meeting.number}/agenda/${props.event.acronym}-drafts.tgz`,
|
tarUrl: `/meeting/${agendaStore.meeting.number}/agenda/${props.event.acronym}-drafts.tgz`,
|
||||||
pdfUrl: `/meeting/${agendaStore.meeting.number}/agenda/${props.event.acronym}-drafts.pdf`,
|
pdfUrl: `/meeting/${agendaStore.meeting.number}/agenda/${props.event.acronym}-drafts.pdf`,
|
||||||
notepadUrl: `https://notes.ietf.org/notes-ietf-${agendaStore.meeting.number}-${props.event.type === 'plenary' ? 'plenary' : props.event.acronym}`
|
notepadUrl: `https://notes.ietf.org/notes-ietf-${agendaStore.meeting.number}-${props.event.type === 'plenary' ? 'plenary' : props.event.acronym}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -219,7 +241,10 @@ async function fetchSessionMaterials () {
|
||||||
state.isLoading = true
|
state.isLoading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/meeting/session/${props.event.sessionId}/materials`, { credentials: 'omit' })
|
const resp = await fetch(
|
||||||
|
`/api/meeting/session/${props.event.sessionId}/materials`,
|
||||||
|
{ credentials: 'include' }
|
||||||
|
)
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
throw new Error(resp.statusText)
|
throw new Error(resp.statusText)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1642,10 +1642,32 @@ def api_get_session_materials (request, session_id=None):
|
||||||
session = get_object_or_404(Session,pk=session_id)
|
session = get_object_or_404(Session,pk=session_id)
|
||||||
|
|
||||||
minutes = session.minutes()
|
minutes = session.minutes()
|
||||||
|
slides_actions = []
|
||||||
|
if can_manage_session_materials(request.user, session.group, session):
|
||||||
|
slides_actions.append({
|
||||||
|
'label': 'Upload slides',
|
||||||
|
'url': reverse(
|
||||||
|
'ietf.meeting.views.upload_session_slides',
|
||||||
|
kwargs={'num': session.meeting.number, 'session_id': session.pk},
|
||||||
|
),
|
||||||
|
})
|
||||||
|
elif not session.is_material_submission_cutoff():
|
||||||
|
slides_actions.append({
|
||||||
|
'label': 'Propose slides',
|
||||||
|
'url': reverse(
|
||||||
|
'ietf.meeting.views.propose_session_slides',
|
||||||
|
kwargs={'num': session.meeting.number, 'session_id': session.pk},
|
||||||
|
),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
pass # no action available if it's past cutoff
|
||||||
|
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
"url": session.agenda().get_href(),
|
"url": session.agenda().get_href(),
|
||||||
"slides": list(map(agenda_extract_slide, session.slides())),
|
"slides": {
|
||||||
|
"decks": list(map(agenda_extract_slide, session.slides())),
|
||||||
|
"actions": slides_actions,
|
||||||
|
},
|
||||||
"minutes": {
|
"minutes": {
|
||||||
"id": minutes.id,
|
"id": minutes.id,
|
||||||
"title": minutes.title,
|
"title": minutes.title,
|
||||||
|
@ -1700,7 +1722,10 @@ def agenda_extract_schedule (item):
|
||||||
"audioStream": item.timeslot.location.audio_stream_url() if item.timeslot.location else "",
|
"audioStream": item.timeslot.location.audio_stream_url() if item.timeslot.location else "",
|
||||||
"webex": item.timeslot.location.webex_url() if item.timeslot.location else "",
|
"webex": item.timeslot.location.webex_url() if item.timeslot.location else "",
|
||||||
"onsiteTool": item.timeslot.location.onsite_tool_url() if item.timeslot.location else "",
|
"onsiteTool": item.timeslot.location.onsite_tool_url() if item.timeslot.location else "",
|
||||||
"calendar": reverse('ietf.meeting.views.agenda_ical', kwargs={'num': item.schedule.meeting.number, 'session_id': item.session.id, })
|
"calendar": reverse(
|
||||||
|
'ietf.meeting.views.agenda_ical',
|
||||||
|
kwargs={'num': item.schedule.meeting.number, 'session_id': item.session.id},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
# "slotType": {
|
# "slotType": {
|
||||||
# "slug": item.slot_type.slug
|
# "slug": item.slot_type.slug
|
||||||
|
|
|
@ -368,12 +368,18 @@ test.describe('past - desktop', () => {
|
||||||
const materialsUrl = (new URL(event.agenda.url)).pathname
|
const materialsUrl = (new URL(event.agenda.url)).pathname
|
||||||
const materialsInfo = {
|
const materialsInfo = {
|
||||||
url: event.agenda.url,
|
url: event.agenda.url,
|
||||||
slides: _.times(5, idx => ({
|
slides: {
|
||||||
id: 100000 + idx,
|
decks: _.times(5, idx => ({
|
||||||
title: faker.commerce.productName(),
|
id: 100000 + idx,
|
||||||
url: `/meeting/${meetingData.meeting.number}/materials/slides-${meetingData.meeting.number}-${event.acronym}-${faker.internet.domainWord()}`,
|
title: faker.commerce.productName(),
|
||||||
ext: ['pdf', 'html', 'md', 'txt', 'pptx'][idx]
|
url: `/meeting/${meetingData.meeting.number}/materials/slides-${meetingData.meeting.number}-${event.acronym}-${faker.internet.domainWord()}`,
|
||||||
})),
|
ext: ['pdf', 'html', 'md', 'txt', 'pptx'][idx]
|
||||||
|
})),
|
||||||
|
actions: [{
|
||||||
|
label: 'Propose slides',
|
||||||
|
url: `/meeting/${meetingData.meeting.number}/session/${event.sessionId}/propose_slides`
|
||||||
|
}]
|
||||||
|
},
|
||||||
minutes: {
|
minutes: {
|
||||||
ext: 'md',
|
ext: 'md',
|
||||||
id: 123456,
|
id: 123456,
|
||||||
|
@ -427,13 +433,16 @@ test.describe('past - desktop', () => {
|
||||||
await navLocator.nth(1).click()
|
await navLocator.nth(1).click()
|
||||||
await expect(navLocator.nth(1)).toHaveClass(/active/)
|
await expect(navLocator.nth(1)).toHaveClass(/active/)
|
||||||
await expect(navLocator.first()).not.toHaveClass(/active/)
|
await expect(navLocator.first()).not.toHaveClass(/active/)
|
||||||
const slidesLocator = page.locator('.agenda-eventdetails .detail-text > .list-group > .list-group-item')
|
const slideDecksLocator = page.locator('.agenda-eventdetails .detail-text .n-card__content > .list-group > .list-group-item')
|
||||||
await expect(slidesLocator).toHaveCount(materialsInfo.slides.length)
|
await expect(slideDecksLocator).toHaveCount(materialsInfo.slides.decks.length)
|
||||||
for (let idx = 0; idx < materialsInfo.slides.length; idx++) {
|
for (let idx = 0; idx < materialsInfo.slides.decks.length; idx++) {
|
||||||
await expect(slidesLocator.nth(idx)).toHaveAttribute('href', materialsInfo.slides[idx].url)
|
await expect(slideDecksLocator.nth(idx)).toHaveAttribute('href', materialsInfo.slides.decks[idx].url)
|
||||||
await expect(slidesLocator.nth(idx).locator('.bi')).toHaveClass(new RegExp(`bi-filetype-${materialsInfo.slides[idx].ext}`))
|
await expect(slideDecksLocator.nth(idx).locator('.bi')).toHaveClass(new RegExp(`bi-filetype-${materialsInfo.slides.decks[idx].ext}`))
|
||||||
await expect(slidesLocator.nth(idx).locator('span')).toContainText(materialsInfo.slides[idx].title)
|
await expect(slideDecksLocator.nth(idx).locator('span')).toContainText(materialsInfo.slides.decks[idx].title)
|
||||||
}
|
}
|
||||||
|
const slideActionButtonLocator = page.locator('.agenda-eventdetails .detail-text .n-card__action > a')
|
||||||
|
await expect(slideActionButtonLocator).toHaveCount(1)
|
||||||
|
await expect(slideActionButtonLocator.first().locator('span')).toContainText('Propose slides')
|
||||||
// Minutes Tab
|
// Minutes Tab
|
||||||
await navLocator.last().click()
|
await navLocator.last().click()
|
||||||
await expect(navLocator.last()).toHaveClass(/active/)
|
await expect(navLocator.last()).toHaveClass(/active/)
|
||||||
|
@ -442,7 +451,7 @@ test.describe('past - desktop', () => {
|
||||||
// Footer Buttons
|
// Footer Buttons
|
||||||
const hedgeDocLink = `https://notes.ietf.org/notes-ietf-${meetingData.meeting.number}-${event.type === 'plenary' ? 'plenary' : event.acronym}`
|
const hedgeDocLink = `https://notes.ietf.org/notes-ietf-${meetingData.meeting.number}-${event.type === 'plenary' ? 'plenary' : event.acronym}`
|
||||||
const footerBtnsLocator = page.locator('.agenda-eventdetails .detail-action > a')
|
const footerBtnsLocator = page.locator('.agenda-eventdetails .detail-action > a')
|
||||||
await expect(footerBtnsLocator).toHaveCount(3)
|
await expect(footerBtnsLocator).toHaveCount(4)
|
||||||
await expect(footerBtnsLocator.first()).toContainText('Download as tarball')
|
await expect(footerBtnsLocator.first()).toContainText('Download as tarball')
|
||||||
await expect(footerBtnsLocator.first()).toHaveAttribute('href', `/meeting/${meetingData.meeting.number}/agenda/${event.acronym}-drafts.tgz`)
|
await expect(footerBtnsLocator.first()).toHaveAttribute('href', `/meeting/${meetingData.meeting.number}/agenda/${event.acronym}-drafts.tgz`)
|
||||||
await expect(footerBtnsLocator.nth(1)).toContainText('Download as PDF')
|
await expect(footerBtnsLocator.nth(1)).toContainText('Download as PDF')
|
||||||
|
@ -483,7 +492,7 @@ test.describe('past - desktop', () => {
|
||||||
await expect(page.locator('.agenda-eventdetails')).toBeVisible()
|
await expect(page.locator('.agenda-eventdetails')).toBeVisible()
|
||||||
// Slides Tab
|
// Slides Tab
|
||||||
await page.locator('.agenda-eventdetails .detail-nav > a').nth(1).click()
|
await page.locator('.agenda-eventdetails .detail-nav > a').nth(1).click()
|
||||||
await expect(page.locator('.agenda-eventdetails .detail-text')).toContainText('No slides submitted for this session.')
|
await expect(page.locator('.agenda-eventdetails .detail-text .n-card__content')).toContainText('No slides submitted for this session.')
|
||||||
// Minutes Tab
|
// Minutes Tab
|
||||||
await page.locator('.agenda-eventdetails .detail-nav > a').nth(2).click()
|
await page.locator('.agenda-eventdetails .detail-nav > a').nth(2).click()
|
||||||
await expect(page.locator('.agenda-eventdetails .detail-text')).toContainText('No minutes submitted for this session.')
|
await expect(page.locator('.agenda-eventdetails .detail-text')).toContainText('No minutes submitted for this session.')
|
||||||
|
|
Loading…
Reference in a new issue