Compare commits
10
Commits
ba41b9c5ef
...
fdd87834ff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdd87834ff | ||
|
|
eb35817a8d | ||
|
|
f8cdc8deda | ||
|
|
e4c03dad6b | ||
|
|
2547a38b02 | ||
|
|
7ac6594292 | ||
|
|
e5189f0d8c | ||
|
|
f4d413086b | ||
|
|
902fbec9a0 | ||
|
|
8f00082431 |
@@ -1 +1,2 @@
|
||||
*.log
|
||||
*.xlsx
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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"]
|
||||
@@ -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)
|
||||
@@ -1,6 +1,7 @@
|
||||
# find comments with estimations out of pre-planning
|
||||
# zisco 2021-09-30 19:50
|
||||
|
||||
# TODO: Excel export
|
||||
from types import ClassMethodDescriptorType
|
||||
from jira import JIRA, Issue, Comment, exceptions
|
||||
import re
|
||||
@@ -16,15 +17,31 @@ logging.basicConfig(filename='sprint efforts.log',
|
||||
|
||||
host = "https://jira.wien.gv.at/"
|
||||
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["Authorization"] = f"Bearer {pat}"
|
||||
jira_server = JIRA(server=host, options={"headers": headers})
|
||||
|
||||
# use filter or single issue
|
||||
issues_Jql = jira_server.search_issues('filter=24707') #, expand='changelog')
|
||||
#issues_Jql = jira_server.search_issues('key in (DKARB-140)') #, expand='changelog')
|
||||
#issues_Jql = jira_server.search_issues('filter=25827') #, 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')
|
||||
|
||||
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
|
||||
comment_line_sub_parts = comment_line_parts[1].split(' (')
|
||||
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
|
||||
estimation_remark = comment_line_sub_parts[1]
|
||||
else:
|
||||
subtask_ph = 0
|
||||
else:
|
||||
if comment_line_sub_parts[1].isnumeric():
|
||||
subtask_ph = float(comment_line_parts[1]) * 0.5
|
||||
if comment_line_sub_parts[0].strip().isnumeric():
|
||||
subtask_ph = float(comment_line_sub_parts[0]) * 0.5
|
||||
estimation_remark = ""
|
||||
else:
|
||||
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
|
||||
try:
|
||||
if sp != sp_sum:
|
||||
@@ -79,3 +103,6 @@ for issue in issues_Jql:
|
||||
|
||||
except exceptions.JIRAError:
|
||||
logging.error("Issue {} has no (visible) Story Points-attribute".format(issue.key))
|
||||
|
||||
worksheet.autofit()
|
||||
workbook.close()
|
||||
@@ -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()
|
||||
@@ -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
@@ -1,13 +1,16 @@
|
||||
# find comments with estimations out of pre-planning
|
||||
# zisco 2021-09-30 19:50
|
||||
|
||||
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
|
||||
|
||||
def take_closest(myList, myNumber):
|
||||
"""
|
||||
@@ -28,34 +31,63 @@ def take_closest(myList, myNumber):
|
||||
return before
|
||||
|
||||
def get_assignee(type, issue):
|
||||
isBuergerfrontend = False
|
||||
if type == 'Dev Backend':
|
||||
return 'reu9001'
|
||||
# isBuergerfrontend = False
|
||||
if (type == 'Dev Backend' or type.startswith('Dev Unit')):
|
||||
# use assignee of story
|
||||
return cfg['def_assignees']['backend'] #'rab9001'
|
||||
#return issue.fields.assignee.name
|
||||
elif type == 'Dev Frontend':
|
||||
for component in issue.fields.components:
|
||||
isBuergerfrontend = component.name == "Bürgerfrontend"
|
||||
if isBuergerfrontend:
|
||||
return 'reu9001'
|
||||
return 'thh9001'
|
||||
elif type.startswith('Test-Anpassung'):
|
||||
return 'osm9005'
|
||||
# for component in issue.fields.components:
|
||||
# isBuergerfrontend = component.name == "Bürgerfrontend"
|
||||
# if isBuergerfrontend:
|
||||
return cfg['def_assignees']['frontend'] #'thh9001'
|
||||
elif type == 'Dev Citizen-Frontend':
|
||||
return cfg['def_assignees']['cit_frontend'] #'reu9001'
|
||||
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'\"')
|
||||
logging.info(f'summary ~ \"{escaped}\"')
|
||||
|
||||
assignee = get_assignee(type, issue)
|
||||
description = get_description(type, no_unit_test=no_unit_test)
|
||||
|
||||
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:
|
||||
single_issue = jira_server.issue(subtask[0].key)
|
||||
if single_issue.fields.summary == f'[{type}] {escaped}':
|
||||
raise 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')
|
||||
return
|
||||
|
||||
issue_type = 'Sub-task'
|
||||
|
||||
assignee = get_assignee(type, issue)
|
||||
description = 's. Story'
|
||||
if 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))
|
||||
|
||||
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',
|
||||
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"
|
||||
sprint = 9
|
||||
sprint = 5
|
||||
|
||||
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=24707') #, expand='changelog')
|
||||
#issues_Jql = jira_server.search_issues('key in (DKARB-140)') #, expand='changelog')
|
||||
issues_Jql = jira_server.search_issues('filter=25827') #, 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')
|
||||
|
||||
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
|
||||
|
||||
issue: Issue
|
||||
@@ -111,12 +167,13 @@ for issue in issues_Jql:
|
||||
|
||||
# only Issues with empty (or 0) story-points
|
||||
sp = 0
|
||||
no_unit_test = False
|
||||
if hasattr(issue.fields, 'customfield_10022'):
|
||||
sp = xsp(issue.fields.customfield_10022)
|
||||
|
||||
if sp != 0:
|
||||
logging.info('Ignoring issue {} as there are Story-Points already omitted'.format(issue))
|
||||
else:
|
||||
# if sp == 0:
|
||||
# logging.info('Ignoring issue {} as there are Story-Points already omitted'.format(issue))
|
||||
# else:
|
||||
single_issue = jira_server.issue(issue.key)
|
||||
comments = single_issue.fields.comment.comments
|
||||
|
||||
@@ -124,8 +181,11 @@ for issue in issues_Jql:
|
||||
for comment in comments:
|
||||
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 "Dev Unit-Tests: NN" in comment_lines:
|
||||
no_unit_test = True
|
||||
|
||||
for comment_line in comment_lines:
|
||||
if comment_line != "":
|
||||
# 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 len(comment_line_parts) == 2:
|
||||
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(' (')
|
||||
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
|
||||
estimation_remark = comment_line_sub_parts[1]
|
||||
else:
|
||||
@@ -144,21 +209,25 @@ for issue in issues_Jql:
|
||||
subtask_ph = float(comment_line_sub_parts[0]) * 0.5
|
||||
estimation_remark = ""
|
||||
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)
|
||||
|
||||
if comment_line_parts[0][:15] != "Test-Ausführung":
|
||||
#if comment_line_parts[0][:15] != "Test-Ausführung":
|
||||
create_subtask(issue,
|
||||
issue.key,
|
||||
comment_line_parts[0],
|
||||
subtask_ph,
|
||||
estimation_remark)
|
||||
estimation_remark,
|
||||
no_unit_test=no_unit_test)
|
||||
sp_sum += subtask_ph / 0.5
|
||||
else:
|
||||
logging.info(f'Sub-task creation skipped {comment_line_parts[0]} equals \'NN\'')
|
||||
|
||||
# find corresponding sub-task
|
||||
# issuelinks.inwardIssue
|
||||
try:
|
||||
if sp != sp_sum:
|
||||
if cfg['update_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))
|
||||
|
||||
@@ -168,6 +237,21 @@ for issue in issues_Jql:
|
||||
finally:
|
||||
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('Summe {}: {}'.format(assignee, sp_sum))
|
||||
|
||||
@@ -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
@@ -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.
Reference in New Issue
Block a user