Sitemap

📊 Jira Worklog Verilerini Python ile Dışa Aktarın ve E-posta ile Ekibinize Gönderin

7 min readApr 27, 2025

--

Hazırlayan: Burak Ceviz — DevSecOps Engineer — Sekom

Ekiplerin zaman takibini daha verimli ve şeffaf hale getirmek, proje ilerleyişini detaylı şekilde analiz etmek artık çok daha kolay. Bu yazıda, Jira üzerindeki worklog (iş kayıtları) verilerini Python kullanarak otomatik olarak çekmeyi, Excel’e aktarmayı ve e-posta yoluyla paylaşmayı adım adım öğreneceksiniz. Kod örnekleri, hata çözümleri ve otomasyon ipuçlarıyla dolu bu rehber sayesinde, manuel raporlama derdinden tamamen kurtulabilirsiniz.

  1. Jira kullanıcılarını (isimler ve ID’ler) alır
  2. Seçilen kullanıcıların iş kayıtlarını çeker
  3. Her iş kaydını ilgili proje ve üst görevle eşleştirir
  4. Tüm verileri bir Excel dosyasına yazar
  5. Raporu bir alıcıya e-posta ile gönderir

Jira REST API’sini ve requests, openpyxl ve smtplib gibi Python paketlerini kullanacağız.

🔑 Adım 1: Jira API Token Oluşturun

Jira Cloud API’ye erişim sağlamak için bir API token’ına ihtiyacınız var.

● Şu adrese gidin: https://id.atlassian.com/manage-profile/security/api-tokens

● “Create API token” (API token oluştur) butonuna tıklayın

● Token’a bir ad verin ve oluşturulan token’ı güvenli bir şekilde saklayın

👥 Adım 2: Tüm Jira Kullanıcılarını Listeleyin

Tüm kullanıcıların görünen adlarını, e-posta adreslerini ve accountId bilgilerini almak için aşağıdaki Python betiğini kullanın:

import requests
from requests.auth import HTTPBasicAuth
JIRA_URL = "https://your-domain.atlassian.net"
EMAIL = "your-email@example.com"
API_TOKEN = "your_api_token"
headers = {"Accept": "application/json"}def get_all_users():
users = []
start_at = 0
max_results = 50
while True:
url = f"{JIRA_URL}/rest/api/3/users/search"
params = {"startAt": start_at, "maxResults": max_results}
response = requests.get(url, headers=headers, auth=HTTPBasicAuth(EMAIL, API_TOKEN), params=params)
data = response.json()
if not data:
break
users.extend(data)
start_at += max_results
return users
if __name__ == "__main__":
user_list = get_all_users()
for user in user_list:
print(f"- {user.get('displayName')} | {user.get('emailAddress')} | {user.get('accountId')}")

Çıktıdan, rapora dahil etmek istediğiniz kullanıcıları seçin.

📑 Adım 3: Worklog Excel Raporunu Oluşturun

Şimdi, belirli kullanıcılar için iş kayıtlarını çekip yapılandıralım. Aşağıda kapsamlı bir Python betiği yer alıyor:

🔧 Yapılandırma

Öncelikle aşağıdaki değişkenleri kendi bilgilerinizle güncelleyin:

JIRA_URL = "https://your-domain.atlassian.net"
EMAIL = "your-email@example.com"
API_TOKEN = "your_api_token"
ACCOUNT_IDS = [
"user-account-id-1",
"user-account-id-2",
# add more as needed
]

📥 Tüm Python Script’i

Bu script aşağıdaki işlemleri gerçekleştirir:

● Son 35 gün içinde iş kaydı (worklog) girilmiş tüm işleri çeker

● Her kullanıcı için, her işte harcanan süreyi toplar

● Kullanıcı, proje ve üst iş bazında toplamları içeren bir Excel dosyası oluşturur

● Bu dosyayı e-posta ile gönderir

