diff --git a/rocketc/rocketc.py b/rocketc/rocketc.py index ed4de9d5..8fdc2d92 100644 --- a/rocketc/rocketc.py +++ b/rocketc/rocketc.py @@ -30,6 +30,39 @@ CACHE_TIMEOUT = 86400 +def generate_custom_fields(course, team=None, specific_team=False): + + team_name, topic = generate_team_variables(team) + return { + "customFields": { + "course": course, + "team": team_name, + "topic": topic, + "specificTeam": specific_team, + } + } + + +def generate_query_dict(course, team=None, specific_team=False): + + team_name, topic = generate_team_variables(team) + query = { + "customFields.course": course, + "customFields.team": team_name, + "customFields.topic": topic, + "customFields.specificTeam": specific_team, + } + return {"query": json.dumps(query)} + + +def generate_team_variables(team): + if isinstance(team, dict): + topic_id = re.sub(r'\W+', '', team.get("topic_id", None)) + team_name = re.sub(r'\W+', '', team.get("name", None)) + return team_name, topic_id + return None, None + + @XBlock.wants("user") # pylint: disable=too-many-ancestors, too-many-instance-attributes @XBlock.wants("settings") class RocketChatXBlock(XBlock, XBlockWithSettingsMixin, StudioEditableXBlockMixin): @@ -133,6 +166,8 @@ class RocketChatXBlock(XBlock, XBlockWithSettingsMixin, StudioEditableXBlockMixi has_score = graded_activity team_view = True + _api_teams = None + _api_rocket_chat = None VIEWS = ["Main View", "Team Discussion", "Specific Channel"] @@ -161,7 +196,6 @@ def student_view(self, context=None): The primary view of the RocketChatXBlock, shown to students when viewing courses. """ - self.api_rocket_chat = self._api_rocket_chat() # pylint: disable=attribute-defined-outside-init in_studio_runtime = hasattr( self.xmodule_runtime, 'is_author_mode') # pylint: disable=no-member @@ -187,7 +221,6 @@ def student_view(self, context=None): def author_view(self, context=None): """ Returns author view fragment on Studio """ # pylint: disable=unused-argument - self.api_rocket_chat.convert_to_private_channel("general") frag = Fragment(u"Studio Runtime RocketChatXBlock") frag.add_javascript(self.resource_string("static/js/src/rocketc.js")) frag.initialize_js('RocketChatXBlock') @@ -222,39 +255,52 @@ def workbench_scenarios(): """), ] - def _api_rocket_chat(self): + @property + def api_rocket_chat(self): """ Creates an ApiRocketChat object """ - try: - user = self.xblock_settings["admin_user"] - password = self.xblock_settings["admin_pass"] - except KeyError: - LOG.exception("The admin's settings has not been found") - raise - api = ApiRocketChat(user, password, self.server_data["private_url_service"]) + if not self._api_rocket_chat: + try: + user = self.xblock_settings["admin_user"] + password = self.xblock_settings["admin_pass"] + except KeyError: + LOG.exception("The admin's settings has not been found") + raise + self._api_rocket_chat = ApiRocketChat( # pylint: disable=attribute-defined-outside-init + user, + password, + self.server_data["private_url_service"] + ) - LOG.info("Api rocketChat initialize: %s ", api) + LOG.info("Api rocketChat initialize: %s ", self._api_rocket_chat) - return api + return self._api_rocket_chat - def _api_teams(self): + @property + def api_teams(self): """ Creates an ApiTeams object """ - try: - client_id = self.xblock_settings["client_id"] - client_secret = self.xblock_settings["client_secret"] - except KeyError: - raise - - server_url = settings.LMS_ROOT_URL - - api = ApiTeams(client_id, client_secret, server_url) + if not self._api_teams: + try: + client_id = self.xblock_settings["client_id"] + client_secret = self.xblock_settings["client_secret"] + except KeyError, xblock_settings_error: + LOG.error('Get rocketchat xblock settings error: %s', xblock_settings_error) + raise + + server_url = settings.LMS_ROOT_URL + + self._api_teams = ApiTeams( # pylint: disable=attribute-defined-outside-init + client_id, + client_secret, + server_url + ) - LOG.info("Api Teams initialize: %s ", api) + LOG.info("Api Teams initialize: %s ", self._api_teams) - return api + return self._api_teams @property def server_data(self): @@ -272,13 +318,12 @@ def user_data(self): """ This method initializes the user's parameters """ - runtime = self.xmodule_runtime # pylint: disable=no-member + runtime = self.runtime # pylint: disable=no-member user = runtime.service(self, 'user').get_current_user() user_data = {} user_data["email"] = user.emails[0] user_data["role"] = runtime.get_user_role() user_data["course_id"] = runtime.course_id - user_data["course"] = re.sub('[^A-Za-z0-9]+', '', runtime.course_id.to_deprecated_string()) # pylint: disable=protected-access user_data["username"] = user.opt_attrs['edx-platform.username'] user_data["anonymous_student_id"] = runtime.anonymous_student_id return user_data @@ -290,6 +335,17 @@ def xblock_settings(self): """ return self.get_xblock_settings() + @property + def course_id(self): + """ + This method allows to get the course_id + """ + try: + return re.sub('[^A-Za-z0-9]+', '', unicode(self.xmodule_runtime.course_id)) + except AttributeError: + course_id = unicode(self.runtime.course_id) + return re.sub('[^A-Za-z0-9]+', '', course_id.split("+branch", 1)[0]) + def channels_enabled(self): """ This method returns a list with the channel options @@ -304,24 +360,19 @@ def get_groups(self): """ This method lists the existing groups """ - api = self._api_teams() - teams = api.get_course_teams(self.runtime.course_id) - topics = [re.sub(r'\W+', '', team["topic_id"]) for team in teams] - # the following instructions get all the groups - # except the groups with the string of some topic in its name - query = {'name': {'$regex': '^(?!.*({topics}).*$)'.format(topics='|'.join(topics))}} if topics else {} - kwargs = {"query": json.dumps(query)} - groups = self._api_rocket_chat().get_groups(**kwargs) - - # these instructions get all the groups with the customField "specificTeam" set - query = {'customFields.specificTeam': {'$regex': '^.*'}} - kwargs = {'query': json.dumps(query)} - team_groups = self._api_rocket_chat().get_groups(**kwargs) + # except the team groups and specific teams + course = self.course_id + kwargs = generate_query_dict(course) + groups = [group.split("__", 1)[0] for group in self.api_rocket_chat.get_groups(**kwargs)] + kwargs = generate_query_dict(course, specific_team=True) # This instruction adds the string "(Team Group)" if the group is in team_groups - # pylint: disable=line-too-long - groups = ['{}-{}'.format('(Team Group)', group) if group in team_groups else group for group in groups] + groups += [ + '{}-{}'.format("(Team Group)", team_group.split("__", 1)[0]) + for team_group in self.api_rocket_chat.get_groups(**kwargs) + ] + groups.append("") return sorted(groups) @@ -333,7 +384,7 @@ def init(self): user_data = self.user_data - response = self.login(user_data) + response = self._login(user_data) if response['success']: response = response['data'] user_id = response['userId'] @@ -342,15 +393,12 @@ def init(self): response['default_group'] = self._join_user_to_groups(user_id, user_data, auth_token) self._update_user(user_id, user_data) - if user_data["role"] == "instructor" and self.rocket_chat_role != "bot": - self.api_rocket_chat.change_user_role(user_id, "bot") - self._grading_discussions(response['default_group']) return response - else: - return response['errorType'] - def login(self, user_data): + return response['errorType'] + + def _login(self, user_data): """ This method allows to get the user's authToken and id or creates a user to login in RocketChat @@ -378,60 +426,57 @@ def login(self, user_data): cache.set(key, data, CACHE_TIMEOUT) return data - def _add_user_to_course_group(self, group_name, user_id): + def _add_user_to_course_group(self, user_id): """ This method add the user to the default course channel """ - api = self.api_rocket_chat - rocket_chat_group = api.search_rocket_chat_group(group_name) - - if rocket_chat_group['success']: - api.add_user_to_group(user_id, rocket_chat_group['group']['_id']) - else: - rocket_chat_group = api.create_group(group_name, [self.user_data["username"]]) - - self.group = api.search_rocket_chat_group( # pylint: disable=attribute-defined-outside-init - group_name) + group_name = "{}__{}".format("General", self.course_id) + self._add_user_to_group( + user_id, + group_name, + members=[self.user_data["username"]], + custom_fields=generate_custom_fields(course=self.course_id), + create=True + ) - def _add_user_to_default_group(self, group_name, user_id): + def _add_user_to_specific_group(self, group_name, user_id): """ + This method allows to add a user to a given group. """ - api = self.api_rocket_chat - group_info = api.search_rocket_chat_group(group_name) - - if group_info["success"]: - api.add_user_to_group(user_id, group_info['group']['_id']) - return True - return False + group = "{}__{}".format(group_name, self.course_id) + result = self._add_user_to_group(user_id, group) + return result, group - def _add_user_to_team_group(self, user_id, username, course_id, auth_token): + def _add_user_to_team_group(self, user_id, username, auth_token): """ Add the user to team's group in rocketChat """ - self.api_teams = self._api_teams() # pylint: disable=attribute-defined-outside-init - team = self._get_team(username, course_id) + team = self._get_team(username) if team is None: self._remove_user_from_group(self.team_channel, user_id, auth_token) return False - topic_id = re.sub(r'\W+', '', team["topic_id"]) - team_name = re.sub(r'\W+', '', team["name"]) - group_name = "-".join(["Team", topic_id, team_name]) + team_name, topic_id = generate_team_variables(team) + group_name = "{}__{}__{}".format(team_name, topic_id, self.course_id) if self.team_channel != group_name: self._remove_user_from_group(self.team_channel, user_id, auth_token) self.team_channel = group_name - response = self._add_user_to_group(user_id, group_name, username) - LOG.info("Add to team group response: %s", response) - return response["success"] + return self._add_user_to_group( + user_id, + group_name, + members=[username], + custom_fields=generate_custom_fields(self.course_id, team), + create=True + ) - def _get_team(self, username, course_id): + def _get_team(self, username): """ This method gets the user's team """ - team = self.api_teams.get_user_team(course_id, username) + team = self.api_teams.get_user_team(self.runtime.course_id, username) LOG.info("Get Team response: %s", team) if team: return team[0] @@ -444,8 +489,7 @@ def _join_user_to_groups(self, user_id, user_data, auth_token): default_channel = self.default_channel if self.selected_view == self.VIEWS[1] and self._teams_is_enabled(): - self.team_view = self._add_user_to_team_group( - user_id, user_data["username"], user_data["course_id"], auth_token) + self.team_view = self._add_user_to_team_group(user_id, user_data["username"], auth_token) self.ui_is_block = self.team_view return self.team_channel @@ -453,35 +497,48 @@ def _join_user_to_groups(self, user_id, user_data, auth_token): if default_channel.startswith("(Team Group)"): return self._join_user_to_specific_team_group(user_id, user_data, default_channel) - self.ui_is_block = self._add_user_to_default_group(default_channel, user_id) + self.ui_is_block, default_channel = self._add_user_to_specific_group(default_channel, user_id) return default_channel else: self.ui_is_block = False - self._add_user_to_course_group(user_data["course"], user_id) + self._add_user_to_course_group(user_id) return None - def _add_user_to_group(self, user_id, group_name, username): - group_info = self.api_rocket_chat.search_rocket_chat_group(group_name) + def _add_user_to_group(self, user_id, group_name, **kwargs): + """ + This method allows to add a user to any channel, returns True if it's successful + """ + group = self.api_rocket_chat.search_rocket_chat_group(group_name) + response = {} - if group_info["success"]: - response = self.api_rocket_chat.add_user_to_group(user_id, group_info['group']['_id']) - LOG.info("Add to team group response: %s", response) - return response + if group["success"]: + response = self.api_rocket_chat.add_user_to_group(user_id, group['group']['_id']) + elif kwargs.get("create", False): + response = self.api_rocket_chat.create_group( + group_name, + kwargs.get("members", []), + **kwargs.get("custom_fields", {}) + ) - return self.api_rocket_chat.create_group(group_name, [username]) + return response.get("success", False) def _join_user_to_specific_team_group(self, user_id, user_data, default_channel): - self.api_teams = self._api_teams() # pylint: disable=attribute-defined-outside-init - team = self._get_team(user_data["username"], user_data["course_id"]) + team = self._get_team(user_data["username"]) if team is None: self.team_view = False return None default_channel = self._create_team_group_name( team, - default_channel.replace("(Team Group)-", "") - ) - response = self._add_user_to_group(user_id, default_channel, user_data["username"]) - self.ui_is_block = response["success"] + default_channel.replace("(Team Group)-", ""), + self.course_id + ) + self.ui_is_block = self._add_user_to_group( + user_id, + default_channel, + members=[user_data["username"]], + custom_fields=generate_custom_fields(self.course_id, team), + create=True + ) return default_channel def _teams_is_enabled(self): @@ -533,54 +590,45 @@ def create_group(self, data, suffix=""): This method allows to create a group """ # pylint: disable=unused-argument - self.api_rocket_chat = self._api_rocket_chat() # pylint: disable=attribute-defined-outside-init - self.api_teams = self._api_teams() # pylint: disable=attribute-defined-outside-init - - group_name = data["groupName"] - description = data["description"] - topic = data["topic"] + group_name = data.get("groupName", None) if group_name == "" or group_name is None: return {"success": False, "error": "Group Name is not valid"} group_name = re.sub(r'\W+', '', group_name) + specific_team = False + members = [] + team = None + + if data.get("asTeam"): + group_name = "{}__{}".format(group_name, self.course_id) + specific_team = True - if data.get("asTeam", False): - custom_fields = {"customFields": {"specificTeam": group_name}} - group = self.api_rocket_chat.create_group(group_name, **custom_fields) - if group["success"]: - self.default_channel = "{}-{}".format('(Team Group)', group_name) + elif data.get("teamGroup"): + team = self._get_team(self.user_data["username"]) + members = list(self.get_team_members(team)) + group_name = self._create_team_group_name(team, group_name, self.course_id) else: - try: - course_id = self.xmodule_runtime.course_id # pylint: disable=no-member - team = self._get_team(self.user_data["username"], course_id) - members = list(self.get_team_members(team)) - group = self.api_rocket_chat.create_group( - self._create_team_group_name(team, group_name), - members - ) - - except AttributeError: - group = self.api_rocket_chat.create_group(group_name) - if group["success"]: - self.default_channel = group_name + group_name = "{}__{}".format(group_name, self.course_id) + + custom_fields = generate_custom_fields(self.course_id, team, specific_team) + group = self.api_rocket_chat.create_group(group_name, members, **custom_fields) if "group" in group: group_id = group["group"]["_id"] - self.api_rocket_chat.set_group_description(group_id, description) - self.api_rocket_chat.set_group_topic(group_id, topic) + self.api_rocket_chat.set_group_description(group_id, data.get("description")) + self.api_rocket_chat.set_group_topic(group_id, data.get("topic")) LOG.info("Method Public Create Group: %s", group) return group @staticmethod - def _create_team_group_name(team, group_name): + def _create_team_group_name(team, group_name, course): - team_topic = re.sub(r'\W+', '', team["topic_id"]) - team_name = re.sub(r'\W+', '', team["name"]) - return "-".join([team_topic, team_name, group_name]) + team_name, team_topic = generate_team_variables(team) + return "{}__{}__{}__{}".format(group_name, team_name, team_topic, course) def _remove_user_from_group(self, group_name, user_id, auth_token=None): """ @@ -594,21 +642,20 @@ def _remove_user_from_group(self, group_name, user_id, auth_token=None): regex = group_name.replace('Team-', '', 1) query = {"name": {"$regex": regex}} kwargs = {"query": json.dumps(query)} - api = self.api_rocket_chat groups = api.list_all_groups(user_id, auth_token, **kwargs) if groups["success"]: groups = groups["groups"] for group in groups: api.kick_user_from_group(user_id, group["_id"]) - return True + return {"success": True} group = api.search_rocket_chat_group(group_name) if group["success"]: group = group["group"] response = api.kick_user_from_group(user_id, group["_id"]) - return response.get('success', False) - return False + return response + return {"success": False, "error": "Channel not found"} def get_team_members(self, team): """ @@ -628,7 +675,6 @@ def leave_group(self, data, suffix=""): """ # pylint: disable=unused-argument - self.api_rocket_chat = self._api_rocket_chat() # pylint: disable=attribute-defined-outside-init username = self.user_data["username"] user = self.api_rocket_chat.search_rocket_chat_user(username) group_name = data["groupName"] @@ -639,8 +685,12 @@ def leave_group(self, data, suffix=""): if group_name == "" or group_name is None: return {"success": False, "error": "Group Name is not valid"} - if group_name == self.team_channel or group_name == self.user_data["course"]: + team = self._get_team(self.user_data["username"]) + team_name, topic_id = generate_team_variables(team) + + if group_name == team_name or group_name == "General": return {"success": False, "error": "You Can Not Leave a Main Group"} + group_name = "{}__{}__{}__{}".format(group_name, team_name, topic_id, self.course_id) return self._remove_user_from_group(group_name, user["user"]["_id"]) @@ -650,20 +700,14 @@ def get_list_of_groups(self, data, suffix=""): # pylint: disable=unused-argument user_id = data.get("userId", None) auth_token = data.get("authToken", None) - self.api_teams = self._api_teams() # pylint: disable=attribute-defined-outside-init if not user_id or not auth_token: LOG.warn("Invalid data for method get_list_of_groups: %s", data) return None - course_id = self.xmodule_runtime.course_id # pylint: disable=no-member - team = self._get_team(self.user_data["username"], course_id) - topic = re.sub(r'\W+', '', team["topic_id"]) - team_name = re.sub(r'\W+', '', team["name"]) + team = self._get_team(self.user_data["username"]) - regex = "-".join([topic, team_name]) - query = {"name": {"$regex": regex}} - kwargs = {"query": json.dumps(query)} + kwargs = generate_query_dict(self.course_id, team=team) groups = list(self._get_list_groups(user_id, auth_token, **kwargs)) return groups @@ -672,7 +716,7 @@ def _get_list_groups(self, user_id, auth_token, **kwargs): """ This method allows to get a list of group names """ - api = self._api_rocket_chat() + api = self.api_rocket_chat groups = api.list_all_groups(user_id, auth_token, **kwargs) if groups["success"]: groups = groups["groups"] @@ -704,7 +748,7 @@ def _filter_by_reaction_and_user_role(self, messages, reaction): Returns generator with filtered messages by a given reaction """ for message in messages: - if not reaction in message.get("reactions", {}): + if reaction not in message.get("reactions", {}): continue usernames = message["reactions"][reaction]["usernames"] @@ -754,7 +798,7 @@ def logout_user(self, request=None, suffix=None): key = request.GET.get("beacon_rc") data = cache.get(key) if data: - api = self._api_rocket_chat() + api = self.api_rocket_chat user_data = data.get("data") login_token = user_data.get("authToken") user_id = user_data.get("userId") diff --git a/rocketc/static/js/src/rocketc.js b/rocketc/static/js/src/rocketc.js index f90a1a1e..c77585e5 100644 --- a/rocketc/static/js/src/rocketc.js +++ b/rocketc/static/js/src/rocketc.js @@ -106,7 +106,8 @@ function RocketChatXBlock(runtime, element) { var groupName = $("#group-name-create").val(); var description = $("#group-description").val(); var topic = $("#group-topic").val(); - var data = {groupName, description, topic}; + var teamGroup = true; + var data = {groupName, description, topic, teamGroup}; $.ajax({ type: "POST", url: createGroup, @@ -133,9 +134,9 @@ function RocketChatXBlock(runtime, element) { $("#select-channel").empty(); $("#group-names").empty(); for (var i in data){ - var item = $(""); + var item = $(""); $("#select-channel").append(item); - $("#group-names").append($("