Skip to content

Commit 7f34d11

Browse files
authored
Merge pull request #422 from MipsaPatel/recommendations-page
Added problem recommendations feature
2 parents 03662a1 + 74679f5 commit 7f34d11

10 files changed

Lines changed: 208 additions & 12 deletions

File tree

controllers/problems.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def by():
9595

9696
return_val["table"] = utilities.get_problems_table(problems,
9797
session.user_id,
98+
"problems-authored",
9899
None)
99100

100101
return_val["problems_count"] = len(problems)
@@ -870,6 +871,7 @@ def search():
870871

871872
return dict(table=utilities.get_problems_table(all_problems,
872873
session.user_id,
874+
"problem-search",
873875
problem_with_user_editorials),
874876
generalized_tags=generalized_tags)
875877

@@ -931,4 +933,56 @@ def trending():
931933

932934
return dict(div=div)
933935

936+
# ----------------------------------------------------------------------------
937+
@auth.requires_login()
938+
def recommendations():
939+
"""
940+
Problem recommendations for the user.
941+
"""
942+
import recommendations.problems as recommendations
943+
944+
ptable = db.problem
945+
rtable = db.problem_recommendations
946+
user_id = session.user_id
947+
refresh = request.vars.get("refresh", "false") == "true"
948+
949+
output = {}
950+
recommendation_pids = []
951+
952+
rows = db(rtable.user_id == user_id).select()
953+
if len(rows) == 0:
954+
refresh = True
955+
956+
if refresh:
957+
recommendation_pids = recommendations.generate_recommendations(user_id)
958+
else:
959+
recommendation_pids, _ = recommendations.retrieve_past_recommendations(user_id)
960+
961+
if len(recommendation_pids) > 0:
962+
problem_details = db(ptable.id.belongs(recommendation_pids)).select()
963+
output["table"] = utilities.get_problems_table(problem_details,
964+
user_id,
965+
"recommendation",
966+
None)
967+
else:
968+
output["table"] = "No recommendations available."
969+
970+
query = (rtable.user_id == user_id) & (rtable.is_active == True)
971+
rows = db(query).select(rtable.generated_at)
972+
973+
output["can_update"] = True
974+
if len(rows) > 0:
975+
output["can_update"] = (datetime.datetime.now().date() - rows[0].generated_at).days \
976+
>= RECOMMENDATION_REFRESH_INTERVAL
977+
978+
return output
979+
980+
# ----------------------------------------------------------------------------
981+
@auth.requires_login()
982+
def update_recommendation_status():
983+
from recommendations.problems import update_recommendation_status
984+
985+
pid = long(request.args[0])
986+
update_recommendation_status(session.user_id, pid)
987+
934988
# ==============================================================================

deploy_scripts/STATIC_FILES

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
js/appjs/problems/index.js
1+
js/appjs/problems/recommendations.js
2+
js/appjs/google_analytics.js

deploy_scripts/static_files_list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ js/appjs/user/submissions.js
2828
js/appjs/user/profile.js
2929
js/appjs/default/user_editorials.js
3030
js/appjs/default/user_wise_editorials.js
31+
js/appjs/problems/recommendations.js
3132
css/stopstalk.css
3233
css/style.css
3334
css/material.css

modules/stopstalk_constants.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,17 @@
5050

5151
REQUEST_FAILURES = (SERVER_FAILURE, NOT_FOUND, OTHER_FAILURE)
5252

53-
VIEW_ONLY_SUBMISSION_SITES = ["HackerEarth"]
53+
VIEW_ONLY_SUBMISSION_SITES = ["HackerEarth"]
54+
55+
# Constants for recommendations
56+
PAST_PROBLEM_COUNT = 20
57+
DIFFICULTY_RANGE = 0.5
58+
RECOMMENDATION_COUNT = 10
59+
RECOMMENDATION_REFRESH_INTERVAL = 7
60+
61+
RECOMMENDATION_STATUS = {
62+
"RECOMMENDED": 0,
63+
"VIEWED": 1,
64+
"ATTEMPTED": 2,
65+
"SOLVED": 3
66+
}

