Skip to content
Closed
37 changes: 31 additions & 6 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/src/components/Round/RoundEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const deleteRound = () => {
.cancelRound(props.round.id)
.then(() => {
emit('update:isRoundEditing', false)
router.reload()
router.go(0)
})
.catch(alertService.error)
}
Expand Down
11 changes: 7 additions & 4 deletions frontend/src/components/Vote/VoteEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -410,14 +410,17 @@ watch(locale, (newLocale) => {
dayjs.locale(newLocale)
})

// Attach scroll listener once the container is rendered (v-else-if means it's null on mount)
watch(editVoteContainer, (el) => {
if (el) {
el.addEventListener('scroll', handleScroll)
}
})

onMounted(() => {
getRoundDetails(voteId)
getPastVotes(voteId)

if (editVoteContainer.value) {
editVoteContainer.value.addEventListener('scroll', handleScroll)
}

handleResize()
window.addEventListener('resize', handleResize)
})
Expand Down
53 changes: 50 additions & 3 deletions frontend/src/components/Vote/VoteRating.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<clip-loader v-if="isLoading" color="#36D7B7" size="20px" />
<div v-else class="vote-controls-button">
<span v-for="rate in [1, 2, 3, 4, 5]" :key="rate">
<cdx-button weight="quiet" @click="setRate(rate)">
<cdx-button weight="quiet" @click="setRate(rate)" :disabled="isLoading">
<star />
</cdx-button>
</span>
Expand All @@ -70,6 +70,16 @@
</span>
</div>

<template v-if="round.show_stats && votesStats">
<h3 class="vote-section-title">{{ $t('montage-vote-my-stats') }}</h3>
<div class="vote-stats">
<div v-for="(count, label) in votesStats.stats" :key="label" class="vote-stats-item">
<span class="vote-stats-label">{{ label }}</span>
<span class="vote-stats-count">{{ count }}</span>
</div>
</div>
</template>

<h3 class="vote-section-title">{{ $t('montage-vote-actions') }}</h3>
<div class="vote-actions">
<div>
Expand All @@ -85,7 +95,7 @@
</cdx-button>
</div>
<div>
<cdx-button weight="quiet" @click="setRate()">
<cdx-button weight="quiet" @click="setRate()" :disabled="isLoading">
<arrow-right class="icon-small" /> {{ $t('montage-vote-skip') }}
</cdx-button>
<cdx-button weight="quiet" @click="goPrevVoteEditing">
Expand Down Expand Up @@ -166,7 +176,7 @@
</template>

