#!/usr/bin/python3 # import work-log entries from elsap csv # avoid multiple logging # TODO: log completely # zisco 2021-10-28 09:33 import os, shutil import logging from types import ClassMethodDescriptorType from jira import JIRA, Issue, Comment, exceptions, JIRAError import re import csv from numpy import single # import xlsxwriter import pandas as pd from pandas.core.dtypes.missing import notnull import json from pandas.core.frame import DataFrame from requests.sessions import codes def process_elsap_file(filename): data = pd.read_excel(filename) df = pd.DataFrame(data, columns=['Mitarbeiter','Datum','Menge.1','Benutzerfeld']) # dtype={'Mitarbeiter': str, 'Menge.1': float, 'Benutzerfeld': str}) df = df.dropna() #filtered_df = df.loc[(df['Datum'] >= '2021-10-01') # & (df['Datum'] < '2021-11-02')] df = df.loc[(df['Datum'] <= '2021-11-07')] logging.info('Consider dates <= {}'.format('2021-11-07')) df['doit'] = True df['new'] = False df['del'] = False df['JIRA'] = '' logging.info("Initial DataFrame size is {} rows".format(len(df.index))) df = single_line_dataframe(df) logging.info("Adapted DataFrame size is {} rows".format(len(df.index))) real = False for index, row in df.iterrows(): # search for corresponding JIRA-issue issue_str = "WRGWR-{}".format(row['JIRA']) print("Working on {}".format(issue_str)) logging.info("Working on {}".format(issue_str)) try: worklogs = jira_server.worklogs(issue_str) except JIRAError as e: logging.error('JIRA-Iyssue {} not found! (status: {} text: {})'.format(issue_str, e.status_code, e.text)) continue # probably work is already logged? for worklog in worklogs: # get corresponding user jira_worklog_user = worklog.author.key elsap_user = user_array[row['Mitarbeiter']] if jira_worklog_user==elsap_user: if not confirm_prompt("User {} has already logged, still add worklog?".format(row['Mitarbeiter'])): df.at[index,'doit'] = False logging.info("No worklog wanted!") issue = jira_server.issue(issue_str) originalEstimate = xsp(issue.fields.timeoriginalestimate) # TODO: Check if original time estimated set? (and, if it set well!) if hasattr(issue.fields, 'customfield_10022'): storypoints = xsp(issue.fields.customfield_10022) if (storypoints/2*28800) != originalEstimate: # adjust if confirm_prompt("Adjust original-estimate with storypoints? (old {}, new {})".format(originalEstimate, storypoints/2*28800)): if real == True: issue.fields.timeoriginalestimate = storypoints/2*28800 # is that needed? issue.update(fields={'timetracking': {'originalEstimate': '{}d'.format(storypoints/2)}}) # print("Old {} new {}".format(storypoints, issue.fields.timeoriginalestimate)) # logging.info("Old estimation {} should be changed to {} days (based on {} storypoints)".format(originalEstimate, storypoints/2, storypoints)) logging.info("Old estimation {} changed to {} days (based on {} storypoints)".format(originalEstimate, storypoints/2, storypoints)) if row['doit'] == True: if confirm_prompt("Add worklog for {}? ".format(issue_str)): if real == True: jira_server.add_worklog(issue_str, timeSpent="{}h".format(row['Menge.1']), user="{} worked on issue {} {} hours at {}".format(row['Mitarbeiter'], issue_str, row['Menge.1'], row['Datum'])) # print(row['Datum'],row['Mitarbeiter'],row['Menge.1'],row['Benutzerfeld'],row['JIRA']) logging.info("{} worked {} hours at {}".format(row['Mitarbeiter'], row['Menge.1'], row['Datum'])) def confirm_prompt(question: str) -> bool: reply = None while reply not in ("", "y", "n"): reply = input(f"{question} (Y/n): ").lower() return (reply in ("", "y")) def extract_jira_issues(comment): if pd.notna(comment): return re.findall(r'WRGWR-\d+', comment) """ # possible values: Refinement (2), 1000 (2,5), Jour Fixe(1) # 819(1),798(2),888(2), Meeting (1.5) commentSplit = comment.split('),') bookedJira = {} singleJira = [] for singleItem in commentSplit: # a valid single item is a pair of two numerical values singleJira = re.findall(r'\d+', singleItem) if len(singleJira) == 2: bookedJira[singleJira[0]] = singleJira[1] """ # return bookedJira def single_line_dataframe(df) -> DataFrame: for index, row in df.iterrows(): issue_number = extract_jira_issues(row['Benutzerfeld']) if issue_number: if len(issue_number) > 1: time = xsp(row['Menge.1'])/len(issue_number) # TODO: Consider possibility of given notation: "758 (2), 759 (1)" df.at[index, 'del'] = True for single_issue in issue_number: # issue_number[1:] new_rows = [] # fix time of first entry, add as much as needed new lines row.loc['Menge.1'] = time row.loc['JIRA'] = single_issue row.loc['new'] = True new_rows.append(row) df = df.append(pd.DataFrame(new_rows, columns=df.columns), ignore_index=True) # .reset_index() else: df.at[index, 'JIRA'] = issue_number else: df.at[index, 'doit'] = False # drop all to-be deleted rows indexNames = df[df['del'] == True].index df.drop(indexNames , inplace=True) # drop all non-doit indexNames = df[df['doit'] == False].index df.drop(indexNames , inplace=True) return df # map elsap-users to jira-users # input_file = open('elsap_to_jira_user.json', encoding="utf-8") userjson = '{"Kratochvil Jakub":"JIRAUSER16603","Zimmerberger Markus":"JIRAUSER17211","Cervenka Raimund":"JIRAUSER14400","Reischitz Ulf":"reu9001","Kremser Peter":"krp9002","Thüringer Helfried":"JIRAUSER15724","Blagojevic Nikolina":"JIRAUSER17212","Unfried Daniel":"und9001","Stefan Ion-Christian":"JIRAUSER16518"}' user_array = json.loads(userjson) # print(type(user_array)) logging.basicConfig(filename='elsap_importer.log', filemode='a', format='%(asctime)s - %(levelname)s: %(message)s', level=logging.INFO, encoding='utf-8') host = "https://jira.wien.gv.at/" pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD" headers = JIRA.DEFAULT_OPTIONS["headers"].copy() headers["Authorization"] = f"Bearer {pat}" jira_server = JIRA(server=host, options={"headers": headers}) logging.info("Connection to JIRA established") xsp = lambda s: s or 0 # open elsap-xlsx with os.scandir(r'C:\Users\zim9003\Documents\reports') as it: for entry in it: if entry.name.endswith(".xlsx") and entry.is_file(): logging.info('Processing file {}'.format(entry.path)) process_elsap_file(entry.path) # move to done logging.info('Moved file to done -> {}'.format(entry.name)) shutil.move(entry.path, r'C:\Users\zim9003\Documents\reports\done\{}'.format(entry.name)) # issues_Jql = jira_server.search_issues('cf[10022] is not EMPTY AND labels not in (Refinement23092021, hotfix0921) AND status not in (Done) and key not in (wrgwr-929)', expand='changelog') # issues_Jql = jira_server.search_issues('status not in (Done)', expand='changelog') """ issues_Jql = jira_server.search_issues('key in (wrgwr-773)', expand='changelog') xsp = lambda s: s or 0 sp_sum = 0 issue: Issue for issue in issues_Jql: logging.info("Working on {}".format(issue.key)) sp = 0 if hasattr(issue.fields, 'customfield_10022'): sp = xsp(issue.fields.customfield_10022) sp_sum += sp # log old values print(sp, sp/8) originalEstimate = xsp(issue.fields.timeoriginalestimate) logging.info("Estimation-system change: Old Story Points {} -> new Story Points {}".format(sp, sp/8)) if (originalEstimate != 0) and (originalEstimate / 14400) != sp: # probably already adapted? Search for activity 'updated the Story Points' doChange = True for history in issue.changelog.histories: if doChange==False: break if (history.author.key=='JIRAUSER17211'): for item in history.items: if (item.field=='Story Points'): doChange = False break if doChange: # update story-points try: issue.update(fields={'customfield_10022': originalEstimate / 14400}) comment = jira_server.add_comment(issue, "Estimation-system change: Old Story Points {} -> new Story Points {}".format(sp, sp/8)) except exceptions.JIRAError: logging.error("Issue {} has no (visible) Story Points-attribute".format(issue.key)) finally: print('Done {}'.format(issue.key)) """