modules/utilities.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -559,12 +559,13 @@ def pretty_string(all_items):
559559
# ------------------------------------------------------------------------------
560560
def get_problems_table(all_problems,
561561
logged_in_user_id,
562+
page_prefix,
562563
problem_with_user_editorials=None):
563564
T = current.T
564565
db = current.db
565566
uetable = db.user_editorials
566567
table = TABLE(_class="bordered centered")
567-
thead = THEAD(TR(TH(T("Problem Name"), _class="problem-search-name-column"),
568+
thead = THEAD(TR(TH(T("Problem Name"), _class=generate_page_specific_class(page_prefix, "name-column")),
568569
TH(T("Problem URL")),
569570
TH(T("Site")),
570571
TH(T("Accuracy")),
@@ -592,11 +593,13 @@ def get_problems_table(all_problems,
592593
problem["link"],
593594
link_class,
594595
link_title,
595-
problem["id"]),
596-
_class="problem-search-name-column"))
596+
problem["id"],
597+
page_prefix=page_prefix),
598+
_class=generate_page_specific_class(page_prefix, "name-column")))
597599
tr.append(TD(A(I(_class="fa fa-link"),
598600
_href=problem["link"],
599-
_class="tag-problem-link",
601+
_class=generate_page_specific_class(page_prefix, "tag-problem-link") + " tag-problem-link",
602+
data={"pid": problem["id"]},
600603
_target="_blank")))
601604
tr.append(TD(IMG(_src=current.get_static_url("images/" + \
602605
urltosite(problem["link"]) + \
@@ -616,7 +619,7 @@ def get_problems_table(all_problems,
616619
"editorials",
617620
args=problem["id"]),
618621
_target="_blank",
619-
_class="problem-search-editorial-link")))
622+
_class=generate_page_specific_class(page_prefix, "editorial-link"))))
620623
else:
621624
tr.append(TD())
622625

@@ -627,7 +630,7 @@ def get_problems_table(all_problems,
627630
_href=URL("problems",
628631
"tag",
629632
vars={"q": tag.encode("utf8"), "page": 1}),
630-
_class="tags-chip",
633+
_class=generate_page_specific_class(page_prefix, "tags-chip") + " tags-chip",
631634
_style="color: white;",
632635
_target="_blank"),
633636
_class="chip"))
@@ -661,7 +664,8 @@ def problem_widget(name,
661664
problem_id,
662665
disable_todo=False,
663666
anchor=True,
664-
request_vars={}):
667+
request_vars={},
668+
page_prefix=None):
665669
"""
666670
Widget to display a problem in UI tables
667671
@@ -682,9 +686,10 @@ def problem_widget(name,
682686
vars=dict(problem_id=problem_id,
683687
**request_vars),
684688
extension=False),
685-
_class="problem-listing " + link_class,
689+
_class=generate_page_specific_class(page_prefix, "problem-listing") + " " + link_class,
686690
_title=link_title,
687691
_target="_blank",
692+
data={"pid": problem_id},
688693
extension=False))
689694
else:
690695
problem_div.append(SPAN(name,
@@ -1474,4 +1479,8 @@ def render_user_editorials_table(user_editorials,
14741479

14751480
return table
14761481

1482+
# ----------------------------------------------------------------------------
1483+
def generate_page_specific_class(page_prefix, class_name):
1484+
return (page_prefix + "-" + class_name) if page_prefix is not None else class_name
1485+
14771486
# =============================================================================

private/scripts/submissions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ def get_submissions(user_id,
191191
Get the submissions and populate the database
192192
"""
193193

194+
from recommendations.problems import update_recommendation_status
195+
194196
submission_count = len(submissions)
195197

196198
if submission_count == 0:
@@ -253,6 +255,8 @@ def get_submissions(user_id,
253255
user_id,
254256
custom)
255257

258+
update_recommendation_status(user_id, pid, submission)
259+
256260
return submission_count
257261

258262
# ------------------------------------------------------------------------------

static/js/appjs/google_analytics.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,14 @@
107107
addEventListener('.custom-user-list-name', 'Custom user name in Modal', false);
108108
addEventListener('.custom-user-modal-site-profile', 'Custom user Site Profile in Modal', false);
109109
addEventListener('#open-side-nav', 'Open Side Navbar', false);
110-
addEventListener('#job-profile-cta', 'StopStalk Job profile CTA', false);
111110

112111
addEventListener('#recent-announcements-update-job-profile-now', 'Onboarding StopStalk Job Profile', false);
113112

