Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] gamification, *: imp selection of cron-updated goals (Backport) #615

Open
wants to merge 1 commit into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions addons/gamification/models/gamification_challenge.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

from dateutil.relativedelta import relativedelta, MO

from odoo import api, models, fields, _, exceptions
from odoo import _, api, exceptions, fields, models
from odoo.http import SESSION_LIFETIME
from odoo.tools import ustr

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -269,20 +270,25 @@ def _update_all(self):
Goals = self.env['gamification.goal']

# include yesterday goals to update the goals that just ended
# exclude goals for portal users that did not connect since the last update
# exclude goals for users that have not interacted with the
# webclient since the last update or whose session is no longer
# valid.
yesterday = fields.Date.to_string(date.today() - timedelta(days=1))
self.env.cr.execute("""SELECT gg.id
FROM gamification_goal as gg
JOIN res_users_log as log ON gg.user_id = log.create_uid
JOIN res_users ru on log.create_uid = ru.id
WHERE (gg.write_date < log.create_date OR ru.share IS NOT TRUE)
AND ru.active IS TRUE
JOIN bus_presence as bp ON bp.user_id = gg.user_id
WHERE gg.write_date <= bp.last_presence
AND bp.last_presence >= now() AT TIME ZONE 'UTC' - interval '%(session_lifetime)s seconds'
AND gg.closed IS NOT TRUE
AND gg.challenge_id IN %s
AND gg.challenge_id IN %(challenge_ids)s
AND (gg.state = 'inprogress'
OR (gg.state = 'reached' AND gg.end_date >= %s))
OR (gg.state = 'reached' AND gg.end_date >= %(yesterday)s))
GROUP BY gg.id
""", [tuple(self.ids), yesterday])
""", {
'session_lifetime': SESSION_LIFETIME,
'challenge_ids': tuple(self.ids),
'yesterday': yesterday
})

Goals.browse(goal_id for [goal_id] in self.env.cr.fetchall()).update_goal()

Expand Down
61 changes: 38 additions & 23 deletions addons/gamification/tests/test_challenge.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from freezegun import freeze_time

from odoo.addons.gamification.tests.common import TransactionCaseGamification

from odoo.exceptions import UserError
from odoo.tools import mute_logger

Expand Down Expand Up @@ -61,14 +63,14 @@ def test_10_reach_challenge(self):
badge_ids = self.env['gamification.badge.user'].search([('badge_id', '=', badge_id), ('user_id', '=', demo.id)])
self.assertEqual(len(badge_ids), 1, "Demo user has not received the badge")

@mute_logger('odoo.models.unlink')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail', 'odoo.addons.auth_signup')
def test_20_update_all_goals_filter(self):
# Enroll two internal and two portal users in the challenge
(
portal_login_before_update,
portal_login_after_update,
internal_login_before_update,
internal_login_after_update,
portal_last_active_old,
portal_last_active_recent,
internal_last_active_old,
internal_last_active_recent,
) = all_test_users = self.env['res.users'].create([
{
'name': f'{kind} {age} login',
Expand All @@ -90,21 +92,33 @@ def test_20_update_all_goals_filter(self):
'user_ids': [(6, 0, all_test_users.ids)]
})

# Setup user access logs
self.env['res.users.log'].search([('create_uid', 'in', challenge.user_ids.ids)]).unlink()
now = datetime.datetime.now()
# Setup user presence
self.env['bus.presence'].search([('user_id', 'in', challenge.user_ids.ids)]).unlink()
now = self.env.cr.now()

# Create "old" log in records
self.env['res.users.log'].create([
{"create_uid": internal_login_before_update.id, 'create_date': now - datetime.timedelta(minutes=3)},
{"create_uid": portal_login_before_update.id, 'create_date': now - datetime.timedelta(minutes=3)},
])
twenty_minutes_ago = now - datetime.timedelta(minutes=20)
with freeze_time(twenty_minutes_ago):
# Not using BusPresence.update_presence to avoid lower level cursor handling there.
self.env['bus.presence'].create([
{
'user_id': user.id,
'last_presence': twenty_minutes_ago,
'last_poll': twenty_minutes_ago,
}
for user in (
portal_last_active_old,
portal_last_active_recent,
internal_last_active_old,
internal_last_active_recent,
)
])

# Reset goal objective values
all_test_users.partner_id.tz = False

# Regenerate all goals
self.env["gamification.goal"].search([]).unlink()
self.env['gamification.goal'].search([]).unlink()
self.assertFalse(self.env['gamification.goal'].search([]))

challenge.action_check()
Expand All @@ -114,27 +128,28 @@ def test_20_update_all_goals_filter(self):
self.assertEqual(len(goal_ids), 4)
self.assertEqual(set(goal_ids.mapped('state')), {'inprogress'})

# Create more recent log in records
self.env['res.users.log'].create([
{"create_uid": internal_login_after_update.id, 'create_date': now + datetime.timedelta(minutes=3)},
{"create_uid": portal_login_after_update.id, 'create_date': now + datetime.timedelta(minutes=3)},
])
# Update presence for 2 users
users_recent = internal_last_active_recent | portal_last_active_recent
users_recent_presence = self.env['bus.presence'].search([('user_id', 'in', users_recent.ids)])
users_recent_presence.last_presence = now
users_recent_presence.last_poll = now
users_recent_presence.flush_recordset()

# Update goal objective checked by goal definition
all_test_users.partner_id.write({'tz': 'Europe/Paris'})
all_test_users.flush_recordset()

# Update goals as done by _cron_update
challenge._update_all()
unchanged_goal_id = self.env['gamification.goal'].search([
unchanged_goal_ids = self.env['gamification.goal'].search([
('challenge_id', '=', challenge.id),
('state', '=', 'inprogress'), # others were updated to "reached"
('user_id', 'in', challenge.user_ids.ids),
])
# Check that even though login record for internal user is older than goal update, their goal was reached.
# Check that goals of users not recently active were not updated
self.assertEqual(
portal_login_before_update,
unchanged_goal_id.user_id,
"Only portal user last logged in before last challenge update should not have been updated.",
portal_last_active_old | internal_last_active_old,
unchanged_goal_ids.user_id,
)


Expand Down
4 changes: 2 additions & 2 deletions odoo/addons/base/models/res_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ class ResUsersLog(models.Model):
_name = 'res.users.log'
_order = 'id desc'
_description = 'Users Log'
# Currenly only uses the magical fields: create_uid, create_date,
# for recording logins. To be extended for other uses (chat presence, etc.)
# Uses the magical fields `create_uid` and `create_date` for recording logins.
# See `bus.presence` for more recent activity tracking purposes.

@api.autovacuum
def _gc_user_logs(self):
Expand Down