def getCommentByID(self, comment_id): """Get the :class:`rtcclient.models.Comment` object by its id Note: the comment id starts from 0 :param comment_id: the comment id (integer or equivalent string) :return: the :class:`rtcclient.models.Comment` object :rtype: rtcclient.models.Comment """ # check the validity of comment id try: if isinstance(comment_id, bool): raise ValueError() if isinstance(comment_id, six.string_types): comment_id = int(comment_id) if not isinstance(comment_id, int): raise ValueError() except (ValueError, TypeError): raise exception.BadValue("Please input valid comment id") comment_url = "/".join([self.url, "rtc_cm:comments/%s" % comment_id]) try: return Comment(comment_url, self.rtc_obj) except HTTPError: self.log.error("Comment %s does not exist", comment_id) raise exception.BadValue("Comment %s does not exist" % comment_id)
def getTemplates(self, workitems, template_folder=None, template_names=None, keep=False, encoding="UTF-8"): """Get templates from a group of to-be-copied :class:`Workitems` and write them to files named after the names in `template_names` respectively. :param workitems: a :class:`list`/:class:`tuple`/:class:`set` contains the ids (integer or equivalent string) of some to-be-copied :class:`Workitems` :param template_names: a :class:`list`/:class:`tuple`/:class:`set` contains the template file names for copied :class:`Workitems`. If `None`, the new template files will be named after the :class:`rtcclient.workitem.Workitem` id with "`.template`" as a postfix :param template_folder: refer to :class:`rtcclient.template.Templater.getTemplate` :param keep: (default is False) refer to :class:`rtcclient.template.Templater.getTemplate` :param encoding: (default is "UTF-8") refer to :class:`rtcclient.template.Templater.getTemplate` """ if (not workitems or isinstance(workitems, six.string_types) or isinstance(workitems, int) or isinstance(workitems, float) or not hasattr(workitems, "__iter__")): error_msg = "Input parameter 'workitems' is not iterable" self.log.error(error_msg) raise exception.BadValue(error_msg) if template_names is not None: if not hasattr(template_names, "__iter__"): error_msg = "Input parameter 'template_names' is not iterable" self.log.error(error_msg) raise exception.BadValue(error_msg) if len(workitems) != len(template_names): error_msg = "".join(["Input parameters 'workitems' and ", "'template_names' have different length"]) self.log.error(error_msg) raise exception.BadValue(error_msg) for index, wk_id in enumerate(workitems): try: if template_names is not None: template_name = template_names[index] else: template_name = ".".join([wk_id, "template"]) self.getTemplate(wk_id, template_name=template_name, template_folder=template_folder, keep=keep, encoding=encoding) except Exception as excp: self.log.error("Exception occurred when fetching" "template from <Workitem %s>: %s", str(wk_id), excp) continue self.log.info("Successfully fetch all the templates from " "workitems: %s", workitems)
def runSavedQueryByID(self, saved_query_id, returned_properties=None): """Query workitems using the saved query id This saved query id can be obtained by below two methods: 1. :class:`rtcclient.models.SavedQuery` object (e.g. mysavedquery.id) 2. your saved query url (e.g. https://myrtc:9443/jazz/web/xxx#action=xxxx%id=_mGYe0CWgEeGofp83pg), where the last "_mGYe0CWgEeGofp83pg" is the saved query id. :param saved_query_id: the saved query id :param returned_properties: the returned properties that you want. Refer to :class:`rtcclient.client.RTCClient` for more explanations :return: a :class:`list` that contains the queried :class:`rtcclient.workitem.Workitem` objects :rtype: list """ if not isinstance(saved_query_id, six.string_types) or not saved_query_id: excp_msg = "Please specify a valid saved query id" self.log.error(excp_msg) raise exception.BadValue(excp_msg) return self._runSavedQuery(saved_query_id, returned_properties=returned_properties)
def getItemType(self, title, returned_properties=None): """Get the :class:`rtcclient.models.ItemType` object by the title :param title: the title (e.g. Story/Epic/..) :param returned_properties: the returned properties that you want. Refer to :class:`rtcclient.client.RTCClient` for more explanations :return: the :class:`rtcclient.models.ItemType` object :rtype: rtcclient.models.ItemType """ if not isinstance(title, six.string_types) or not title: excp_msg = "Please specify a valid email address name" self.log.error(excp_msg) raise exception.BadValue(excp_msg) self.log.debug("Try to get <ItemType %s>", title) itemtypes = self._getItemTypes(returned_properties=returned_properties, title=title) if itemtypes is not None: itemtype = itemtypes[0] self.log.info("Get <ItemType %s> in <ProjectArea %s>", itemtype, self) return itemtype excp_msg = "No itemtype's name is %s in <ProjectArea %s>" % (title, self) self.log.error(excp_msg) raise exception.NotFound(excp_msg)
def getAdministrator(self, email, returned_properties=None): """Get the :class:`rtcclient.models.Administrator` object by the email address :param email: the email address (e.g. [email protected]) :param returned_properties: the returned properties that you want. Refer to :class:`rtcclient.client.RTCClient` for more explanations :return: the :class:`rtcclient.models.Administrator` object :rtype: rtcclient.models.Administrator """ if not isinstance(email, six.string_types) or "@" not in email: excp_msg = "Please specify a valid email address name" self.log.error(excp_msg) raise exception.BadValue(excp_msg) self.log.debug("Try to get Administrator whose email is %s", email) rp = returned_properties administrators = self._getAdministrators(returned_properties=rp, email=email) if administrators is not None: administrator = administrators[0] self.log.info("Get <Administrator %s> in <ProjectArea %s>", administrator, self) return administrator msg = "No administrator's email is %s in <ProjectArea %s>" % (email, self) self.log.error(msg) raise exception.NotFound(msg)
def removeSubscribers(self, emails_list): """Remove subscribers from this workitem If the subscribers have not been added, no more actions will be performed. :param emails_list: a :class:`list`/:class:`tuple`/:class:`set` contains the the subscribers' emails """ if not hasattr(emails_list, "__iter__"): error_msg = "Input parameter 'emails_list' is not iterable" self.log.error(error_msg) raise exception.BadValue(error_msg) # overall flag missing_flags = True headers, raw_data = self._perform_subscribe() for email in emails_list: missing_flag, raw_data = self._remove_subscriber(email, raw_data) missing_flags = missing_flags and missing_flag if missing_flags: return self._update_subscribe(headers, raw_data) self.log.info("Successfully remove subscribers: %s for <Workitem %s>", emails_list, self)
def addSubscribers(self, emails_list): """Add subscribers to this workitem If the subscribers have already been added, no more actions will be performed. :param emails_list: a :class:`list`/:class:`tuple`/:class:`set` contains the the subscribers' emails """ if not hasattr(emails_list, "__iter__"): error_msg = "Input parameter 'emails_list' is not iterable" self.log.error(error_msg) raise exception.BadValue(error_msg) # overall flag existed_flags = False headers, raw_data = self._perform_subscribe() for email in emails_list: existed_flag, raw_data = self._add_subscriber(email, raw_data) existed_flags = existed_flags and existed_flag if existed_flags: return self._update_subscribe(headers, raw_data) self.log.info("Successfully add subscribers: %s for <Workitem %s>", emails_list, self)
def addParent(self, parent_id): """Add a parent to current workitem Notice: for a certain workitem, no more than one parent workitem can be added and specified :param parent_id: the parent workitem id/number (integer or equivalent string) """ if isinstance(parent_id, bool): raise exception.BadValue("Please input a valid workitem id") if isinstance(parent_id, six.string_types): parent_id = int(parent_id) if not isinstance(parent_id, int): raise exception.BadValue("Please input a valid workitem id") self.log.debug( "Try to add a parent <Workitem %s> to current " "<Workitem %s>", parent_id, self) headers = copy.deepcopy(self.rtc_obj.headers) headers["Content-Type"] = self.OSLC_CR_JSON req_url = "".join([ self.url, "?oslc_cm.properties=com.ibm.team.workitem.", "linktype.parentworkitem.parent" ]) parent_tag = ("rtc_cm:com.ibm.team.workitem.linktype." "parentworkitem.parent") parent_url = ("{0}/resource/itemName/com.ibm.team." "workitem.WorkItem/{1}".format(self.rtc_obj.url, parent_id)) parent_original = {parent_tag: [{"rdf:resource": parent_url}]} self.put(req_url, verify=False, proxies=self.rtc_obj.proxies, headers=headers, data=json.dumps(parent_original)) self.log.info( "Successfully add a parent <Workitem %s> to current " "<Workitem %s>", parent_id, self)
def _addChild(self, child_id, children_original): child_tag = ("rtc_cm:com.ibm.team.workitem.linktype." "parentworkitem.children") # check data type if isinstance(child_id, bool): raise exception.BadValue("Invalid workitem id: %s", child_id) if isinstance(child_id, six.string_types): child_id = int(child_id) if not isinstance(child_id, int): raise exception.BadValue("Invalid workitem id: %s", child_id) # add child url child_url = ("{0}/resource/itemName/com.ibm.team." "workitem.WorkItem/{1}".format(self.rtc_obj.url, child_id)) new_child = {"rdf:resource": child_url} if new_child not in children_original[child_tag]: children_original[child_tag].append(new_child) else: self.log.debug( "Child <Workitem %s> has already been added to " "current <Workitem %s>. Ignore it.", child_id, self)
def runSavedQueryByUrl(self, saved_query_url, returned_properties=None): """Query workitems using the saved query url :param saved_query_url: the saved query url :param returned_properties: the returned properties that you want. Refer to :class:`rtcclient.client.RTCClient` for more explanations :return: a :class:`list` that contains the queried :class:`rtcclient.workitem.Workitem` objects :rtype: list """ try: if "=" not in saved_query_url: raise exception.BadValue() saved_query_id = saved_query_url.split("=")[-1] if not saved_query_id: raise exception.BadValue() except: error_msg = "No saved query id is found in the url" self.log.error(error_msg) raise exception.BadValue(error_msg) return self._runSavedQuery(saved_query_id, returned_properties=returned_properties)
def render(self, template, **kwargs): """Renders the template :param template: The template to render. The template is actually a file, which is usually generated by :class:`rtcclient.template.Templater.getTemplate` and can also be modified by user accordingly. :param kwargs: The `kwargs` dict is used to fill the template. These two parameter are mandatory: * description * title Some of below parameters (which may not be included in some customized workitem type ) are mandatory if `keep` (parameter in :class:`rtcclient.template.Templater.getTemplate`) is set to `False`; Optional for otherwise. * teamArea (Team Area) * ownedBy (Owned By) * plannedFor(Planned For) * severity(Severity) * priority(Priority) * filedAgainst(Filed Against) Actually all these needed keywords/attributes/fields can be retrieved by :class:`rtcclient.template.Templater.listFields` :return: the :class:`string` object :rtype: string """ if kwargs.get("title", None) is not None: kwargs["title"] = escape(kwargs["title"]) if kwargs.get("description", None) is not None: kwargs["description"] = escape(kwargs["description"]) try: temp = self.environment.get_template(template) return temp.render(**kwargs) except AttributeError: err_msg = "Invalid value for 'template'" self.log.error(err_msg) raise exception.BadValue(err_msg)
def _remove_subscriber(self, email, raw_data): if not isinstance(email, six.string_types) or "@" not in email: excp_msg = "Please specify a valid email address name: %s" % email self.log.error(excp_msg) raise exception.BadValue(excp_msg) missing_flag = True del_sub = self.rtc_obj.getOwnedBy(email) description = raw_data.get("rdf:RDF").get("rdf:Description") subs = description.get("rtc_cm:subscribers", None) if subs is None: # no subscribers self.log.error("No subscribers for <Workitem %s>", self) else: if isinstance(subs, OrderedDict): # only one subscriber exist missing_flag = self._check_missing_subscriber(del_sub, subs) if not missing_flag: description.pop("rtc_cm:subscribers") else: self.log.error( "The subscriber %s has not been " "added. No need to unsubscribe", del_sub.email) else: # a list: several subscribers # check existing for exist_sub in subs: missing_flag = self._check_missing_subscriber( del_sub, exist_sub) if not missing_flag: subs.remove(exist_sub) if len(subs) == 1: # only one existing description["rtc_cm:subscribers"] = subs[0] break else: self.log.error( "The subscriber %s has not been " "added. No need to unsubscribe", del_sub.email) return missing_flag, raw_data
def listFields(self, template): """List all the attributes to be rendered from the template file :param template: The template to render. The template is actually a file, which is usually generated by :class:`rtcclient.template.Templater.getTemplate` and can also be modified by user accordingly. :return: a :class:`set` contains all the needed attributes :rtype: set """ try: temp_source = self.environment.loader.get_source( self.environment, template) return self.listFieldsFromSource(temp_source) except AttributeError: err_msg = "Invalid value for 'template'" self.log.error(err_msg) raise exception.BadValue(err_msg)
def removeChildren(self, child_ids): """Remove children from current workitem :param child_ids: a :class:`list` contains the children workitem id/number (integer or equivalent string) """ if not hasattr(child_ids, "__iter__"): error_msg = "Input parameter 'child_ids' is not iterable" self.log.error(error_msg) raise exception.BadValue(error_msg) self.log.debug( "Try to remove children <Workitem %s> from current " "<Workitem %s>", child_ids, self) self._removeChildren(child_ids) self.log.info( "Successfully remove children <Workitem %s> from " "current <Workitem %s>", child_ids, self)
def _add_subscriber(self, email, raw_data): if not isinstance(email, six.string_types) or "@" not in email: excp_msg = "Please specify a valid email address name: %s" % email self.log.error(excp_msg) raise exception.BadValue(excp_msg) existed_flag = False new_subscriber = self.rtc_obj.getOwnedBy(email) new_sub = OrderedDict() new_sub["@rdf:resource"] = new_subscriber.url description = raw_data.get("rdf:RDF").get("rdf:Description") subs = description.get("rtc_cm:subscribers", None) if subs is None: # no subscribers added_url = "http://jazz.net/xmlns/prod/jazz/rtc/cm/1.0/" raw_data["rdf:RDF"]["@xmlns:rtc_cm"] = added_url description["rtc_cm:subscribers"] = new_sub else: if isinstance(subs, OrderedDict): # only one subscriber exist existed_flag = self._check_exist_subscriber( new_subscriber, subs) if not existed_flag: subs = [subs] subs.append(new_sub) description["rtc_cm:subscribers"] = subs else: # a list: several subscribers # check existing for exist_sub in subs: existed_flag = self._check_exist_subscriber( new_subscriber, exist_sub) if existed_flag: break else: subs.append(new_sub) return existed_flag, raw_data
def getAction(self, action_name): """Get the :class:`rtcclient.models.Action` object by its name :param action_name: the name/title of the action :return: the :class:`rtcclient.models.Action` object :rtype: rtcclient.models.Action """ self.log.debug("Try to get <Action %s>", action_name) if not isinstance(action_name, six.string_types) or not action_name: excp_msg = "Please specify a valid action name" self.log.error(excp_msg) raise exception.BadValue(excp_msg) actions = self._getActions(action_name=action_name) if actions is not None: action = actions[0] self.log.info("Find <Action %s>", action) return action self.log.error("No Action named %s", action_name) raise exception.NotFound("No Action named %s" % action_name)
def getRole(self, label): """Get the :class:`rtcclient.models.Role` object by the label name :param label: the label name of the role :return: the :class:`rtcclient.models.Role` object :rtype: :class:`rtcclient.models.Role` """ if not isinstance(label, six.string_types) or not label: excp_msg = "Please specify a valid role label" self.log.error(excp_msg) raise exception.BadValue(excp_msg) roles = self.getRoles() if roles is not None: for role in roles: if role.label == label: self.log.info("Get <Role %s> in <ProjectArea %s>", role, self) return role excp_msg = "No role's label is %s in <ProjectArea %s>" % (label, self) self.log.error(excp_msg) raise exception.NotFound(excp_msg)
def getTemplate(self, copied_from, template_name=None, template_folder=None, keep=False, encoding="UTF-8"): """Get template from some to-be-copied :class:`rtcclient.workitem.Workitem` The resulting XML document is returned as a :class:`string`, but if `template_name` (a string value) is specified, it is written there instead. :param copied_from: the to-be-copied :class:`rtcclient.workitem.Workitem` id (integer or equivalent string) :param template_name: the template file name :param template_folder: the folder to store template file :param keep: (default is False) If `True`, some of below parameters (which may not be included in some customized :class:`rtcclient.workitem.Workitem` type ) will remain unchangeable with the to-be-copied :class:`rtcclient.workitem.Workitem`. Otherwise for `False`. * teamArea (Team Area) * ownedBy (Owned By) * plannedFor(Planned For) * severity(Severity) * priority(Priority) * filedAgainst(Filed Against) :param encoding: (default is "UTF-8") coding format :return: * a :class:`string` object: if `template_name` is not specified * write the template to file `template_name`: if `template_name` is specified """ try: if isinstance(copied_from, bool) or isinstance(copied_from, float): raise ValueError() if isinstance(copied_from, six.string_types): copied_from = int(copied_from) if not isinstance(copied_from, int): raise ValueError() except ValueError: err_msg = "Please input a valid workitem id you want to copy from" self.log.error(err_msg) raise exception.BadValue(err_msg) self.log.info("Fetch the template from <Workitem %s> with [keep]=%s", copied_from, keep) if template_folder is None: template_folder = self.searchpath # identify whether output to a file if template_name is not None: template_file_path = os.path.join(template_folder, template_name) output = open(template_file_path, "w") else: template_file_path = None output = None workitem_url = "/".join([self.url, "oslc/workitems/%s" % copied_from]) resp = self.get(workitem_url, verify=False, proxies=self.rtc_obj.proxies, headers=self.rtc_obj.headers, cookies=self.rtc_obj.cookiejar) raw_data = xmltodict.parse(resp.content) # pre-adjust the template: # remove some attribute to avoid being overwritten, which will only be # generated when the workitem is created wk_raw_data = raw_data.get("oslc_cm:ChangeRequest") self._remove_long_fields(wk_raw_data) # Be cautious when you want to modify these fields # These fields have been tested as must-removed one remove_fields = [ "@rdf:about", "dc:created", "dc:creator", "dc:identifier", "rtc_cm:contextId", "rtc_cm:comments", "rtc_cm:state", "dc:type", "rtc_cm:subscribers", "dc:modified", "rtc_cm:modifiedBy", "rtc_cm:resolved", "rtc_cm:resolvedBy", "rtc_cm:resolution", "rtc_cm:startDate", "rtc_cm:timeSpent", "rtc_cm:progressTracking", "rtc_cm:projectArea", "oslc_cm:relatedChangeManagement", "oslc_cm:trackedWorkItem", "oslc_cm:tracksWorkItem", "rtc_cm:timeSheet", "oslc_pl:schedule" ] for remove_field in remove_fields: try: wk_raw_data.pop(remove_field) self.log.debug( "Successfully remove field [%s] from the " "template originated from <Workitem %s>", remove_field, copied_from) except: self.log.warning( "No field named [%s] in this template " "from <Workitem %s>", remove_field, copied_from) continue wk_raw_data["dc:description"] = "{{ description }}" wk_raw_data["dc:title"] = "{{ title }}" if keep: if template_file_path: self.log.info("Writing the template to file %s", template_file_path) return xmltodict.unparse(raw_data, output=output, encoding=encoding, pretty=True) replace_fields = [("rtc_cm:teamArea", "{{ teamArea }}"), ("rtc_cm:ownedBy", "{{ ownedBy }}"), ("rtc_cm:plannedFor", "{{ plannedFor }}"), ("rtc_cm:foundIn", "{{ foundIn }}"), ("oslc_cm:severity", "{{ severity }}"), ("oslc_cm:priority", "{{ priority }}"), ("rtc_cm:filedAgainst", "{{ filedAgainst }}")] for field in replace_fields: try: wk_raw_data[field[0]]["@rdf:resource"] = field[1] self.log.debug("Successfully replace field [%s] with [%s]", field[0], field[1]) except: self.log.warning("Cannot replace field [%s]", field[0]) continue if template_file_path: self.log.info("Writing the template to file %s", template_file_path) return xmltodict.unparse(raw_data, output=output, encoding=encoding, pretty=True)