114113
addEventListener('#problem-difficulty-later', 'Problem Difficulty modal - Later', false);
115114
addEventListener('#skip-this-problem', 'Problem Difficulty modal - Skip problem', false);
116115
addEventListener('#know-more-dashboard-page', 'Know more - Dashboard page', false);
116+
117+
addEventListener('#problem-recommendations-cta', 'Find me problems - Problem recommendations', false);
117118
};
118119

119120
var addProblemPageButtonsToGA = function() {
@@ -170,6 +171,9 @@
170171
var addProblemSearchPageToGA = function() {
171172
addEventListener('.tag-problem-link', 'Tags problem link', false);
172173
addEventListener('.tags-chip', 'Tags page chip', false);
174+
addEventListener('.problem-search-problem-listing', 'Problem name problem page link', false);
175+
addEventListener('.problem-search-tag-problem-link', 'Problem search tags problem link', false);
176+
addEventListener('.problem-search-tags-chip', 'Problem search tags page chip', false);
173177
addEventListener('#problem-name', 'Problem Search Problem name field', false);
174178
addEventListener('#profile-site', 'Problem Search Site field', false, 'change');
175179
addEventListener('#orderby-problem', 'Problem Search Order By field', false, 'change');
@@ -227,6 +231,24 @@
227231
addEventListener('.atcoder-handle-card-update-now', 'Cards - Atcoder handle update now', false);
228232
};
229233

234+
var addProblemRecommendationsPageToGA = function() {
235+
addEventListener('#update-problem-recommendations', 'Refresh problem recommendations', false);
236+
addEventListener('#close-refresh-recommendations', 'Close refresh problem recommendations modal', false);
237+
addEventListener('#confirm-refresh-recommendations', 'Confirm refresh problem recommendations modal', false);
238+
addEventListener('#recommendation-contact-us', 'Recommendation feedback', false);
239+
addEventListener('.recommendation-problem-listing', 'Recommendations page problem link', false);
240+
addEventListener('.recommendation-tag-problem-link', 'Recommendations page tags link', false);
241+
addEventListener('.recommendation-tags-chip', 'Recommendations page tags chip', false);
242+
addEventListener('.recommendation-editorial-link', 'Recommendations page editorial link', false);
243+
};
244+
245+
var addProblemsAuthoredPageToGA = function() {
246+
addEventListener('.problems-authored-problem-listing', 'Problems authored page problem link', false);
247+
addEventListener('.problems-authored-tag-problem-link', 'Problems authored page tags link', false);
248+
addEventListener('.problems-authored-tags-chip', 'Problems authored page tags chip', false);
249+
addEventListener('.problems-authored-editorial-link', 'Problems authored page editorial link', false);
250+
};
251+
230252

231253
$(document).ready(function() {
232254
addNavItemsToGA();
@@ -244,5 +266,7 @@
244266
addProblemEditorialsPageToGA();
245267
addDashboardPageToGA();
246268
addMiscellaneousToGA();
269+
addProblemRecommendationsPageToGA();
270+
addProblemsAuthoredPageToGA();
247271
});
248272
})(jQuery);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
(function($) {
2+
'use strict';
3+
$(document).ready(function() {
4+
$('#recommendation-refresh-modal').modal();
5+
6+
$('#confirm-refresh-recommendations').click(function() {
7+
var $this = $(this);
8+
$.ajax({
9+
url: fetchRecommendationsUrl,
10+
method: 'GET',
11+
data: {refresh: canUpdate},
12+
success: function(response) {
13+
$('#recommendations-table-content').html(response['table']);
14+
15+
if (response['can_update'] === false) {
16+
$this.addClass("disabled");
17+
} else {
18+
$this.removeClass("disabled");
19+
}
20+
},
21+
error: function(err) {
22+
$.web2py.flash('Could not fetch new recommendations.');
23+
}
24+
});
25+
});
26+
27+
$(document).on('click', '.recommendation-problem-listing, .recommendation-tag-problem-link', function() {
28+
var $this = $(this);
29+
var pid = $this.data('pid');
30+
31+
$.ajax({
32+
url: '/problems/update_recommendation_status/' + pid,
33+
method: 'POST',
34+
success: function(response) {
35+
},
36+
error: function(err) {
37+
}
38+
})
39+
});
40+
});
41+
})(jQuery);