<script setup>
import { ref, watch, computed } from 'vue'
import { ref, watch, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import jurorService from '@/services/jurorService'
import { useRouter } from 'vue-router'
Expand Down Expand Up @@ -200,6 +210,7 @@ const voteContainer = ref(null)
const showSidebar = ref(true)
const imageCache = new Map()
const isLoading = ref(false)
const votesStats = ref(null)

const props = defineProps({
round: Object,
Expand Down Expand Up @@ -279,6 +290,10 @@ function setRate(rate) {
if (stats.value.total_open_tasks <= 10) {
skips.value = 0
}
// Refresh the vote stats after each successful vote
if (props.round.show_stats) {
jurorService.getRoundVotesStats(props.round.id).then(r => { votesStats.value = r.data })
}
if (counter.value === 4 || !stats.value.total_open_tasks) {
counter.value = 0
getTasks()
Expand Down Expand Up @@ -417,6 +432,12 @@ watch(voteContainer, () => {
voteContainer.value.focus()
}
})

onMounted(() => {
if (props.round.show_stats) {
jurorService.getRoundVotesStats(props.round.id).then(r => { votesStats.value = r.data })
}
})
</script>

<style scoped>
Expand Down Expand Up @@ -605,4 +626,30 @@ watch(voteContainer, () => {
margin-top: 24px;
width: 232px;
}
.vote-stats {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 8px;
}

.vote-stats-item {
display: flex;
align-items: center;
gap: 6px;
background: #f0f0f0;
border-radius: 4px;
padding: 4px 10px;
font-size: 13px;
}

.vote-stats-label {
font-weight: 500;
color: rgba(0,0,0,0.6);
}

.vote-stats-count {
font-weight: 700;
color: rgba(0,0,0,0.87);
}
</style>
29 changes: 25 additions & 4 deletions frontend/src/components/Vote/VoteYesNo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@
<div class="vote-controls">
<clip-loader v-if="isLoading" color="#36D7B7" size="20px" />
<div v-else class="vote-controls-button">
<cdx-button action="progressive" weight="quiet" @click="setRate(5)">
<cdx-button action="progressive" weight="quiet" @click="setRate(5)" :disabled="isLoading">
<thumb-up class="icon-small" /> {{ $t('montage-vote-accept') }}
</cdx-button>
<cdx-button action="destructive" weight="quiet" @click="setRate(1)">
<cdx-button action="destructive" weight="quiet" @click="setRate(1)" :disabled="isLoading">
<thumb-down class="icon-small" /> {{ $t('montage-vote-decline') }}
</cdx-button>
</div>
Expand All @@ -71,6 +71,16 @@
</span>
</div>

<template v-if="round.show_stats && votesStats">
<h3 class="vote-section-title">{{ $t('montage-vote-my-stats') }}</h3>
<div class="vote-stats">
<div v-for="(count, label) in votesStats.stats" :key="label" class="vote-stats-item">
<span class="vote-stats-label">{{ label }}</span>
<span class="vote-stats-count">{{ count }}</span>
</div>
</div>
</template>

<h3 class="vote-section-title">{{ $t('montage-vote-actions') }}</h3>
<div class="vote-actions">
<div>
Expand All @@ -86,7 +96,7 @@
</cdx-button>
</div>
<div>
<cdx-button weight="quiet" @click="setRate()">
<cdx-button weight="quiet" @click="setRate()" :disabled="isLoading">
<arrow-right class="icon-small" /> {{ $t('montage-vote-skip') }}
</cdx-button>
<cdx-button weight="quiet" @click="goPrevVoteEditing">
Expand Down Expand Up @@ -170,7 +180,7 @@
</template>

<script setup>
import { ref, watch, computed } from 'vue'
import { ref, watch, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import jurorService from '@/services/jurorService'
import { useRouter } from 'vue-router'
Expand Down Expand Up @@ -215,6 +225,7 @@ const roundLink = [props.round.id, props.round.canonical_url_name].join('-')
const isLoading = ref(false)
const images = ref(null)
const stats = ref(null)
const votesStats = ref(null)

const rating = ref({
current: null,
Expand Down Expand Up @@ -284,6 +295,10 @@ function setRate(rate) {
if (stats.value.total_open_tasks <= 10) {
skips.value = 0
}
// Refresh vote stats after each successful vote
if (props.round.show_stats) {
jurorService.getRoundVotesStats(props.round.id).then(r => { votesStats.value = r.data })
}
if (counter.value === 4 || !stats.value.total_open_tasks) {
counter.value = 0
getTasks()
Expand Down Expand Up @@ -424,6 +439,12 @@ watch(voteContainer, () => {
voteContainer.value.focus()
}
})

onMounted(() => {
if (props.round.show_stats) {
jurorService.getRoundVotesStats(props.round.id).then(r => { votesStats.value = r.data })
}
})
</script>

<style scoped>
Expand Down
2 changes: 2 additions & 0 deletions montage/admin_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@ def cancel_campaign(user_dao, campaign_id):

def _prepare_round_params(coord_dao, request_dict):
rnd_dict = {}
if not request_dict:
request_dict = {}
req_columns = ['jurors', 'name', 'vote_method', 'deadline_date']
extra_columns = ['description', 'config', 'directions', 'show_stats']
valid_vote_methods = ['ranking', 'rating', 'yesno']
Expand Down
11 changes: 9 additions & 2 deletions montage/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,21 @@ def make_entry(edict):
'upload_user_id': edict['img_user'],
'upload_user_text': edict['img_user_text']}
if edict.get('oi_archive_name'):
# The file has multiple versions
# The file has multiple versions — use the ORIGINAL (first) upload's
# author and date for competition eligibility, not the latest re-upload.
raw_entry['flags'] = {
'reupload': True,
'reupload_date': wpts2dt(edict['rec_img_timestamp']),
'reupload_user_id': edict['rec_img_user'],
'reupload_user_text': edict['rec_img_text'],
'archive_name': edict['oi_archive_name']}
raw_entry['upload_date'] = wpts2dt(edict['img_timestamp'])
# The original first version's timestamp and uploader is in rec_img_*
raw_entry['upload_date'] = wpts2dt(edict['rec_img_timestamp'])
raw_entry['upload_user_text'] = edict['rec_img_text']
raw_entry['upload_user_id'] = edict['rec_img_user']
else:
# Single-version file: img_timestamp IS the original upload date
raw_entry['upload_date'] = wpts2dt(edict['img_timestamp'])
raw_entry['resolution'] = width * height
if edict.get('flags'):
raw_entry['flags'] = edict['flags']
Expand Down
30 changes: 20 additions & 10 deletions montage/mw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,26 @@ def endpoint(self, next, response_dict, request, _route):
try:
ret = next()
except Exception as e:
if self.debug_errors and not isinstance(e, MontageError):
import pdb; pdb.post_mortem()
import pdb;pdb.set_trace()
if self.raise_errors:
raise
ret = None
exc_info = ExceptionInfo.from_current()
err = '%s: %s' % (exc_info.exc_type, exc_info.exc_msg)
response_dict['errors'].append(err)
response_dict['status'] = 'exception'
if isinstance(e, MontageError):
err = str(e)
# Some clastic errors like BadRequest have a description property
if hasattr(e, 'description') and e.description:
err = e.description
response_dict['errors'].append(err)
response_dict['status'] = 'failure'
response_dict['_status_code'] = getattr(e, 'code', getattr(e, 'status_code', 400))
ret = None
else:
if self.debug_errors:
import pdb; pdb.post_mortem()
if self.raise_errors:
raise
ret = None
exc_info = ExceptionInfo.from_current()
err = '%s: %s' % (exc_info.exc_type, exc_info.exc_msg)
response_dict['errors'].append(err)
response_dict['status'] = 'exception'
response_dict['_status_code'] = 500
else:
status_code = response_dict.pop('_status_code', None)
if response_dict.get('errors'):
Expand Down
2 changes: 1 addition & 1 deletion montage/rdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@

MAINTAINERS = [
'MahmoudHashemi', 'Slaporte', 'Yarl', 'LilyOfTheWest',
'Jayprakash12345', 'Ciell', 'Effeietsanders'
'Jayprakash12345', 'Ciell', 'Effeietsanders', 'AyushShukla1807'
]

"""
Expand Down