import requests
from requests.auth import HTTPBasicAuth
from datetime import datetime, timedelta, timezone
from dateutil.parser import parse as parse_date
import pandas as pd
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from openpyxl import Workbook
from openpyxl.utils.dataframe import dataframe_to_rows
from openpyxl.styles import Font
# Configuration
JIRA_URL = "https://your-domain.atlassian.net"
EMAIL = "your-email@example.com"
API_TOKEN = "your_api_token"
ACCOUNT_IDS = [
"user-account-id-1",
"user-account-id-2",
]
EXCEL_FILE = "jira_worklogs_report.xlsx"
headers = {"Accept": "application/json"}
cutoff_date = datetime.now(timezone.utc) - timedelta(days=35)
def get_issue_keys():
all_keys = []
start_at = 0
max_results = 100
while True:
jql = " OR ".join([f"worklogAuthor = {aid}" for aid in ACCOUNT_IDS])
url = f"{JIRA_URL}/rest/api/3/search"
params = {
"jql": f"({jql}) AND worklogDate >= -35d",
"fields": "key",
"startAt": start_at,
"maxResults": max_results
}
response = requests.get(url, headers=headers, auth=HTTPBasicAuth(EMAIL, API_TOKEN), params=params)
data = response.json()
issues = data.get("issues", [])
all_keys.extend([issue["key"] for issue in issues])
if start_at + max_results >= data.get("total", 0):
break
start_at += max_results
return all_keys
def get_worklogs(issue_key):
all_worklogs = []
start_at = 0
max_results = 100
while True:
url = f"{JIRA_URL}/rest/api/3/issue/{issue_key}/worklog"
params = {"startAt": start_at, "maxResults": max_results}
response = requests.get(url, headers=headers, auth=HTTPBasicAuth(EMAIL, API_TOKEN), params=params)
data = response.json()
worklogs = data.get("worklogs", [])
all_worklogs.extend(worklogs)
if start_at + max_results >= data.get("total", 0):
break
start_at += max_results
return all_worklogs
def get_issue_details(issue_key):
url = f"{JIRA_URL}/rest/api/3/issue/{issue_key}"
response = requests.get(url, headers=headers, auth=HTTPBasicAuth(EMAIL, API_TOKEN))
fields = response.json().get("fields", {})
return fields.get("summary", ""), fields.get("parent", {}).get("key", "None"), fields.get("project", {}).get("key", "Unknown")
def parse_timespent(timespent_str):
total_minutes = 0
parts = timespent_str.strip().split()
for part in parts:
if "h" in part:
total_minutes += int(part.replace("h", "")) * 60
elif "m" in part:
total_minutes += int(part.replace("m", ""))
return total_minutes
def format_minutes(minutes):
h = minutes // 60
m = minutes % 60
return f"{h}h {m}m" if m > 0 else f"{h}h"
# Processing
data_rows = []
user_totals = {}
user_project_totals = {}
project_totals = {}
parent_totals = {}
issue_keys = get_issue_keys()
for key in issue_keys:
summary, parent_key, project_key = get_issue_details(key)
worklogs = get_worklogs(key)
for log in worklogs:
author_id = log.get("author", {}).get("accountId")
if author_id in ACCOUNT_IDS:
started = parse_date(log["started"])
if started >= cutoff_date:
author_name = log.get("author", {}).get("displayName", "")
time_spent = log["timeSpent"]
minutes = parse_timespent(time_spent)
data_rows.append([project_key, key, parent_key, author_name, time_spent, started.strftime("%Y-%m-%d %H:%M"), summary]) user_totals[author_name] = user_totals.get(author_name, 0) + minutes
project_totals[project_key] = project_totals.get(project_key, 0) + minutes
parent_totals[parent_key] = parent_totals.get(parent_key, 0) + minutes
if author_name not in user_project_totals:
user_project_totals[author_name] = {}
user_project_totals[author_name][project_key] = user_project_totals[author_name].get(project_key, 0) + minutes
# Write to Excel
wb = Workbook()
ws = wb.active
ws.title = "Worklogs"
ws.append(["Total Time per User"])
for user, minutes in user_totals.items():
ws.append([user, format_minutes(minutes)])
if user in user_project_totals:
for project, mins in user_project_totals[user].items():
ws.append(["", f"{project}: {format_minutes(mins)}"])
ws.append(["", "------"])
ws.append([])
ws.append(["Total Time per Project"])
for project, minutes in project_totals.items():
ws.append([project, format_minutes(minutes)])
ws.append([])
ws.append(["Total Time per Parent Issue"])
for parent, minutes in parent_totals.items():
ws.append([parent, format_minutes(minutes)])
ws.append([])
columns = ["Project", "Issue", "Parent", "Author", "Time Spent", "Started", "Summary"]
ws.append(columns)
for row in data_rows:
ws.append(row)
for cell in ws[1]:
cell.font = Font(bold=True)
wb.save(EXCEL_FILE)
print(f"Excel file created: {EXCEL_FILE}")
# Emaildef send_email_with_attachments():
from_address = "your_sender_email@example.com"
to_address = "your_receiver_email@example.com"
subject = "Jira Worklog Report"
body = "Hello,\n\nPlease find attached the Jira worklog report for the past 35 days.\n\nBest regards." msg = MIMEMultipart()
msg["From"] = from_address
msg["To"] = to_address
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
with open(EXCEL_FILE, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header("Content-Disposition", f"attachment; filename={EXCEL_FILE}")
msg.attach(part)
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(from_address, "your_email_app_password")
server.sendmail(from_address, to_address, msg.as_string())
server.quit()
print("Email successfully sent!")
send_email_with_attachments()

📧 Adım 4: Raporu E-posta ile Gönderin

Script, oluşturulan Excel dosyasını otomatik olarak Gmail SMTP kullanarak e-posta ile gönderir:

● Gönderen (from_address) ve alıcı (to_address) adreslerini güncelleyin

● Gmail kullanıyorsanız, “Uygulama Şifreleri”ni etkinleştirin

server.login(from_address, “your_app_password”)

smtplib kütüphanesinin etkin olduğundan emin olun ve Gmail’de “daha az güvenli uygulamalar” ayarı açık olmalı ya da doğrudan Uygulama Şifresi kullanılmalıdır.

✅ Çıktı Örneği

Excel dosyasında şu bilgiler yer alır:

● Kullanıcıların toplam süreleri

● Proje bazında detaylı dağılımlar

● Üst görev (parent issue) bazında zaman paylaşımları

● Tarih ve açıklama bilgileriyle birlikte tüm iş kayıtlarının tam listesi

🧠 Neden Faydalı?

Yönetsel İçgörü: Kim ne zaman ve hangi iş üzerinde çalıştı, kolayca görebilirsiniz

Zaman Takibi: Faturalandırılabilir saatlerle karşılaştırma yapabilirsiniz

Denetim Hazırlığı: Geliştirici aktivitelerini açık ve düzenli şekilde kaydedebilirsiniz

🛠️ Sık Karşılaşılan Hatalar ve Çözümleri (Jira Python Entegrasyonu)

Jira API ile Python kullanarak raporlama yaparken karşılaşabileceğiniz bazı yaygın hatalar ve bunların çözüm yolları aşağıda listelenmiştir. Bu bölüm, projenizin stabil çalışmasını sağlamak ve olası sorunlara hızlı müdahale edebilmek adına rehber niteliğindedir.

❗️ 401 Unauthorized — Jira API Kimlik Doğrulama Hatası

Bu hata, genellikle şu durumlarda ortaya çıkar:

● E-posta adresiniz yanlış girilmiş olabilir

● API token süresi dolmuş olabilir

● HTTPBasicAuth ile yapılan kimlik doğrulaması başarısız olmuştur

✅ Çözüm:

Kodunuzda kimlik doğrulama kısmının doğru tanımlandığından emin olun:

from requests.auth import HTTPBasicAuth
auth = HTTPBasicAuth(EMAIL, API_TOKEN)

Ayrıca Atlassian hesabınızda geçerli bir API token’ınız olduğundan ve Jira kullanıcı e-postanızı kullandığınızdan emin olun.

❗️ smtplib.SMTPAuthenticationError — Gmail SMTP Oturum Hatası

Bu hata, SMTP ile e-posta gönderirken şifre doğrulaması başarısız olduğunda görülür. Gmail, artık doğrudan şifreyle girişe izin vermemekte; bunun yerine Uygulama Şifresi kullanılmalıdır.

✅ Çözüm :

● Google hesabınızda 2 Adımlı Doğrulama’yı etkinleştirin

https://myaccount.google.com/apppasswords adresinden yeni bir uygulama şifresi oluşturun

● Script’te bu uygulama şifresini kullanarak oturum açın:

server.login(from_address, "your_app_password")

❗️ JSONDecodeError — Geçersiz API Yanıtı

Bazen Jira API’den gelen yanıt boş, eksik ya da hatalı biçimlendirilmiş olabilir. Bu durumda response.json() satırı hata verir.

✅ Çözüm :

JSON dönüşümünden önce yanıtın gerçekten JSON formatında olup olmadığını kontrol edin:

if response.status_code == 200:
data = response.json()
else:
print(f"API hatası: {response.status_code}")

Gerekiyorsa log tutarak hangi isteğin başarısız olduğunu analiz edebilirsiniz.

❗️ KeyError — Beklenmeyen Veri Yapısı

Bazı Jira kurulumlarında parent, summary ya da accountId gibi alanlar her zaman gelmeyebilir. dict[“key”] kullanımı, bu tür durumlarda script’in kırılmasına neden olur.

✅ Çözüm :

Python’daki get() metodunu tercih edin. Bu yöntemle, anahtar yoksa None ya da varsayılan bir değer döner:

summary = fields.get("summary", "Özet bulunamadı")

Bu, daha dayanıklı ve esnek bir veri işleme sağlar.

❗️ UnicodeEncodeError — Excel’e Yazarken Karakter Hatası

Türkçe karakter içeren metinler ya da özel semboller Excel’e yazılırken kodlama problemi yaşanabilir. Bu da script’in çalışmasını durdurur.

✅ Çözüm :

● openpyxl gibi Unicode destekli kütüphaneleri tercih edin

● Excel yazımında özel karakterlere dikkat edin, gerekiyorsa str.encode(“utf-8”, errors=”ignore”) gibi ön işlemler uygulayın

● Excel’in varsayılan yazı tipi Latin-1 değilse uyumsuzluk çıkabilir, bu durumda font ayarı yapılabilir

💬 İpucu: Tüm hata durumlarını try/except bloklarıyla sarmak, hem hataların loglanmasını sağlar hem de script’in yarıda kesilmesini önler. Örneğin:

try:
response = requests.get(url, headers=headers, auth=auth)
data = response.json()
except Exception as e:
print(f"İstek sırasında hata oluştu: {e}")

🏁 Sonuç

Bu yöntem, Jira iş kayıtlarının raporlanmasını otomatikleştirerek ekiplerin biçimlendirme, iletim ve analiz süreçlerinde tam kontrol sağlamasına yardımcı olur. İsterseniz bu yapıyı Microsoft Teams, Slack ya da veritabanları ile entegre ederek daha da genişletebilirsiniz.

❓Jira Worklog Python Raporlama Hakkında Sıkça Sorulan Sorular

Jira worklog verisi nasıl dışa aktarılır?

Jira’nın REST API servisi kullanılarak belirli kullanıcıların iş kayıtları çekilebilir. Python’daki requests kütüphanesiyle veriler alınır, ardından pandas ile işlenerek Excel formatına dönüştürülür. Bu sayede manuel raporlama ihtiyacı ortadan kalkar.

Jira worklog verisi Excel dosyasına nasıl yazılır?

API’den gelen JSON formatındaki veriler pandas DataFrame’e dönüştürülerek openpyxl yardımıyla Excel’e yazılır. Her satırda proje, kullanıcı, iş tanımı, harcanan süre gibi detaylar yer alır. Excel dosyası, okunabilir ve filtrelenebilir şekilde yapılandırılır.

Python ile Jira raporu otomatik olarak nasıl gönderilir?

Hazırlanan Excel dosyası, Python’daki smtplib ve email modülleri sayesinde otomatik olarak e-posta ile gönderilebilir. Gmail gibi servislerde uygulama şifresi kullanılarak güvenli bağlantı sağlanır. Bu işlem zamanlanarak tam otomasyon da sağlanabilir.

Jira raporuna hangi kullanıcılar dahil edilir?

Jira API üzerinden alınan kullanıcı listesinden seçilen accountId değerleri script’e eklenir. Script, yalnızca bu kullanıcıların iş kayıtlarını filtreleyerek çeker. Böylece yalnızca belirli ekip üyelerinin verileri rapora dahil edilir.

Gmail SMTP ile e-posta göndermek güvenli midir?

Gmail SMTP servisi üzerinden e-posta göndermek uygulama şifresi kullanıldığında oldukça güvenlidir. Ana şifre yerine tek seferlik bir şifre oluşturularak kullanılır. Bu şifre sadece belirlenen uygulama için geçerlidir ve istenildiğinde iptal edilebilir.

--

--

Sekom
Sekom

Written by Sekom

Sekom İletişim Sistemleri, birçok farklı uzmanlık alanıyla bilişim sektöründe faaliyet gösteren, uçtan uca bir dijital dönüşüm entegratörüdür.

No responses yet