Compare commits

...
10 Commits
13 changed files with 696 additions and 65 deletions
+1
View File
@@ -1 +1,2 @@
*.log *.log
*.xlsx
+17
View File
@@ -0,0 +1,17 @@
from atlassian import Bitbucket
from jira import JIRA
host = "https://bitbucket.wien.gv.at/"
pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD"
# Initialize Bitbucket instance
#TODO Use token for authentication
bitbucket = Bitbucket(
url=host,
username='zim9003', # Assuming you need authentication
password='3S1punk08_114$', # Use token for authentication
)
repos = bitbucket.repo_list('WRGWR')
for repo in repos:
print(repo)
+101
View File
@@ -0,0 +1,101 @@
# make page-header of confluence-area unique (add WGWR2)
# zisco 2023-11-10 06:59
from distutils.log import INFO
from queue import Full
from sqlite3 import Timestamp
from atlassian import Confluence
import logging
import requests
import json
from datetime import date, datetime
# Custom serializer function
def custom_serializer(obj):
if isinstance(obj, datetime):
#'2024-03-28T09:20:00.000+01:00'
#return obj.strftime('%Y-%m-%dT%H:%M:%S')
return obj.strftime('%Y-%m-%dT%H:%M:%S.000+01:00')
raise TypeError(f'Object of type {type(obj)} is not JSON serializable')
logging.basicConfig(filename='confluence blog timestamp change.log',
filemode='a',
format='%(asctime)s - %(levelname)s: %(message)s',
level=logging.WARNING,
encoding='utf-8')
logger = logging.getLogger('myLogger')
logger.setLevel(level=logging.INFO)
confluence_host = "https://confluence.wien.gv.at/"
confluence_pat = "MDI3ODU2NzkzMTAyOsPz47kjJvVdJa1bLpen3W5sB6S5"
s = requests.Session()
s.headers['Authorization'] = f"Bearer {confluence_pat}"
confluence = Confluence(url=confluence_host, session=s)
# Function to retrieve page content by page ID
def get_page_content(page_id):
blog_page = confluence.get_page_by_id(page_id, expand='body.storage')
space = confluence.get_page_by_id(page_id, expand='space')
return blog_page, space['space']
# Function to create a new page with specified creation date
def create_page_with_creation_date(space, title, body, creation_date):
#headers = {'Content-Type': 'application/json'}
data = {
"type": "page",
"title": title,
"body": {
"storage": {
"value": body,
"representation": "storage"
}
},
"metadata": {
"createdDate": creation_date
}
}
status = confluence.create_page(space=space, title=title, body=body, type='blogpost')
return status
# Function to delete a page by page ID
def delete_page(page_id):
response = confluence.remove_page(page_id=page_id)
return response.status_code == 204
"""
def change_blog_timestamp(page_id):
blog_page = confluence.get_page_by_id(page_id, expand=None, status=None, version=None)
#confluence.update_page(page_id=page_id, Timestamp=datetime.now())
data = {
"history": {
"createdDate": datetime.now()
}
}
json_data = json.dumps(data, default=custom_serializer)
confluence.update_page_property(page_id=page_id, data=json_data)
# Get old page contents
page_content = confluence.get_page_by_id(page_id=page_id, expand='body') # Old page contents
#Rename and move page, get updated content
#new_page_content = confluence.update_page(old_page_content['id'], title=new_title, body=old_page_content['body'], parent_id=parent_id, type="page", representation="storage")
confluence.update_page(page_id=page_id, title=page_title) #, body=page_content['body'])
logger.info(f"New page title '{page_title}'") """
# Example usage
page_id = 317036255 # Jenkins-Blog
new_creation_date = datetime.now()
page_content, space = get_page_content(page_id)
if page_content:
title = page_content["title"]
body = page_content["body"]
new_page_id = create_page_with_creation_date(space['key'], title, body, new_creation_date)
delete_page(page_id)
else:
print("Page not found.")
#change_blog_timestamp(page_id=page_id)
+58
View File
@@ -0,0 +1,58 @@
# make page-header of confluence-area unique (add WGWR2)
# zisco 2023-11-10 06:59
from distutils.log import INFO
from atlassian import Confluence
import logging
import requests
logging.basicConfig(filename='confluence bulk.log',
filemode='a',
format='%(asctime)s - %(levelname)s: %(message)s',
level=logging.WARNING,
encoding='utf-8')
logger = logging.getLogger('myLogger')
logger.setLevel(level=logging.INFO)
confluence_host = "https://confluence.wien.gv.at/"
confluence_pat = "MDI3ODU2NzkzMTAyOsPz47kjJvVdJa1bLpen3W5sB6S5"
s = requests.Session()
s.headers['Authorization'] = f"Bearer {confluence_pat}"
confluence = Confluence(url=confluence_host, session=s)
def recurse_child_pages(page_id, level=0):
level += 1
child_pages = confluence.get_page_child_by_type(page_id, type='page', start=None, limit=None, expand=None)
for child_page in child_pages:
page_title = child_page["title"]
st = "\t" * level
logger.info(f"{st}Page {page_title}")
if page_title.find('[WGWR3]') != -1:
rename_page_title(child_page["id"], child_page["title"])
recurse_child_pages(child_page["id"], level=level)
def rename_page_title(page_id, page_title):
#append 'WGWR2'
#Confluence.update_page(page_id, title='WGWR2_' , body, parent_id=None, type='page', representation='storage', minor_edit=False, full_width=False)
page_title = page_title.replace('[WGWR3]','WGWR3_')
# Get old page contents
page_content = confluence.get_page_by_id(page_id=page_id, expand='body') # Old page contents
#Rename and move page, get updated content
#new_page_content = confluence.update_page(old_page_content['id'], title=new_title, body=old_page_content['body'], parent_id=parent_id, type="page", representation="storage")
confluence.update_page(page_id=page_id, title=page_title) #, body=page_content['body'])
logger.info(f"New page title '{page_title}'")
# get main page
#Confluence.get_page_by_id(page_id, expand=None, status=None, version=None)
#page = Confluence.get_page_child_by_type('Wiener Gebäude- und Wohnungsregister', 'WGWR2')
#page_id = 118856417 # WGWR2
page_id = 280660965 # WGWR3
recurse_child_pages(page_id=page_id)
#body = page["body"]["storage"]["value"]
+101
View File
@@ -0,0 +1,101 @@
# use planning-excel to actualize MADD-Teamspace
# zisco 2024-06-28 09:57
from distutils.log import INFO
from queue import Full
from sqlite3 import Timestamp
from atlassian import Confluence
import logging
import requests
import json
from datetime import date, datetime
# Custom serializer function
def custom_serializer(obj):
if isinstance(obj, datetime):
#'2024-03-28T09:20:00.000+01:00'
#return obj.strftime('%Y-%m-%dT%H:%M:%S')
return obj.strftime('%Y-%m-%dT%H:%M:%S.000+01:00')
raise TypeError(f'Object of type {type(obj)} is not JSON serializable')
logging.basicConfig(filename='confluence blog timestamp change.log',
filemode='a',
format='%(asctime)s - %(levelname)s: %(message)s',
level=logging.WARNING,
encoding='utf-8')
logger = logging.getLogger('myLogger')
logger.setLevel(level=logging.INFO)
confluence_host = "https://confluence.wien.gv.at/"
confluence_pat = "MDI3ODU2NzkzMTAyOsPz47kjJvVdJa1bLpen3W5sB6S5"
s = requests.Session()
s.headers['Authorization'] = f"Bearer {confluence_pat}"
confluence = Confluence(url=confluence_host, session=s)
# Function to retrieve page content by page ID
def get_page_content(page_id):
blog_page = confluence.get_page_by_id(page_id, expand='body.storage')
space = confluence.get_page_by_id(page_id, expand='space')
return blog_page, space['space']
# Function to create a new page with specified creation date
def create_page_with_creation_date(space, title, body, creation_date):
#headers = {'Content-Type': 'application/json'}
data = {
"type": "page",
"title": title,
"body": {
"storage": {
"value": body,
"representation": "storage"
}
},
"metadata": {
"createdDate": creation_date
}
}
status = confluence.create_page(space=space, title=title, body=body, type='blogpost')
return status
# Function to delete a page by page ID
def delete_page(page_id):
response = confluence.remove_page(page_id=page_id)
return response.status_code == 204
"""
def change_blog_timestamp(page_id):
blog_page = confluence.get_page_by_id(page_id, expand=None, status=None, version=None)
#confluence.update_page(page_id=page_id, Timestamp=datetime.now())
data = {
"history": {
"createdDate": datetime.now()
}
}
json_data = json.dumps(data, default=custom_serializer)
confluence.update_page_property(page_id=page_id, data=json_data)
# Get old page contents
page_content = confluence.get_page_by_id(page_id=page_id, expand='body') # Old page contents
#Rename and move page, get updated content
#new_page_content = confluence.update_page(old_page_content['id'], title=new_title, body=old_page_content['body'], parent_id=parent_id, type="page", representation="storage")
confluence.update_page(page_id=page_id, title=page_title) #, body=page_content['body'])
logger.info(f"New page title '{page_title}'") """
# Example usage
page_id = 317036255 # Jenkins-Blog
new_creation_date = datetime.now()
page_content, space = get_page_content(page_id)
if page_content:
title = page_content["title"]
body = page_content["body"]
new_page_id = create_page_with_creation_date(space['key'], title, body, new_creation_date)
delete_page(page_id)
else:
print("Page not found.")
#change_blog_timestamp(page_id=page_id)
+34 -7
View File
@@ -1,6 +1,7 @@
# find comments with estimations out of pre-planning # find comments with estimations out of pre-planning
# zisco 2021-09-30 19:50 # zisco 2021-09-30 19:50
# TODO: Excel export
from types import ClassMethodDescriptorType from types import ClassMethodDescriptorType
from jira import JIRA, Issue, Comment, exceptions from jira import JIRA, Issue, Comment, exceptions
import re import re
@@ -16,15 +17,31 @@ logging.basicConfig(filename='sprint efforts.log',
host = "https://jira.wien.gv.at/" host = "https://jira.wien.gv.at/"
pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD" pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD"
sprint = 9 sprint = 1
project = 'WRWGR3'
row = 0; col = 0
workbook = xlsxwriter.Workbook(f'efforts {project} sprint {sprint} (from comment).xlsx')
worksheet = workbook.add_worksheet('efforts')
cell_format = workbook.add_format()
cell_format.set_bold()
worksheet.write(row, col, 'Story' , cell_format)
worksheet.write(row, col + 1, 'Sub-Task' , cell_format)
worksheet.write(row, col + 2, 'Summary' , cell_format)
worksheet.write(row, col + 3, 'timeoriginalestimate', cell_format)
worksheet.write(row, col + 4, 'SP' , cell_format)
worksheet.write(row, col + 5, 'PT' , cell_format)
row += 1
headers = JIRA.DEFAULT_OPTIONS["headers"].copy() headers = JIRA.DEFAULT_OPTIONS["headers"].copy()
headers["Authorization"] = f"Bearer {pat}" headers["Authorization"] = f"Bearer {pat}"
jira_server = JIRA(server=host, options={"headers": headers}) jira_server = JIRA(server=host, options={"headers": headers})
# use filter or single issue # use filter or single issue
issues_Jql = jira_server.search_issues('filter=24707') #, expand='changelog') #issues_Jql = jira_server.search_issues('filter=25827') #, expand='changelog')
#issues_Jql = jira_server.search_issues('key in (DKARB-140)') #, expand='changelog') issues_Jql = jira_server.search_issues('key in (WRGWR-2838, WRGWR-2839)') #, expand='changelog')
#issues_Jql = jira_server.search_issues('labels in (Refinement11012022) AND type not in (Bug) AND status not in (Done) and ("Story Points" is EMPTY or "Story Points" = 0)') #, expand='changelog') #issues_Jql = jira_server.search_issues('labels in (Refinement11012022) AND type not in (Bug) AND status not in (Done) and ("Story Points" is EMPTY or "Story Points" = 0)') #, expand='changelog')
logging.info('Sum effort stored in issue-comments of pre-planning sprint {}'.format(sprint)) logging.info('Sum effort stored in issue-comments of pre-planning sprint {}'.format(sprint))
@@ -60,18 +77,25 @@ for issue in issues_Jql:
if (comment_line_parts[1][-2:] != 'NN') and (comment_line_parts[1][-1:] != '-'): # NN means NotNecessary if (comment_line_parts[1][-2:] != 'NN') and (comment_line_parts[1][-1:] != '-'): # NN means NotNecessary
comment_line_sub_parts = comment_line_parts[1].split(' (') comment_line_sub_parts = comment_line_parts[1].split(' (')
if len(comment_line_sub_parts)>1: if len(comment_line_sub_parts)>1:
if comment_line_sub_parts[0].isnumeric(): if comment_line_sub_parts[0].strip().isnumeric():
subtask_ph = float(comment_line_sub_parts[0]) * 0.5 subtask_ph = float(comment_line_sub_parts[0]) * 0.5
estimation_remark = comment_line_sub_parts[1] estimation_remark = comment_line_sub_parts[1]
else: else:
subtask_ph = 0 subtask_ph = 0
else: else:
if comment_line_sub_parts[1].isnumeric(): if comment_line_sub_parts[0].strip().isnumeric():
subtask_ph = float(comment_line_parts[1]) * 0.5 subtask_ph = float(comment_line_sub_parts[0]) * 0.5
estimation_remark = "" estimation_remark = ""
else: else:
subtask_ph = 0 subtask_ph = 0
#logging.info(f"Effort {comment_line_sub_parts[0]}")
worksheet.write(row, col, issue.key)
worksheet.write(row, col + 1, 'na')
worksheet.write(row, col + 2, comment_line_parts[0])
worksheet.write(row, col + 3, 'na')
worksheet.write(row, col + 4, subtask_ph)
worksheet.write(row, col + 5, f'=E{row + 1}/0.5')
row += 1
sp_sum += subtask_ph / 0.5 sp_sum += subtask_ph / 0.5
try: try:
if sp != sp_sum: if sp != sp_sum:
@@ -79,3 +103,6 @@ for issue in issues_Jql:
except exceptions.JIRAError: except exceptions.JIRAError:
logging.error("Issue {} has no (visible) Story Points-attribute".format(issue.key)) logging.error("Issue {} has no (visible) Story Points-attribute".format(issue.key))
worksheet.autofit()
workbook.close()
+73
View File
@@ -0,0 +1,73 @@
# find comments with estimations out of pre-planning
# zisco 2021-09-30 19:50
from types import ClassMethodDescriptorType
from jira import JIRA, Issue, Comment, exceptions
from datetime import date
import re
import csv
import xlsxwriter
import logging
logging.basicConfig(filename='sprint efforts.log',
filemode='a',
format='%(asctime)s - %(levelname)s: %(message)s',
level=logging.INFO,
encoding='utf-8')
host = "https://jira.wien.gv.at/"
pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD"
#sprint = 2
row = 0; col = 0
workbook = xlsxwriter.Workbook(f'efforts as of preplanning {date.today()}.xlsx')
worksheet = workbook.add_worksheet('efforts')
cell_format = workbook.add_format()
cell_format.set_bold()
worksheet.write(row, col, 'Story' , cell_format)
worksheet.write(row, col + 1, 'Sub-Task' , cell_format)
worksheet.write(row, col + 2, 'Summary' , cell_format)
worksheet.write(row, col + 3, 'Labels' , cell_format)
worksheet.write(row, col + 4, 'timeoriginalestimate', cell_format)
worksheet.write(row, col + 5, 'SP' , cell_format)
worksheet.write(row, col + 6, 'PT' , cell_format)
row += 1
headers = JIRA.DEFAULT_OPTIONS["headers"].copy()
headers["Authorization"] = f"Bearer {pat}"
jira_server = JIRA(server=host, options={"headers": headers})
# use filter or single issue
issues_Jql = jira_server.search_issues('filter=28412') #, expand='changelog')
#issues_Jql = jira_server.search_issues('key in (wrgwr-2838, wrgwr-2839)', expand='changelog')
#issues_Jql = jira_server.search_issues('key in (DKARB-140)') #, expand='changelog')
#issues_Jql = jira_server.search_issues('labels in (Refinement11012022) AND type not in (Bug) AND status not in (Done) and ("Story Points" is EMPTY or "Story Points" = 0)') #, expand='changelog')
logging.info(f'Sum effort stored in issue-comments of pre-planning of {date.today()}')
xsp = lambda s: s or 0
issue: Issue
for issue in issues_Jql:
single_issue = jira_server.issue(issue.key)
logging.info(f'OriginalTimeEstimate: {single_issue.fields.timeoriginalestimate}')
for subtask in issue.fields.subtasks:
ssubtask = jira_server.issue(subtask.key) # this is essential to access 'timeoriginalestimate'
worksheet.write(row, col, issue.key)
worksheet.write(row, col + 1, ssubtask.key)
worksheet.write(row, col + 2, ssubtask.fields.summary)
alllabels = []
labels = ssubtask.fields.labels
for i in labels:
alllabels.append(i)
worksheet.write(row, col + 3, ','.join(alllabels, ))
worksheet.write(row, col + 4, ssubtask.fields.timeoriginalestimate)
worksheet.write(row, col + 5, f'=E{row + 1}/28800')
worksheet.write(row, col + 6, f'=F{row + 1}/0.5')
row += 1
worksheet.autofit()
workbook.close()
+69
View File
@@ -0,0 +1,69 @@
# find comments with estimations out of pre-planning
# zisco 2021-09-30 19:50
# TODO: Consider later created sub-tasks
from multiprocessing.sharedctypes import Value
from types import ClassMethodDescriptorType
from jira import JIRA, Issue, Comment, exceptions
import re
import csv
import xlsxwriter
import logging
import yaml
from bisect import bisect_left
from datetime import date
logging.basicConfig(filename='sub_task_generator.log',
filemode='a',
format='%(asctime)s - %(levelname)s: %(message)s',
level=logging.INFO,
encoding='utf-8')
with open("config.yaml", encoding='utf8') as f:
cfg = yaml.load(f, Loader=yaml.FullLoader)
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})
# use filter or single issue
issues_Jql = jira_server.search_issues('filter=31528') #, expand='changelog')
logging.info(f"Modify 'DoD SA' sub-tasks with link to Dev sub-tasks")
issue: Issue
for issue in issues_Jql:
logging.info('Working on {}'.format(issue))
# else:
# single_issue = jira_server.issue(issue.key)
# find corresponding Dev sub-tasks
#escaped = issue.fields.summary.replace('"', r'\"')
#escaped = escaped.replace('[DoD SA]', '').strip()
escaped = issue.fields.summary.replace('[DoD SA]', '').strip()
logging.info(f'summary ~ \"{escaped}\"')
description = issue.fields.description
links_section = '\r\n||Dev-Links||Typ||'
position = description.find(links_section)
if position != -1:
# clear list
description = description[:position + len(links_section)].strip()
else:
description += links_section
for type in ['Backend','Frontend']:
subtask = jira_server.search_issues(f'summary ~ \"Dev {type} {escaped}\"')
if subtask:
# adapt DoD SA-desc
description += "\r\n|" + subtask[0].key + f'|{type}|'
try:
issue.update(fields={'description': description})
except Exception as e:
logging.info(e)
logging.info(f'Link to {type} for {issue} created')
+110 -26
View File
@@ -1,13 +1,16 @@
# find comments with estimations out of pre-planning # find comments with estimations out of pre-planning
# zisco 2021-09-30 19:50 # zisco 2021-09-30 19:50
from multiprocessing.sharedctypes import Value
from types import ClassMethodDescriptorType from types import ClassMethodDescriptorType
from jira import JIRA, Issue, Comment, exceptions from jira import JIRA, Issue, Comment, exceptions
import re import re
import csv import csv
import xlsxwriter import xlsxwriter
import logging import logging
import yaml
from bisect import bisect_left from bisect import bisect_left
from datetime import date
def take_closest(myList, myNumber): def take_closest(myList, myNumber):
""" """
@@ -28,34 +31,63 @@ def take_closest(myList, myNumber):
return before return before
def get_assignee(type, issue): def get_assignee(type, issue):
isBuergerfrontend = False # isBuergerfrontend = False
if type == 'Dev Backend': if (type == 'Dev Backend' or type.startswith('Dev Unit')):
return 'reu9001' # use assignee of story
return cfg['def_assignees']['backend'] #'rab9001'
#return issue.fields.assignee.name
elif type == 'Dev Frontend': elif type == 'Dev Frontend':
for component in issue.fields.components: # for component in issue.fields.components:
isBuergerfrontend = component.name == "Bürgerfrontend" # isBuergerfrontend = component.name == "Bürgerfrontend"
if isBuergerfrontend: # if isBuergerfrontend:
return 'reu9001' return cfg['def_assignees']['frontend'] #'thh9001'
return 'thh9001' elif type == 'Dev Citizen-Frontend':
elif type.startswith('Test-Anpassung'): return cfg['def_assignees']['cit_frontend'] #'reu9001'
return 'osm9005' elif type.startswith('Test-Anpassung') or type == 'DoD Test':
if issue.key.startswith('WRGWR-'):
return cfg['def_assignees']['wgwr_test'] #'ada9001'
elif issue.key.startswith('BAUWB-'):
return cfg['def_assignees']['bauwb_test'] #'osm9005'
elif issue.key.startswith('DKARB-'):
return cfg['def_assignees']['far_test'] #'osm9005'
else:
return cfg['def_assignees']['default'] #'zim9003'
elif type == 'DoD PO':
return cfg['def_assignees']['po'] #'kom9010'
elif type == 'DoD SA':
return cfg['def_assignees']['sa'] #'krp9002'
def create_subtask(issue, parent, type, subtask_sp, estimation_remark): def create_subtask(issue, parent, type, subtask_sp=0, estimation_remark="", no_unit_test=False):
escaped = issue.fields.summary.replace('"', r'\"') escaped = issue.fields.summary.replace('"', r'\"')
logging.info(f'summary ~ \"{escaped}\"') logging.info(f'summary ~ \"{escaped}\"')
assignee = get_assignee(type, issue)
description = get_description(type, no_unit_test=no_unit_test)
try: try:
subtask = jira_server.search_issues(f'summary ~ \"{type} {escaped}\"') project = parent.split('-')[0]
subtask = jira_server.search_issues(f'summary ~ \"{type} {escaped}\" AND project = {project}') # add the project
if subtask: if subtask:
single_issue = jira_server.issue(subtask[0].key)
if single_issue.fields.summary == f'[{type}] {escaped}':
raise exceptions.JIRAError raise exceptions.JIRAError
except exceptions.JIRAError: except exceptions.JIRAError:
# correct estimation
#subtask.fields('timetracking': {'originalEstimate'}).Value() = subtask_sp
#single_issue = jira_server.issue(subtask[0].key)
try:
if cfg['update_desc_only']:
single_issue.update(fields={'description': description})
else:
single_issue.update(fields={'timetracking': {'originalEstimate': '{}d'.format(subtask_sp)}, 'description': description, 'assignee': {'name': assignee}})
except Exception as e:
logging.info(e)
logging.info(f'Skip creating Sub-Task {type} for {issue} as it already exists') logging.info(f'Skip creating Sub-Task {type} for {issue} as it already exists')
return return
issue_type = 'Sub-task' issue_type = 'Sub-task'
assignee = get_assignee(type, issue)
description = 's. Story'
if estimation_remark != "": if estimation_remark != "":
description += "\nEstimation remark: {}".format(estimation_remark) description += "\nEstimation remark: {}".format(estimation_remark)
@@ -82,26 +114,50 @@ def create_subtask(issue, parent, type, subtask_sp, estimation_remark):
logging.info('Created Sub-Task {}'.format(new_issue)) logging.info('Created Sub-Task {}'.format(new_issue))
def get_description(type, no_unit_test=False):
if (type == 'Dev Backend'):
description = cfg['templates']['subtask_desc_be_dev']
if no_unit_test==False:
description += cfg['templates']['add_unit_test_desc']
elif (type in ('Dev Frontend', 'Dev Citizen-Frontend')):
description = cfg['templates']['subtask_desc_fe_dev']
elif (type == 'DoD SA'):
description = cfg['templates']['subtask_desc_sa']
#TODO: Add links to DEV-SubTasks
elif (type == 'Test-Anpassung (TOSCA/xRay)'):
#TODO: Use desc of story
description = 's. Story'
#description = cfg['templates']['subtask_desc_test']
elif (type == 'DoD PO'):
description = cfg['templates']['subtask_desc_po']
else:
description = 's. Story'
return description
logging.basicConfig(filename='sub_task_generator.log', logging.basicConfig(filename='sub_task_generator.log',
filemode='a', filemode='a',
format='%(asctime)s - %(levelname)s: %(message)s', format='%(asctime)s - %(levelname)s: %(message)s',
level=logging.INFO, level=logging.INFO,
encoding='utf-8') encoding='utf-8')
with open("config.yaml", encoding='utf8') as f:
cfg = yaml.load(f, Loader=yaml.FullLoader)
host = "https://jira.wien.gv.at/" host = "https://jira.wien.gv.at/"
pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD" pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD"
sprint = 9 sprint = 5
headers = JIRA.DEFAULT_OPTIONS["headers"].copy() headers = JIRA.DEFAULT_OPTIONS["headers"].copy()
headers["Authorization"] = f"Bearer {pat}" headers["Authorization"] = f"Bearer {pat}"
jira_server = JIRA(server=host, options={"headers": headers}) jira_server = JIRA(server=host, options={"headers": headers})
# use filter or single issue # use filter or single issue
issues_Jql = jira_server.search_issues('filter=24707') #, expand='changelog') issues_Jql = jira_server.search_issues('filter=25827') #, expand='changelog')
#issues_Jql = jira_server.search_issues('key in (DKARB-140)') #, expand='changelog') #issues_Jql = jira_server.search_issues('filter=30617') #, expand='changelog')
issues_Jql = jira_server.search_issues('key in (BAUWB-600)') #, expand='changelog')
#issues_Jql = jira_server.search_issues('labels in (Refinement11012022) AND type not in (Bug) AND status not in (Done) and ("Story Points" is EMPTY or "Story Points" = 0)') #, expand='changelog') #issues_Jql = jira_server.search_issues('labels in (Refinement11012022) AND type not in (Bug) AND status not in (Done) and ("Story Points" is EMPTY or "Story Points" = 0)') #, expand='changelog')
logging.info('Creating sub-tasks out of estimation of pre-planning sprint {}'.format(sprint)) logging.info(f'Creating sub-tasks out of estimation of pre-planning of {date.today()}')
xsp = lambda s: s or 0 xsp = lambda s: s or 0
issue: Issue issue: Issue
@@ -111,12 +167,13 @@ for issue in issues_Jql:
# only Issues with empty (or 0) story-points # only Issues with empty (or 0) story-points
sp = 0 sp = 0
no_unit_test = False
if hasattr(issue.fields, 'customfield_10022'): if hasattr(issue.fields, 'customfield_10022'):
sp = xsp(issue.fields.customfield_10022) sp = xsp(issue.fields.customfield_10022)
if sp != 0: # if sp == 0:
logging.info('Ignoring issue {} as there are Story-Points already omitted'.format(issue)) # logging.info('Ignoring issue {} as there are Story-Points already omitted'.format(issue))
else: # else:
single_issue = jira_server.issue(issue.key) single_issue = jira_server.issue(issue.key)
comments = single_issue.fields.comment.comments comments = single_issue.fields.comment.comments
@@ -124,8 +181,11 @@ for issue in issues_Jql:
for comment in comments: for comment in comments:
comment_lines = comment.body.splitlines() comment_lines = comment.body.splitlines()
if len(comment_lines) == 5: if len(comment_lines) in (5, 9):
if comment_lines[0][:5] == "Dev B": if comment_lines[0][:5] == "Dev B":
if "Dev Unit-Tests: NN" in comment_lines:
no_unit_test = True
for comment_line in comment_lines: for comment_line in comment_lines:
if comment_line != "": if comment_line != "":
# pos = comment_lines[2].find('NN') # pos = comment_lines[2].find('NN')
@@ -134,9 +194,14 @@ for issue in issues_Jql:
# if 1. Test* != NN create a sub-task (if not existing) # if 1. Test* != NN create a sub-task (if not existing)
if len(comment_line_parts) == 2: if len(comment_line_parts) == 2:
if comment_line_parts[1] != u'\xa0': # empty string if comment_line_parts[1] != u'\xa0': # empty string
if (comment_line_parts[1][-2:] != 'NN') and (comment_line_parts[1][-1:] != '-'): # NN means NotNecessary if (comment_line_parts[1][-2:] != 'NN'): # NN means NotNecessary, "-"" means we don't know yet (but subtask will be created)
if (comment_line_parts[1][-1:] == '-'):
subtask_ph = 0
estimation_remark = "(not estimated yet)"
else:
comment_line_sub_parts = comment_line_parts[1].split(' (') comment_line_sub_parts = comment_line_parts[1].split(' (')
if len(comment_line_sub_parts)>1: if len(comment_line_sub_parts)>1:
if comment_line_sub_parts[0].strip().isnumeric():
subtask_ph = float(comment_line_sub_parts[0]) * 0.5 subtask_ph = float(comment_line_sub_parts[0]) * 0.5
estimation_remark = comment_line_sub_parts[1] estimation_remark = comment_line_sub_parts[1]
else: else:
@@ -144,21 +209,25 @@ for issue in issues_Jql:
subtask_ph = float(comment_line_sub_parts[0]) * 0.5 subtask_ph = float(comment_line_sub_parts[0]) * 0.5
estimation_remark = "" estimation_remark = ""
else: else:
if comment_line_sub_parts[0].strip() == "tbd": if comment_line_sub_parts[0].strip().lower() == "tbd":
subtask_ph = take_closest([1,2,3,5,8,13,21,42], sp_sum/3) subtask_ph = take_closest([1,2,3,5,8,13,21,42], sp_sum/3)
if comment_line_parts[0][:15] != "Test-Ausführung": #if comment_line_parts[0][:15] != "Test-Ausführung":
create_subtask(issue, create_subtask(issue,
issue.key, issue.key,
comment_line_parts[0], comment_line_parts[0],
subtask_ph, subtask_ph,
estimation_remark) estimation_remark,
no_unit_test=no_unit_test)
sp_sum += subtask_ph / 0.5 sp_sum += subtask_ph / 0.5
else:
logging.info(f'Sub-task creation skipped {comment_line_parts[0]} equals \'NN\'')
# find corresponding sub-task # find corresponding sub-task
# issuelinks.inwardIssue # issuelinks.inwardIssue
try: try:
if sp != sp_sum: if sp != sp_sum:
if cfg['update_sp_sum']:
issue.update(fields={'customfield_10022': sp_sum}) issue.update(fields={'customfield_10022': sp_sum})
logging.info("Estimated story-points changed to Sum(Sub-Tasks story-points): {}".format(sp_sum, sp_sum)) logging.info("Estimated story-points changed to Sum(Sub-Tasks story-points): {}".format(sp_sum, sp_sum))
@@ -168,6 +237,21 @@ for issue in issues_Jql:
finally: finally:
logging.info('Done {}'.format(issue.key)) logging.info('Done {}'.format(issue.key))
#create standard sub-tasks
if cfg['separate_dod_subtasks']:
for type in ['DoD SA','DoD PO']:
create_subtask(issue,
issue.key,
type,
no_unit_test=no_unit_test
)
#add or remove label #Unit-Test on story-level
if no_unit_test == False:
#issue.remove_field_value("labels", u'unit_test')
issue.add_field_value("labels", u'unit_test')
#issue.update(fields={"labels": issue.fields.labels})
#print('{}: {}, SP: {}'.format(issue.key, issue.fields.summary, sp)) #print('{}: {}, SP: {}'.format(issue.key, issue.fields.summary, sp))
#print('Summe {}: {}'.format(assignee, sp_sum)) #print('Summe {}: {}'.format(assignee, sp_sum))
+76
View File
@@ -0,0 +1,76 @@
from atlassian import Bitbucket
from jira import JIRA
host = "https://bitbucket.wien.gv.at/"
pat = "OTAyMTM3MjY0MjE0Ovz6bu0ti1TlEdSegmmiWiJ2wGeD"
# Initialize Bitbucket instance
#TODO Use token for authentication
bitbucket = Bitbucket(
url=host,
username='zim9003', # Assuming you need authentication
password='<PWD>', # Use token for authentication
)
# Define repository information
project_key = 'WRGWR' # Bitbucket project key
repository_slug = 'WGWR' # Repository slug
file_path = 'Application/MagWien.Wgwr.Repository/Scripts/10_WGWR_01.04_xxxxxx.01_UPDATES.txt' # Path to the specific file within the repo
# Function to fetch all commits that touched a specific file
def get_file_commits(bitbucket, project_key, repository_slug, file_path):
commits = []
start = 0
limit = 100 # Number of commits to retrieve per API call
hash_newest = 'refs/heads/master'
while True:
# Fetch a list of commits with pagination
response = bitbucket.get_commits(
project_key=project_key,
repository_slug=repository_slug,
limit=limit,
hash_newest=hash_newest
)
print(type(response))
# Check if the response is empty or an error occurred
# if not response or 'values' not in response:
# break
# Filter commits that modified the specific file
for cnt in range(0, limit):
commit = list(response)[cnt]
commit_id = commit['id']
# Get detailed info about each commit
commit_details = bitbucket.get_commit_changes(
project_key=project_key,
repository_slug=repository_slug,
commit_id=commit_id
)
# Check if the file path is in the list of modified files
if any(file['path']['toString'] == file_path for file in commit_details['files']):
commit.append({
'commit_id': commit_id,
'message': commit['message'],
'author': commit['author'],
'date': commit['authorTimestamp']
})
# Check if we've retrieved all commits
if len(response['values']) < limit:
break
start += limit
return commits
# Get and print commits affecting the specified file
commits = get_file_commits(bitbucket, project_key, repository_slug, file_path)
for commit in commits:
print(f"Commit ID: {commit['commit_id']}")
print(f"Message: {commit['message']}")
print(f"Author: {commit['author']}")
print(f"Date: {commit['date']}")
print("--------")
+24
View File
@@ -0,0 +1,24 @@
{maintainer: 'Markus Zimmerberger',
templates: {
subtask_desc_be_dev: "||Definition of Done||Verantwortung||\r\n|Code eingechecked und steht unter Versionsverwaltung|DEV|\r\n|Der Code ist vollständig implementiert|DEV|\r\n|Deployment Scripts fertig (wo notwendig, Story mit Label versehen)|DEV|\r\n|Die Coding Standards _(Confluence Link einfügen)_ und die internen Konventionen _(Verweis)_ wurden eingehalten. (optional)|DEV|\r\n|Alle Akzeptanzkriterien werden erfüllt|DEV|\r\n|*Dokumentationen sind aktualisiert*|DEV|",
subtask_desc_fe_dev: "||Definition of Done||Verantwortung||\r\n|Code eingechecked und steht unter Versionsverwaltung|DEV|\r\n|Der Code ist vollständig implementiert|DEV|\r\n|Die Coding Standards _(Confluence Link einfügen)_ und die internen Konventionen _(Verweis)_ wurden eingehalten. (optional)|DEV|\r\n|Alle Akzeptanzkriterien werden erfüllt|DEV|\r\n|*Dokumentationen sind aktualisiert*|DEV|",
subtask_desc_sa: "||Definition of Done||Verantwortung||\r\n|Code reviewed|SA|\r\n|Die Coding Standards _(Confluence Link einfügen)_ und die internen Konventionen _(Verweis)_ wurden eingehalten. (optional)|SA|\r\n|*Dokumentationen sind aktualisiert*|SA|",
subtask_desc_test: "||Definition of Done||Verantwortung||\r\n|Tests laut Testkonzept abgeschlossen|TE|\r\n|Deployed auf Testumgebungen|TE|\r\n|*Dokumentationen sind aktualisiert*|TE|",
subtask_desc_po: "||Definition of Done||Verantwortung||\r\n|Im PO-Review akzeptiert|PO|",
add_unit_test_desc: "\r\n|Die Unit-Tests wurden durchgeführt und bestanden (wo notwendig, Story mit Label versehen)|DEV|"},
separate_dod_subtasks: True,
link_dev_tasks_to_sa: True,
update_sp_sum: True,
update_desc_only: True,
def_assignees: {
backend: 'ila9001', #'rab9001',
frontend: 'ban9002', #'thh9001',
cit_frontend: 'reu9001',
wgwr_test: 'ada9001',
bauwb_test: 'osm9005',
far_test: 'osm9005',
po: 'kom9010',
sa: 'krp9002',
default: 'zim9003'
}
}
Binary file not shown.
Binary file not shown.