views/layout.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
<ul id="welcome" class="dropdown-content" style="min-width: 190px; color: #536dfe;">
8484
{{if auth.is_logged_in():}}
8585
<li><a href="{{=URL('user', 'profile', args=session.handle)}}" data-analytics-label="Nav User Profile" class="nav-dropdown">{{=T("Profile")}}</a></li>
86+
<li><a href="{{=URL('default', 'job_profile')}}" data-analytics-label="Nav Job Preferences" class="nav-dropdown">{{=T("Job Preferences")}}</a></li>
8687
<li><a href="{{=URL('user', 'submissions', args=session.handle)}}" data-analytics-label="Nav My Submissions" class="nav-dropdown">{{=T("My Submissions")}}</a></li>
8788
<li><a href="{{=URL('default', 'user_wise_editorials', args=session.handle)}}" data-analytics-label="Nav My Editorials" class="nav-dropdown">{{=T("My Editorials")}}</a></li>
8889
<li><a href="{{=URL('user', 'update_details')}}" data-analytics-label="Nav Update Details" class="nav-dropdown">{{=T("Update Details")}}</a></li>
@@ -116,7 +117,7 @@
116117
<div class="right">
117118
<span class="left">
118119
</span>
119-
<a id="job-profile-cta" class="btn btn-default black-text" style="background-color: #f5f5f5; margin-right: 13px;" href="{{=URL('default', 'job_profile')}}" target="_blank">Job Profile</a>
120+
<a id="problem-recommendations-cta" class="btn btn-default black-text" style="background-color: #fff; margin-right: 13px;" href="{{=URL('problems', 'recommendations')}}">Find Me Problems</a>
120121
<ul class="right" style="float: left;">
121122
<li><a class="dropdown-button left" href="" data-activates="welcome"><i class="fa fa-sort-down fa-2x"></i></a></li>
122123
<!-- Dropdown Trigger -->
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{{extend 'layout.html'}}
2+
<html>
3+
{{block head}}
4+
<title>{{=T("Problem Recommendations")}}</title>
5+
<meta name="description" content="Problem recommendations">
6+
<link rel="stylesheet" href="{{=URL('static', 'flag-icon/css/flag-icon.min.css')}}">
7+
8+
<script>
9+
var fetchRecommendationsUrl = "{{=URL('problems', 'recommendations', extension='json')}}";
10+
var canUpdate = ("{{=can_update}}" === "True");
11+
</script>
12+
13+
<script src="{{=get_static_url('js/appjs/problems/recommendations.js')}}" type="text/javascript"></script>
14+
{{end}}
15+
16+
<body>
17+
<h1>{{=T("Problem Recommendations")}}</h1>
18+
<div id="recommendation-refresh-modal" style="width: 50%" class="modal center">
19+
<div class="modal-content">
20+
<h5 style="margin-bottom: 15px;" id="recommendation-refresh-modal-header">Are you sure?</h5>
21+
<div>You cannot refresh the recommendations for the next {{=RECOMMENDATION_REFRESH_INTERVAL}} days.</div>
22+
<div>Please <a id="recommendation-contact-us" href="{{=URL('default', 'contact_us')}}" target="_blank">write to us</a> for feedback on recommendations!</div>
23+
</div>
24+
<div class="modal-footer">
25+
<button id="close-refresh-recommendations" class="modal-close waves-effect orange lighten-5 btn-flat">Don't Refresh</button>
26+
<button id="confirm-refresh-recommendations" class="modal-close waves-effect blue lighten-5 btn-flat">Refresh</button>
27+
</div>
28+
</div>
29+
<div>
30+
{{if can_update:}}
31+
<button id="update-problem-recommendations" data-target="recommendation-refresh-modal" class="btn btn-default white-text tooltipped modal-trigger" data-delay="50" data-tooltip="Fetch new recommendations" style="margin-bottom: 15px;">Refresh</button>
32+
{{else:}}
33+
<div class="tooltipped" data-delay="50" data-tooltip="Can only update once in {{=RECOMMENDATION_REFRESH_INTERVAL}} days">
34+
<button id="update-problem-recommendations-disabled" class="btn btn-default disabled white-text" style="margin-bottom: 15px;">Refresh</button>
35+
</div>
36+
{{pass}}
37+
</div>
38+
<br/>
39+
<div id="recommendation-table">
40+
<div class="row">
41+
<div class="col offset-s1 s10 z-depth-2">
42+
<div id="recommendations-table-content">{{=table}}</div>
43+
</div>
44+
</div>
45+
</div>
46+
<br/>
47+
</body>
48+
</html>

0 commit comments

Comments
 (0)