]> git.somenet.org - pub/jan/mattermost-bot.git/blob - modules/DialogManagedCourseFeedback.py
requirements.txt
[pub/jan/mattermost-bot.git] / modules / DialogManagedCourseFeedback.py
1 # Mattermost Bot module.
2 #  Copyright (c) 2016-2022 by Someone <someone@somenet.org> (aka. Jan Vales <jan@jvales.net>)
3 #  published under MIT-License
4
5 from inspect import cleandoc
6 import csv
7 import os
8 import re
9
10
11 # pylint: disable=wrong-import-position
12 from AbstractCommand import AbstractCommand
13 class DialogManagedCourseFeedback(AbstractCommand):
14     TRIGGER = "course-feedback"
15     CONFIG = {"display_name": "somebot-command", "auto_complete": True,
16               "auto_complete_hint": "['--help']",
17              }
18     CONFIG["auto_complete_desc"] = CONFIG["description"] = AbstractCommand.ICON_PRIVATE+"Run in course-channel. Opens dialog to edit/delete course-feedback."
19
20
21     def __init__(self, team_id, datadir):
22         super().__init__(team_id)
23
24         self.datadir = datadir
25
26
27     def on_POST(self, request, data):
28         if re.match(r"^(-|—|–)+help$", data["text"]):
29             request.respond_cmd_temp(cleandoc("""
30                 ## :pencil:``Course feedback!``:rocket:
31                 We want your feedback in order to find out which courses need to be improved asap to achieve the excellence we are accustomed to, or better.
32                 The feedback will be anonymized during aggregation, then analyzed and discussed with the appropriate people like the course admins, the deans of study affairs and the vice rector for teaching matters and/or people who can give legal advice about course rules.
33
34                 What we want to know:
35                 + Was there an online option and how was it?
36                 + How much time did you spent on this course?
37                 + This course's modalities - describe them in a few words. (Also consider updating the info in VoWi - its a work resource for us and we use it to represent you)
38                 + How did you like the modalities of this course? (Would you want it to be applied to others?)
39                 + Overall feedback for this course.
40                 + Feedback about this survey for :fsinf:. (This will not be shared outside of FSInf)
41
42                 :information_source: *We are your legally appointed representatives - we will never forward any personal info about you without your explicit permission.* :)
43                 """))
44             return
45
46         c = self.bot.api.get_channel(data["channel_id"])
47
48         if c["type"] != 'O' or c["name"] in ["town-square", "off-topic"] or c["display_name"][0] == "=":
49             request.respond_cmd_err("``/"+self.TRIGGER+"`` must be run in a course-channel.")
50             return
51
52         self._open_dialog(request, data)
53         request.respond_cmd_temp("## :white_check_mark: Success! :)\n#### Opened dialog.")
54
55
56     def on_POST_interactive(self, request, data):
57 #        import pprint
58 #        print("on_POST_interactive")
59 #        pprint.pprint(data)
60
61         self._open_dialog(request, data)
62         request.respond()
63
64
65     def on_POST_dialog(self, request, data):
66 #        import pprint
67 #        print("on_POST_dialog")
68 #        pprint.pprint(data)
69
70         if data["submission"]["feedback_hours"] and int(data["submission"]["feedback_hours"]) < 0:
71             request.respond(200, {"errors": {"feedback_hours": "Hours must be positive."}})
72             return
73
74         if data["submission"]["feedback_hours"] and int(data["submission"]["feedback_hours"]) > 3624:
75             request.respond(200, {"errors": {"feedback_hours": "Hours must be smaller than 3624. Did you really spend EVERY HOUR of the semester for this course? Did you not sleep at all?"}})
76             return
77
78
79         c = self.bot.api.get_channel(data["channel_id"])
80
81         self._response_store(self.datadir+data["callback_id"], "-feedback_distance.txt", data["submission"]["feedback_distance"], c["name"])
82         self._response_store(self.datadir+data["callback_id"], "-feedback_course.txt", data["submission"]["feedback_course"], c["name"])
83         self._response_store(self.datadir+data["callback_id"], "-feedback_hours.txt", data["submission"]["feedback_hours"], c["name"])
84         self._response_store(self.datadir+data["callback_id"], "-feedback_modus.txt", data["submission"]["feedback_modus"], c["name"])
85         self._response_store(self.datadir+data["callback_id"], "-feedback_modus_like.txt", data["submission"]["feedback_modus_like"], c["name"])
86         self._response_store(self.datadir+data["callback_id"], "-feedback_fsinf.txt", data["submission"]["feedback_fsinf"], c["name"])
87
88         request.respond(200, {})
89
90
91     def _open_dialog(self, request, data):
92         callback_id = "res-"+data["team_id"]+"-"+data["channel_id"]+"-"+data["user_id"]
93
94         feedback_distance = self._response_load(self.datadir+callback_id, "-feedback_distance.txt")
95         feedback_course = self._response_load(self.datadir+callback_id, "-feedback_course.txt")
96         feedback_hours = self._response_load(self.datadir+callback_id, "-feedback_hours.txt")
97         feedback_modus = self._response_load(self.datadir+callback_id, "-feedback_modus.txt")
98         feedback_modus_like = self._response_load(self.datadir+callback_id, "-feedback_modus_like.txt", "0")
99         feedback_fsinf = self._response_load(self.datadir+callback_id, "-feedback_fsinf.txt")
100
101         dialog = {
102             "callback_id": callback_id,
103             "title": "Course feedback",
104             "submit_label":"Submit",
105             "elements":[{
106                 "display_name": "How many hours did you invest into this course?",
107                 "placeholder": "Hours. Positive integer.",
108                 "name": "feedback_hours",
109                 "type": "text",
110                 "subtype": "number",
111                 "optional": True,
112                 "default": feedback_hours
113             },{
114                 "display_name": "How did you like the modalities of this course? (Do you want it to be applied to others?)",
115                 "placeholder": "Type here. Max. 3000 characters.",
116                 "name": "feedback_modus_like",
117                 "type": "radio",
118                 "options": [
119                     {"text": "I loved it", "value": "2" },
120                     {"text": "I liked it", "value": "1" },
121                     {"text": "I'm indifferent/Don't want to answer (default)", "value": "0" },
122                     {"text": "I disliked it", "value": "-1" },
123                     {"text": "I hated it", "value": "-2" },
124                 ],
125                 "optional": True,
126                 "default": feedback_modus_like
127             },{
128                 "display_name": "Describe the modalities (issues encountered and possible improvements) in a few words (Max. 3000 characters)",
129                 "placeholder": "Type here. Max. 3000 characters.\n+ What was it? \n+ Were deadlines announced in a timely manner?\n+ Was the grading surprising?\n+ Random changes during the semester?",
130                 "name": "feedback_modus",
131                 "type": "textarea",
132                 "optional": True,
133                 "default": feedback_modus
134             },{
135                 "display_name": "Was there an online option? Was it announced? Was it possible to complete the course only online? (Max. 3000 characters)",
136                 "placeholder": "Type here. Max. 3000 characters.\n+ What did you think about the online option?\n+ Was it announced?\n+ Was it possible to complete the course only online?",
137                 "name": "feedback_distance",
138                 "type": "textarea",
139                 "help_text": "All your imput on all questions will be anonymized during aggregation. Aggregated input will be analyzed by FSInf and further used/forwarded to the course admins, the deans of study affairs, the vice rector for teaching and possibly others that are deemed able to fix things. You can edit/clear it at any time.",
140                 "optional": True,
141                 "default": feedback_distance
142             },{
143                 "display_name": "Overall feedback about the course (Max. 3000 characters)",
144                 "placeholder": "Type here. Max. 3000 characters.\n+ What should be changed?\n+ What was unfair?\n+ What was really good?",
145                 "name": "feedback_course",
146                 "type": "textarea",
147                 "optional": True,
148                 "default": feedback_course
149             },{
150                 "display_name": "Feedback (about this survey) for FSInf (Max. 3000 characters)",
151                 "placeholder": "Type here. Max. 3000 characters.",
152                 "name": "feedback_fsinf",
153                 "type": "textarea",
154                 "help_text": "Your input will be anonymized during aggregation. Aggregated input will be analyzed by FSInf. We will not forward this text to anyone. You can edit/clear it at any time.",
155                 "optional": True,
156                 "default": feedback_fsinf
157             }]
158         }
159
160         self.bot.api.open_dialog(data["trigger_id"], self.URL+"/dialog", dialog)
161
162
163     def _response_load(self, path, filename, default=""):
164         path = path+filename
165         ret = default
166         if os.path.isfile(path):
167             with open(path, "r") as f:
168                 ret = f.read()
169         return ret
170
171
172     def _response_store(self, path, filename, data, channelname=""):
173         path = path+filename
174         data = str(data).strip()
175         if data == "" or data == "None" or data == "0":
176             if os.path.isfile(path):
177                 os.remove(path)
178                 self.bot.debug_chan("``course-feedback::"+channelname+filename+"`` result deleted.")
179         else:
180             with open(path, "w") as f:
181                 f.write(data)
182             self.bot.debug_chan("``course-feedback::"+channelname+filename+"`` result stored.\n```\n"+data+"\n```")