chub-dev 0.1.0 → 0.1.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1492 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: issues
|
|
3
|
+
description: "Jira Python SDK Coding Guidelines for writing code using the Jira API with official libraries and SDKs"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "python"
|
|
6
|
+
versions: "3.10.5"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "jira,issues,atlassian,project-management,agile"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Jira Python SDK Coding Guidelines
|
|
13
|
+
|
|
14
|
+
You are a Jira API coding expert. Help me with writing code using the Jira API calling the official libraries and SDKs.
|
|
15
|
+
|
|
16
|
+
You can find the official Jira API documentation and code samples here:
|
|
17
|
+
https://jira.readthedocs.io/
|
|
18
|
+
https://developer.atlassian.com/cloud/jira/platform/rest/v3/
|
|
19
|
+
|
|
20
|
+
## Golden Rule: Use the Correct and Current SDK
|
|
21
|
+
|
|
22
|
+
Always use the jira Python library to interact with the Jira Cloud, Jira Server, and Jira Data Center REST APIs. This is the most actively maintained and comprehensive Python wrapper for Jira APIs.
|
|
23
|
+
|
|
24
|
+
- **Library Name:** jira
|
|
25
|
+
- **PyPI Package:** `jira`
|
|
26
|
+
- **Minimum Python Version:** 3.10 or newer
|
|
27
|
+
- **Legacy Libraries**: `jira-python`, `atlassian-python-api` (different library), and other unofficial packages may have different APIs
|
|
28
|
+
|
|
29
|
+
**Installation:**
|
|
30
|
+
|
|
31
|
+
- **Correct:** `pip install jira`
|
|
32
|
+
- **Correct:** `pip install jira[opt,cli,test]` (with optional dependencies)
|
|
33
|
+
|
|
34
|
+
**APIs and Usage:**
|
|
35
|
+
|
|
36
|
+
- **Correct:** `from jira import JIRA`
|
|
37
|
+
- **Correct:** `jira = JIRA('https://your-domain.atlassian.net', basic_auth=('email', 'token'))`
|
|
38
|
+
- **Correct:** `jira.search_issues('project = PROJ')`
|
|
39
|
+
- **Incorrect:** Using REST API endpoints directly without the SDK
|
|
40
|
+
- **Incorrect:** Using deprecated cookie-based authentication
|
|
41
|
+
- **Incorrect:** Using username/password for Jira Cloud (use API tokens instead)
|
|
42
|
+
|
|
43
|
+
## Authentication
|
|
44
|
+
|
|
45
|
+
The jira library supports multiple authentication methods. Choose the appropriate method based on your Jira instance type.
|
|
46
|
+
|
|
47
|
+
### API Token Authentication (Recommended for Jira Cloud)
|
|
48
|
+
|
|
49
|
+
Generate an API token at: https://id.atlassian.com/manage-profile/security/api-tokens
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from jira import JIRA
|
|
53
|
+
|
|
54
|
+
# Using basic_auth with email and API token
|
|
55
|
+
jira = JIRA(
|
|
56
|
+
server='https://your-domain.atlassian.net',
|
|
57
|
+
basic_auth=('your.email@example.com', 'your_api_token')
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Personal Access Token (PAT) Authentication (Self-Hosted Jira)
|
|
62
|
+
|
|
63
|
+
For Jira Server/Data Center instances:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from jira import JIRA
|
|
67
|
+
|
|
68
|
+
# Using token_auth parameter
|
|
69
|
+
jira = JIRA(
|
|
70
|
+
server='https://jira.yourcompany.com',
|
|
71
|
+
token_auth='your_personal_access_token'
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Alternative: Using headers
|
|
75
|
+
headers = JIRA.DEFAULT_OPTIONS["headers"].copy()
|
|
76
|
+
headers["Authorization"] = f"Bearer {personal_access_token}"
|
|
77
|
+
|
|
78
|
+
jira = JIRA(
|
|
79
|
+
server='https://jira.yourcompany.com',
|
|
80
|
+
options={"headers": headers}
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### OAuth Authentication
|
|
85
|
+
|
|
86
|
+
For OAuth integrations:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from jira import JIRA
|
|
90
|
+
|
|
91
|
+
oauth_dict = {
|
|
92
|
+
'access_token': 'your_access_token',
|
|
93
|
+
'access_token_secret': 'your_access_token_secret',
|
|
94
|
+
'consumer_key': 'your_consumer_key',
|
|
95
|
+
'key_cert': key_cert_data # RSA private key data
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
jira = JIRA(
|
|
99
|
+
server='https://your-domain.atlassian.net',
|
|
100
|
+
oauth=oauth_dict
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Kerberos Authentication
|
|
105
|
+
|
|
106
|
+
For enterprise environments with Kerberos:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from jira import JIRA
|
|
110
|
+
|
|
111
|
+
# Basic Kerberos
|
|
112
|
+
jira = JIRA(server='https://jira.yourcompany.com', kerberos=True)
|
|
113
|
+
|
|
114
|
+
# With options
|
|
115
|
+
jira = JIRA(
|
|
116
|
+
server='https://jira.yourcompany.com',
|
|
117
|
+
kerberos=True,
|
|
118
|
+
kerberos_options={'mutual_authentication': 'DISABLED'}
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Environment Variables Setup
|
|
123
|
+
|
|
124
|
+
Create a `.env` file or set environment variables:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
JIRA_SERVER=https://your-domain.atlassian.net
|
|
128
|
+
JIRA_EMAIL=your.email@example.com
|
|
129
|
+
JIRA_API_TOKEN=your_api_token_here
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Load environment variables in your code:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import os
|
|
136
|
+
from jira import JIRA
|
|
137
|
+
|
|
138
|
+
jira = JIRA(
|
|
139
|
+
server=os.environ['JIRA_SERVER'],
|
|
140
|
+
basic_auth=(os.environ['JIRA_EMAIL'], os.environ['JIRA_API_TOKEN'])
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Initialization
|
|
145
|
+
|
|
146
|
+
Create a JIRA client instance for all API interactions:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from jira import JIRA
|
|
150
|
+
|
|
151
|
+
# Basic initialization
|
|
152
|
+
jira = JIRA(
|
|
153
|
+
server='https://your-domain.atlassian.net',
|
|
154
|
+
basic_auth=('your.email@example.com', 'your_api_token')
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# With custom options
|
|
158
|
+
options = {
|
|
159
|
+
'server': 'https://your-domain.atlassian.net',
|
|
160
|
+
'verify': True, # SSL certificate verification
|
|
161
|
+
'max_retries': 3,
|
|
162
|
+
'timeout': 30,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
jira = JIRA(
|
|
166
|
+
options=options,
|
|
167
|
+
basic_auth=('your.email@example.com', 'your_api_token')
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Error Handling
|
|
172
|
+
|
|
173
|
+
Handle exceptions for robust applications:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from jira import JIRA, JIRAError
|
|
177
|
+
|
|
178
|
+
jira = JIRA(
|
|
179
|
+
server='https://your-domain.atlassian.net',
|
|
180
|
+
basic_auth=('your.email@example.com', 'your_api_token')
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
issue = jira.issue('PROJ-123')
|
|
185
|
+
print(f'Issue: {issue.key} - {issue.fields.summary}')
|
|
186
|
+
except JIRAError as e:
|
|
187
|
+
if e.status_code == 404:
|
|
188
|
+
print('Issue not found')
|
|
189
|
+
elif e.status_code == 401:
|
|
190
|
+
print('Authentication failed')
|
|
191
|
+
elif e.status_code == 403:
|
|
192
|
+
print('Permission denied')
|
|
193
|
+
else:
|
|
194
|
+
print(f'Error: {e.status_code} - {e.text}')
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(f'Unexpected error: {e}')
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Core API Surfaces
|
|
200
|
+
|
|
201
|
+
### Projects
|
|
202
|
+
|
|
203
|
+
#### Get Project
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
# Minimal example
|
|
207
|
+
project = jira.project('PROJ')
|
|
208
|
+
|
|
209
|
+
print(project.key)
|
|
210
|
+
print(project.name)
|
|
211
|
+
print(project.lead.displayName)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Advanced: Get Project with All Details
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
# Get project with all properties
|
|
218
|
+
project = jira.project('PROJ')
|
|
219
|
+
|
|
220
|
+
print('Project Key:', project.key)
|
|
221
|
+
print('Project Name:', project.name)
|
|
222
|
+
print('Project Lead:', project.lead.displayName)
|
|
223
|
+
print('Description:', project.raw.get('description', 'No description'))
|
|
224
|
+
print('Project Type:', project.projectTypeKey)
|
|
225
|
+
print('URL:', project.raw.get('url'))
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### Get All Projects
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
# Get all projects
|
|
232
|
+
projects = jira.projects()
|
|
233
|
+
|
|
234
|
+
for project in projects:
|
|
235
|
+
print(f'{project.key}: {project.name}')
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### Get Project Components
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# Get components for a project
|
|
242
|
+
components = jira.project_components('PROJ')
|
|
243
|
+
|
|
244
|
+
for component in components:
|
|
245
|
+
print(f'{component.name}: {component.description}')
|
|
246
|
+
if component.lead:
|
|
247
|
+
print(f' Lead: {component.lead.displayName}')
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### Get Project Versions
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
# Get versions/releases for a project
|
|
254
|
+
versions = jira.project_versions('PROJ')
|
|
255
|
+
|
|
256
|
+
for version in versions:
|
|
257
|
+
print(f'{version.name} - Released: {version.released}')
|
|
258
|
+
if hasattr(version, 'releaseDate'):
|
|
259
|
+
print(f' Release Date: {version.releaseDate}')
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Issues
|
|
263
|
+
|
|
264
|
+
#### Get Issue
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
# Minimal example
|
|
268
|
+
issue = jira.issue('PROJ-123')
|
|
269
|
+
|
|
270
|
+
print(issue.key)
|
|
271
|
+
print(issue.fields.summary)
|
|
272
|
+
print(issue.fields.status.name)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### Advanced: Get Issue with Specific Fields
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
# Get issue with expanded fields
|
|
279
|
+
issue = jira.issue(
|
|
280
|
+
'PROJ-123',
|
|
281
|
+
fields='summary,status,assignee,reporter,created,updated,priority,labels',
|
|
282
|
+
expand='changelog,renderedFields'
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
print('Key:', issue.key)
|
|
286
|
+
print('Summary:', issue.fields.summary)
|
|
287
|
+
print('Status:', issue.fields.status.name)
|
|
288
|
+
print('Priority:', issue.fields.priority.name)
|
|
289
|
+
print('Assignee:', issue.fields.assignee.displayName if issue.fields.assignee else 'Unassigned')
|
|
290
|
+
print('Reporter:', issue.fields.reporter.displayName)
|
|
291
|
+
print('Created:', issue.fields.created)
|
|
292
|
+
print('Updated:', issue.fields.updated)
|
|
293
|
+
print('Labels:', issue.fields.labels)
|
|
294
|
+
|
|
295
|
+
# Access changelog
|
|
296
|
+
if hasattr(issue, 'changelog'):
|
|
297
|
+
print('\nChange History:')
|
|
298
|
+
for history in issue.changelog.histories:
|
|
299
|
+
print(f' {history.created} by {history.author.displayName}')
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
#### Create Issue
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
# Minimal example
|
|
306
|
+
new_issue = jira.create_issue(
|
|
307
|
+
project='PROJ',
|
|
308
|
+
summary='New issue from Python',
|
|
309
|
+
description='This is a test issue created via the API',
|
|
310
|
+
issuetype={'name': 'Task'}
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
print(f'Created issue: {new_issue.key}')
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Advanced: Create Issue with All Fields
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
# Advanced example with multiple fields
|
|
320
|
+
issue_dict = {
|
|
321
|
+
'project': {'key': 'PROJ'},
|
|
322
|
+
'summary': 'Implement user authentication',
|
|
323
|
+
'description': 'Need to implement OAuth 2.0 authentication for the API.\n\nAcceptance Criteria:\n- Support OAuth 2.0\n- Token refresh\n- Secure storage',
|
|
324
|
+
'issuetype': {'name': 'Story'},
|
|
325
|
+
'priority': {'name': 'High'},
|
|
326
|
+
'labels': ['security', 'authentication', 'api'],
|
|
327
|
+
'components': [{'name': 'Backend'}],
|
|
328
|
+
'assignee': {'accountId': '5b10a2844c20165700ede21g'},
|
|
329
|
+
'duedate': '2025-12-31',
|
|
330
|
+
'customfield_10001': 'Custom field value',
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
new_issue = jira.create_issue(fields=issue_dict)
|
|
334
|
+
|
|
335
|
+
print(f'Created issue: {new_issue.key} - {new_issue.fields.summary}')
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### Bulk Create Issues
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
# Create multiple issues at once
|
|
342
|
+
issue_list = [
|
|
343
|
+
{
|
|
344
|
+
'project': {'key': 'PROJ'},
|
|
345
|
+
'summary': 'First task',
|
|
346
|
+
'description': 'Description for first task',
|
|
347
|
+
'issuetype': {'name': 'Task'},
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
'project': {'key': 'PROJ'},
|
|
351
|
+
'summary': 'Second task',
|
|
352
|
+
'description': 'Description for second task',
|
|
353
|
+
'issuetype': {'name': 'Task'},
|
|
354
|
+
'priority': {'name': 'High'},
|
|
355
|
+
},
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
issues = jira.create_issues(field_list=issue_list)
|
|
359
|
+
|
|
360
|
+
for issue in issues:
|
|
361
|
+
print(f'Created: {issue["issue"].key}')
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### Update Issue
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
# Update issue fields
|
|
368
|
+
issue = jira.issue('PROJ-123')
|
|
369
|
+
|
|
370
|
+
# Method 1: Using update method
|
|
371
|
+
issue.update(
|
|
372
|
+
summary='Updated summary',
|
|
373
|
+
description='Updated description',
|
|
374
|
+
priority={'name': 'Critical'}
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Method 2: Using fields parameter
|
|
378
|
+
issue.update(fields={
|
|
379
|
+
'labels': ['updated', 'priority'],
|
|
380
|
+
'customfield_10001': 'New value'
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
# Method 3: Silent update (no notifications)
|
|
384
|
+
issue.update(
|
|
385
|
+
notify=False,
|
|
386
|
+
description='Silent update - no emails sent'
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
print('Issue updated successfully')
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Assign Issue
|
|
393
|
+
|
|
394
|
+
```python
|
|
395
|
+
# Assign issue to a user
|
|
396
|
+
jira.assign_issue('PROJ-123', 'username')
|
|
397
|
+
|
|
398
|
+
# Or use accountId
|
|
399
|
+
jira.assign_issue('PROJ-123', '5b10a2844c20165700ede21g')
|
|
400
|
+
|
|
401
|
+
# Unassign issue
|
|
402
|
+
jira.assign_issue('PROJ-123', None)
|
|
403
|
+
|
|
404
|
+
print('Issue assigned successfully')
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
#### Delete Issue
|
|
408
|
+
|
|
409
|
+
```python
|
|
410
|
+
# Delete an issue
|
|
411
|
+
issue = jira.issue('PROJ-123')
|
|
412
|
+
issue.delete()
|
|
413
|
+
|
|
414
|
+
# Delete with subtasks
|
|
415
|
+
issue.delete(deleteSubtasks=True)
|
|
416
|
+
|
|
417
|
+
print('Issue deleted successfully')
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Issue Search
|
|
421
|
+
|
|
422
|
+
#### Basic JQL Search
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
# Search for issues in a project
|
|
426
|
+
issues = jira.search_issues('project = PROJ')
|
|
427
|
+
|
|
428
|
+
for issue in issues:
|
|
429
|
+
print(f'{issue.key}: {issue.fields.summary}')
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
#### Advanced: Search with Pagination and Options
|
|
433
|
+
|
|
434
|
+
```python
|
|
435
|
+
# Advanced search with pagination and field selection
|
|
436
|
+
issues = jira.search_issues(
|
|
437
|
+
jql_str='project = PROJ AND status = "In Progress" ORDER BY created DESC',
|
|
438
|
+
startAt=0,
|
|
439
|
+
maxResults=50,
|
|
440
|
+
fields='summary,status,assignee,priority,created',
|
|
441
|
+
expand='changelog'
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
print(f'Total issues: {issues.total}')
|
|
445
|
+
print(f'Returned: {len(issues)}')
|
|
446
|
+
|
|
447
|
+
for issue in issues:
|
|
448
|
+
print(f'{issue.key}: {issue.fields.summary}')
|
|
449
|
+
print(f' Status: {issue.fields.status.name}')
|
|
450
|
+
print(f' Priority: {issue.fields.priority.name}')
|
|
451
|
+
print(f' Created: {issue.fields.created}')
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
#### Pagination Example
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
# Fetch all issues with pagination
|
|
458
|
+
def get_all_issues(jira, jql):
|
|
459
|
+
"""Fetch all issues matching JQL query with pagination"""
|
|
460
|
+
all_issues = []
|
|
461
|
+
start_at = 0
|
|
462
|
+
max_results = 100
|
|
463
|
+
|
|
464
|
+
while True:
|
|
465
|
+
issues = jira.search_issues(
|
|
466
|
+
jql_str=jql,
|
|
467
|
+
startAt=start_at,
|
|
468
|
+
maxResults=max_results
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
all_issues.extend(issues)
|
|
472
|
+
|
|
473
|
+
print(f'Fetched {len(all_issues)} of {issues.total} issues')
|
|
474
|
+
|
|
475
|
+
if len(issues) < max_results:
|
|
476
|
+
break
|
|
477
|
+
|
|
478
|
+
start_at += max_results
|
|
479
|
+
|
|
480
|
+
return all_issues
|
|
481
|
+
|
|
482
|
+
# Usage
|
|
483
|
+
all_project_issues = get_all_issues(jira, 'project = PROJ')
|
|
484
|
+
print(f'Total issues fetched: {len(all_project_issues)}')
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
#### Complex JQL Queries
|
|
488
|
+
|
|
489
|
+
```python
|
|
490
|
+
# Complex JQL with multiple conditions
|
|
491
|
+
jql = """
|
|
492
|
+
project = PROJ AND
|
|
493
|
+
status IN ("To Do", "In Progress") AND
|
|
494
|
+
priority IN (High, Critical) AND
|
|
495
|
+
assignee = currentUser() AND
|
|
496
|
+
created >= -30d
|
|
497
|
+
ORDER BY priority DESC, created ASC
|
|
498
|
+
"""
|
|
499
|
+
|
|
500
|
+
issues = jira.search_issues(jql.strip(), maxResults=100)
|
|
501
|
+
|
|
502
|
+
print(f'High priority issues: {len(issues)}')
|
|
503
|
+
for issue in issues:
|
|
504
|
+
print(f'{issue.key}: {issue.fields.summary} ({issue.fields.priority.name})')
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
#### Common JQL Patterns
|
|
508
|
+
|
|
509
|
+
```python
|
|
510
|
+
# Issues assigned to current user
|
|
511
|
+
issues = jira.search_issues('assignee = currentUser()')
|
|
512
|
+
|
|
513
|
+
# Issues created in last 7 days
|
|
514
|
+
issues = jira.search_issues('created >= -7d')
|
|
515
|
+
|
|
516
|
+
# High priority unresolved bugs
|
|
517
|
+
issues = jira.search_issues('type = Bug AND priority = High AND status != Resolved')
|
|
518
|
+
|
|
519
|
+
# Issues in specific sprint
|
|
520
|
+
issues = jira.search_issues('sprint = 123')
|
|
521
|
+
|
|
522
|
+
# Issues with specific labels
|
|
523
|
+
issues = jira.search_issues('labels IN (urgent, critical)')
|
|
524
|
+
|
|
525
|
+
# Issues due this week
|
|
526
|
+
issues = jira.search_issues('duedate >= startOfWeek() AND duedate <= endOfWeek()')
|
|
527
|
+
|
|
528
|
+
# Combining multiple conditions
|
|
529
|
+
issues = jira.search_issues(
|
|
530
|
+
'project = PROJ AND issuetype in (Story, Task) AND status = Open',
|
|
531
|
+
maxResults=50
|
|
532
|
+
)
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Comments
|
|
536
|
+
|
|
537
|
+
#### Get Comments
|
|
538
|
+
|
|
539
|
+
```python
|
|
540
|
+
# Get all comments for an issue
|
|
541
|
+
issue = jira.issue('PROJ-123')
|
|
542
|
+
comments = issue.fields.comment.comments
|
|
543
|
+
|
|
544
|
+
for comment in comments:
|
|
545
|
+
print(f'{comment.author.displayName} at {comment.created}:')
|
|
546
|
+
print(f' {comment.body}')
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
#### Get Specific Comment
|
|
550
|
+
|
|
551
|
+
```python
|
|
552
|
+
# Get a specific comment by ID
|
|
553
|
+
comment = jira.comment('PROJ-123', '10234')
|
|
554
|
+
|
|
555
|
+
print(f'Author: {comment.author.displayName}')
|
|
556
|
+
print(f'Created: {comment.created}')
|
|
557
|
+
print(f'Body: {comment.body}')
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### Add Comment
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
# Add a simple comment
|
|
564
|
+
comment = jira.add_comment('PROJ-123', 'This is a new comment from the API')
|
|
565
|
+
|
|
566
|
+
print(f'Comment added: {comment.id}')
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
#### Advanced: Add Comment with Visibility
|
|
570
|
+
|
|
571
|
+
```python
|
|
572
|
+
# Add comment visible only to specific role
|
|
573
|
+
comment = jira.add_comment(
|
|
574
|
+
'PROJ-123',
|
|
575
|
+
'This is visible only to administrators',
|
|
576
|
+
visibility={'type': 'role', 'value': 'Administrators'}
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# Add comment visible to specific group
|
|
580
|
+
comment = jira.add_comment(
|
|
581
|
+
'PROJ-123',
|
|
582
|
+
'This is visible only to developers',
|
|
583
|
+
visibility={'type': 'group', 'value': 'jira-developers'}
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
print(f'Restricted comment added: {comment.id}')
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
#### Add Comment with Rich Text (ADF Format)
|
|
590
|
+
|
|
591
|
+
```python
|
|
592
|
+
# Add comment using Atlassian Document Format (ADF)
|
|
593
|
+
comment_adf = {
|
|
594
|
+
"type": "doc",
|
|
595
|
+
"version": 1,
|
|
596
|
+
"content": [
|
|
597
|
+
{
|
|
598
|
+
"type": "paragraph",
|
|
599
|
+
"content": [
|
|
600
|
+
{
|
|
601
|
+
"type": "text",
|
|
602
|
+
"text": "This is a comment with "
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
"type": "text",
|
|
606
|
+
"text": "bold text",
|
|
607
|
+
"marks": [{"type": "strong"}]
|
|
608
|
+
}
|
|
609
|
+
]
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
"type": "codeBlock",
|
|
613
|
+
"content": [
|
|
614
|
+
{
|
|
615
|
+
"type": "text",
|
|
616
|
+
"text": "print('Hello, World!')"
|
|
617
|
+
}
|
|
618
|
+
]
|
|
619
|
+
}
|
|
620
|
+
]
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
comment = jira.add_comment('PROJ-123', comment_adf)
|
|
624
|
+
print(f'Rich text comment added: {comment.id}')
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
#### Update Comment
|
|
628
|
+
|
|
629
|
+
```python
|
|
630
|
+
# Update an existing comment
|
|
631
|
+
comment = jira.comment('PROJ-123', '10234')
|
|
632
|
+
|
|
633
|
+
comment.update(body='Updated comment text')
|
|
634
|
+
|
|
635
|
+
# Silent update (no notifications)
|
|
636
|
+
comment.update(body='Quiet update', notify=False)
|
|
637
|
+
|
|
638
|
+
print('Comment updated successfully')
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
#### Delete Comment
|
|
642
|
+
|
|
643
|
+
```python
|
|
644
|
+
# Delete a comment
|
|
645
|
+
comment = jira.comment('PROJ-123', '10234')
|
|
646
|
+
comment.delete()
|
|
647
|
+
|
|
648
|
+
print('Comment deleted successfully')
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Transitions
|
|
652
|
+
|
|
653
|
+
#### Get Available Transitions
|
|
654
|
+
|
|
655
|
+
```python
|
|
656
|
+
# Get all available transitions for an issue
|
|
657
|
+
transitions = jira.transitions('PROJ-123')
|
|
658
|
+
|
|
659
|
+
print('Available transitions:')
|
|
660
|
+
for transition in transitions:
|
|
661
|
+
print(f" {transition['id']}: {transition['name']} -> {transition['to']['name']}")
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
#### Transition Issue
|
|
665
|
+
|
|
666
|
+
```python
|
|
667
|
+
# Transition issue to a new status
|
|
668
|
+
jira.transition_issue('PROJ-123', '21') # Use transition ID
|
|
669
|
+
|
|
670
|
+
print('Issue transitioned successfully')
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
#### Advanced: Transition with Fields
|
|
674
|
+
|
|
675
|
+
```python
|
|
676
|
+
# Transition issue and set fields
|
|
677
|
+
jira.transition_issue(
|
|
678
|
+
'PROJ-123',
|
|
679
|
+
'5', # Transition ID for "Resolve Issue"
|
|
680
|
+
assignee={'name': 'pm_user'},
|
|
681
|
+
resolution={'id': '3'}, # Fixed
|
|
682
|
+
comment='Issue resolved and tested'
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
# Alternative using fields parameter
|
|
686
|
+
jira.transition_issue(
|
|
687
|
+
'PROJ-123',
|
|
688
|
+
'5',
|
|
689
|
+
fields={
|
|
690
|
+
'assignee': {'name': 'qa_user'},
|
|
691
|
+
'resolution': {'name': 'Done'},
|
|
692
|
+
'customfield_10001': 'custom value'
|
|
693
|
+
}
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
print('Issue transitioned with fields updated')
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Attachments
|
|
700
|
+
|
|
701
|
+
#### Add Attachment
|
|
702
|
+
|
|
703
|
+
```python
|
|
704
|
+
# Add attachment from file path
|
|
705
|
+
jira.add_attachment(
|
|
706
|
+
issue='PROJ-123',
|
|
707
|
+
attachment='/path/to/file.pdf'
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
print('Attachment added successfully')
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
#### Advanced: Add Multiple Attachments
|
|
714
|
+
|
|
715
|
+
```python
|
|
716
|
+
# Add multiple attachments
|
|
717
|
+
jira.add_attachment(
|
|
718
|
+
issue='PROJ-123',
|
|
719
|
+
attachment='/path/to/document.pdf',
|
|
720
|
+
filename='report.pdf'
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
# Add attachment from file object
|
|
724
|
+
with open('/path/to/file.txt', 'rb') as f:
|
|
725
|
+
jira.add_attachment(issue='PROJ-123', attachment=f, filename='data.txt')
|
|
726
|
+
|
|
727
|
+
# Add multiple files
|
|
728
|
+
attachments = ['/path/to/file1.txt', '/path/to/file2.pdf']
|
|
729
|
+
for attachment_path in attachments:
|
|
730
|
+
jira.add_attachment(issue='PROJ-123', attachment=attachment_path)
|
|
731
|
+
|
|
732
|
+
print('Multiple attachments added successfully')
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
#### Get Attachments
|
|
736
|
+
|
|
737
|
+
```python
|
|
738
|
+
# Get all attachments for an issue
|
|
739
|
+
issue = jira.issue('PROJ-123')
|
|
740
|
+
|
|
741
|
+
for attachment in issue.fields.attachment:
|
|
742
|
+
print(f'Filename: {attachment.filename}')
|
|
743
|
+
print(f'Size: {attachment.size} bytes')
|
|
744
|
+
print(f'MIME Type: {attachment.mimeType}')
|
|
745
|
+
print(f'Created: {attachment.created}')
|
|
746
|
+
print(f'Download URL: {attachment.content}')
|
|
747
|
+
print()
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
#### Download Attachment
|
|
751
|
+
|
|
752
|
+
```python
|
|
753
|
+
# Download attachment content
|
|
754
|
+
issue = jira.issue('PROJ-123')
|
|
755
|
+
|
|
756
|
+
for attachment in issue.fields.attachment:
|
|
757
|
+
# Get attachment content
|
|
758
|
+
content = attachment.get()
|
|
759
|
+
|
|
760
|
+
# Save to file
|
|
761
|
+
with open(attachment.filename, 'wb') as f:
|
|
762
|
+
f.write(content)
|
|
763
|
+
|
|
764
|
+
print(f'Downloaded: {attachment.filename}')
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
#### Delete Attachment
|
|
768
|
+
|
|
769
|
+
```python
|
|
770
|
+
# Delete an attachment by ID
|
|
771
|
+
jira.delete_attachment('10001')
|
|
772
|
+
|
|
773
|
+
print('Attachment deleted successfully')
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Agile - Boards
|
|
777
|
+
|
|
778
|
+
#### Get All Boards
|
|
779
|
+
|
|
780
|
+
```python
|
|
781
|
+
# Get all boards
|
|
782
|
+
boards = jira.boards()
|
|
783
|
+
|
|
784
|
+
for board in boards:
|
|
785
|
+
print(f'{board.id}: {board.name} ({board.type})')
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
#### Advanced: Get Boards with Filters
|
|
789
|
+
|
|
790
|
+
```python
|
|
791
|
+
# Get boards with specific parameters
|
|
792
|
+
boards = jira.boards(
|
|
793
|
+
startAt=0,
|
|
794
|
+
maxResults=50,
|
|
795
|
+
type='scrum', # or 'kanban'
|
|
796
|
+
name='Development',
|
|
797
|
+
projectKeyOrID='PROJ'
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
for board in boards:
|
|
801
|
+
print(f'{board.id}: {board.name}')
|
|
802
|
+
print(f' Type: {board.type}')
|
|
803
|
+
if hasattr(board, 'location'):
|
|
804
|
+
print(f' Location: {board.location.name}')
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
#### Get Specific Board
|
|
808
|
+
|
|
809
|
+
```python
|
|
810
|
+
# Get a specific board by ID
|
|
811
|
+
board = jira.board(1)
|
|
812
|
+
|
|
813
|
+
print(f'Board: {board.name}')
|
|
814
|
+
print(f'Type: {board.type}')
|
|
815
|
+
print(f'Location: {board.location.name}')
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
#### Get Issues on Board
|
|
819
|
+
|
|
820
|
+
```python
|
|
821
|
+
# Get all issues on a board
|
|
822
|
+
board_id = 1
|
|
823
|
+
issues = jira.search_issues(f'board = {board_id}')
|
|
824
|
+
|
|
825
|
+
for issue in issues:
|
|
826
|
+
print(f'{issue.key}: {issue.fields.summary}')
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### Agile - Sprints
|
|
830
|
+
|
|
831
|
+
#### Get Sprints for Board
|
|
832
|
+
|
|
833
|
+
```python
|
|
834
|
+
# Get all sprints for a board
|
|
835
|
+
board_id = 1
|
|
836
|
+
sprints = jira.sprints(board_id)
|
|
837
|
+
|
|
838
|
+
for sprint in sprints:
|
|
839
|
+
print(f'{sprint.id}: {sprint.name}')
|
|
840
|
+
print(f' State: {sprint.state}')
|
|
841
|
+
if hasattr(sprint, 'startDate'):
|
|
842
|
+
print(f' Start: {sprint.startDate}')
|
|
843
|
+
if hasattr(sprint, 'endDate'):
|
|
844
|
+
print(f' End: {sprint.endDate}')
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
#### Advanced: Get Sprints with Filters
|
|
848
|
+
|
|
849
|
+
```python
|
|
850
|
+
# Get sprints with specific state
|
|
851
|
+
board_id = 1
|
|
852
|
+
sprints = jira.sprints(
|
|
853
|
+
board_id,
|
|
854
|
+
startAt=0,
|
|
855
|
+
maxResults=50,
|
|
856
|
+
state='active' # 'active', 'closed', 'future'
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
for sprint in sprints:
|
|
860
|
+
print(f'{sprint.name} - {sprint.state}')
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
#### Get Sprint
|
|
864
|
+
|
|
865
|
+
```python
|
|
866
|
+
# Get a specific sprint by ID
|
|
867
|
+
sprint = jira.sprint(123)
|
|
868
|
+
|
|
869
|
+
print(f'Sprint: {sprint.name}')
|
|
870
|
+
print(f'State: {sprint.state}')
|
|
871
|
+
print(f'Start Date: {sprint.startDate}')
|
|
872
|
+
print(f'End Date: {sprint.endDate}')
|
|
873
|
+
print(f'Goal: {sprint.goal}')
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
#### Get Issues in Sprint
|
|
877
|
+
|
|
878
|
+
```python
|
|
879
|
+
# Get all issues in a sprint
|
|
880
|
+
sprint_id = 123
|
|
881
|
+
issues = jira.search_issues(f'sprint = {sprint_id}')
|
|
882
|
+
|
|
883
|
+
print(f'Issues in sprint: {len(issues)}')
|
|
884
|
+
for issue in issues:
|
|
885
|
+
print(f'{issue.key}: {issue.fields.summary}')
|
|
886
|
+
print(f' Status: {issue.fields.status.name}')
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
#### Advanced: Get Sprint Issues with JQL
|
|
890
|
+
|
|
891
|
+
```python
|
|
892
|
+
# Get incomplete issues in active sprint
|
|
893
|
+
sprint_id = 123
|
|
894
|
+
jql = f'sprint = {sprint_id} AND status != Done'
|
|
895
|
+
|
|
896
|
+
issues = jira.search_issues(jql, maxResults=100)
|
|
897
|
+
|
|
898
|
+
print(f'Incomplete issues: {len(issues)}')
|
|
899
|
+
for issue in issues:
|
|
900
|
+
print(f'{issue.key}: {issue.fields.summary} ({issue.fields.status.name})')
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
### Users
|
|
904
|
+
|
|
905
|
+
#### Get Current User
|
|
906
|
+
|
|
907
|
+
```python
|
|
908
|
+
# Get currently authenticated user
|
|
909
|
+
current_user = jira.current_user()
|
|
910
|
+
|
|
911
|
+
print(f'Username: {current_user}')
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
#### Search for Users
|
|
915
|
+
|
|
916
|
+
```python
|
|
917
|
+
# Search for users by username or email
|
|
918
|
+
users = jira.search_users(user='john')
|
|
919
|
+
|
|
920
|
+
for user in users:
|
|
921
|
+
print(f'{user.displayName} ({user.emailAddress})')
|
|
922
|
+
print(f' Account ID: {user.accountId}')
|
|
923
|
+
print(f' Active: {user.active}')
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
#### Advanced: Search Users with Filters
|
|
927
|
+
|
|
928
|
+
```python
|
|
929
|
+
# Search for users in a project
|
|
930
|
+
users = jira.search_users(
|
|
931
|
+
user='john',
|
|
932
|
+
startAt=0,
|
|
933
|
+
maxResults=50,
|
|
934
|
+
includeActive=True,
|
|
935
|
+
includeInactive=False
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
for user in users:
|
|
939
|
+
print(f'{user.displayName} - {user.emailAddress}')
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
#### Search Assignable Users
|
|
943
|
+
|
|
944
|
+
```python
|
|
945
|
+
# Search for users who can be assigned to issues in a project
|
|
946
|
+
users = jira.search_assignable_users_for_projects(
|
|
947
|
+
username='', # Empty string to get all
|
|
948
|
+
projectKeys='PROJ',
|
|
949
|
+
startAt=0,
|
|
950
|
+
maxResults=50
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
for user in users:
|
|
954
|
+
print(f'{user.displayName} can be assigned to PROJ')
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### Watchers
|
|
958
|
+
|
|
959
|
+
#### Get Watchers
|
|
960
|
+
|
|
961
|
+
```python
|
|
962
|
+
# Get watchers for an issue
|
|
963
|
+
issue = jira.issue('PROJ-123')
|
|
964
|
+
watchers = jira.watchers(issue)
|
|
965
|
+
|
|
966
|
+
print(f'Watch Count: {watchers.watchCount}')
|
|
967
|
+
print('Watchers:')
|
|
968
|
+
for watcher in watchers.watchers:
|
|
969
|
+
print(f' {watcher.displayName}')
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
#### Add Watcher
|
|
973
|
+
|
|
974
|
+
```python
|
|
975
|
+
# Add a watcher to an issue
|
|
976
|
+
jira.add_watcher('PROJ-123', 'username')
|
|
977
|
+
|
|
978
|
+
# Or use accountId
|
|
979
|
+
jira.add_watcher('PROJ-123', '5b10a2844c20165700ede21g')
|
|
980
|
+
|
|
981
|
+
print('Watcher added successfully')
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
#### Remove Watcher
|
|
985
|
+
|
|
986
|
+
```python
|
|
987
|
+
# Remove a watcher from an issue
|
|
988
|
+
jira.remove_watcher('PROJ-123', 'username')
|
|
989
|
+
|
|
990
|
+
print('Watcher removed successfully')
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
### Labels
|
|
994
|
+
|
|
995
|
+
#### Get Issue Labels
|
|
996
|
+
|
|
997
|
+
```python
|
|
998
|
+
# Get labels for an issue
|
|
999
|
+
issue = jira.issue('PROJ-123')
|
|
1000
|
+
labels = issue.fields.labels
|
|
1001
|
+
|
|
1002
|
+
print('Labels:', labels)
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
#### Update Labels
|
|
1006
|
+
|
|
1007
|
+
```python
|
|
1008
|
+
# Add labels to an issue
|
|
1009
|
+
issue = jira.issue('PROJ-123')
|
|
1010
|
+
issue.update(fields={'labels': ['backend', 'api', 'urgent']})
|
|
1011
|
+
|
|
1012
|
+
# Append labels (preserve existing)
|
|
1013
|
+
existing_labels = issue.fields.labels
|
|
1014
|
+
new_labels = existing_labels + ['new-label']
|
|
1015
|
+
issue.update(fields={'labels': new_labels})
|
|
1016
|
+
|
|
1017
|
+
print('Labels updated successfully')
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
### Filters
|
|
1021
|
+
|
|
1022
|
+
#### Get Favorite Filters
|
|
1023
|
+
|
|
1024
|
+
```python
|
|
1025
|
+
# Get all favorite filters for current user
|
|
1026
|
+
filters = jira.favourite_filters()
|
|
1027
|
+
|
|
1028
|
+
for f in filters:
|
|
1029
|
+
print(f'{f.id}: {f.name}')
|
|
1030
|
+
print(f' JQL: {f.jql}')
|
|
1031
|
+
print(f' Owner: {f.owner.displayName}')
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
#### Get Filter
|
|
1035
|
+
|
|
1036
|
+
```python
|
|
1037
|
+
# Get a specific filter by ID
|
|
1038
|
+
filter_obj = jira.filter('10000')
|
|
1039
|
+
|
|
1040
|
+
print(f'Filter: {filter_obj.name}')
|
|
1041
|
+
print(f'JQL: {filter_obj.jql}')
|
|
1042
|
+
print(f'Owner: {filter_obj.owner.displayName}')
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
#### Search Using Filter
|
|
1046
|
+
|
|
1047
|
+
```python
|
|
1048
|
+
# Execute a saved filter
|
|
1049
|
+
filter_id = 10000
|
|
1050
|
+
jql = f'filter = {filter_id}'
|
|
1051
|
+
issues = jira.search_issues(jql, maxResults=100)
|
|
1052
|
+
|
|
1053
|
+
print(f'Filter results: {len(issues)} issues')
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
### Versions (Releases)
|
|
1057
|
+
|
|
1058
|
+
#### Create Version
|
|
1059
|
+
|
|
1060
|
+
```python
|
|
1061
|
+
# Create a new version/release
|
|
1062
|
+
version = jira.create_version(
|
|
1063
|
+
name='v1.2.0',
|
|
1064
|
+
project='PROJ',
|
|
1065
|
+
description='Q4 2025 Release',
|
|
1066
|
+
releaseDate='2025-12-15',
|
|
1067
|
+
archived=False,
|
|
1068
|
+
released=False
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
print(f'Created version: {version.name} (ID: {version.id})')
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
#### Get Version
|
|
1075
|
+
|
|
1076
|
+
```python
|
|
1077
|
+
# Get a specific version
|
|
1078
|
+
version = jira.version('10001')
|
|
1079
|
+
|
|
1080
|
+
print(f'Version: {version.name}')
|
|
1081
|
+
print(f'Released: {version.released}')
|
|
1082
|
+
if hasattr(version, 'releaseDate'):
|
|
1083
|
+
print(f'Release Date: {version.releaseDate}')
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
#### Update Version
|
|
1087
|
+
|
|
1088
|
+
```python
|
|
1089
|
+
# Update a version
|
|
1090
|
+
version = jira.version('10001')
|
|
1091
|
+
version.update(
|
|
1092
|
+
name='v1.2.1',
|
|
1093
|
+
description='Updated release',
|
|
1094
|
+
released=True,
|
|
1095
|
+
releaseDate='2025-12-20'
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
print('Version updated successfully')
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
#### Delete Version
|
|
1102
|
+
|
|
1103
|
+
```python
|
|
1104
|
+
# Delete a version
|
|
1105
|
+
version = jira.version('10001')
|
|
1106
|
+
version.delete()
|
|
1107
|
+
|
|
1108
|
+
print('Version deleted successfully')
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
### Components
|
|
1112
|
+
|
|
1113
|
+
#### Create Component
|
|
1114
|
+
|
|
1115
|
+
```python
|
|
1116
|
+
# Create a new component
|
|
1117
|
+
component = jira.create_component(
|
|
1118
|
+
name='Frontend',
|
|
1119
|
+
project='PROJ',
|
|
1120
|
+
description='Frontend components and pages',
|
|
1121
|
+
leadAccountId='5b10a2844c20165700ede21g'
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
print(f'Created component: {component.name} (ID: {component.id})')
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
#### Get Component
|
|
1128
|
+
|
|
1129
|
+
```python
|
|
1130
|
+
# Get a specific component
|
|
1131
|
+
component = jira.component('10000')
|
|
1132
|
+
|
|
1133
|
+
print(f'Component: {component.name}')
|
|
1134
|
+
print(f'Description: {component.description}')
|
|
1135
|
+
if component.lead:
|
|
1136
|
+
print(f'Lead: {component.lead.displayName}')
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
#### Update Component
|
|
1140
|
+
|
|
1141
|
+
```python
|
|
1142
|
+
# Update a component
|
|
1143
|
+
component = jira.component('10000')
|
|
1144
|
+
component.update(
|
|
1145
|
+
name='Updated Frontend',
|
|
1146
|
+
description='Updated description'
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
print('Component updated successfully')
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
### Workflows
|
|
1153
|
+
|
|
1154
|
+
#### Get Workflows
|
|
1155
|
+
|
|
1156
|
+
```python
|
|
1157
|
+
# Get all workflows (requires project admin permissions)
|
|
1158
|
+
workflows = jira.workflows()
|
|
1159
|
+
|
|
1160
|
+
for workflow in workflows:
|
|
1161
|
+
print(f'{workflow.name}')
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
### Custom Fields
|
|
1165
|
+
|
|
1166
|
+
#### Get All Fields
|
|
1167
|
+
|
|
1168
|
+
```python
|
|
1169
|
+
# Get all fields in Jira
|
|
1170
|
+
fields = jira.fields()
|
|
1171
|
+
|
|
1172
|
+
print('Custom Fields:')
|
|
1173
|
+
for field in fields:
|
|
1174
|
+
if field['custom']:
|
|
1175
|
+
print(f"{field['id']}: {field['name']}")
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
#### Get Custom Field Value
|
|
1179
|
+
|
|
1180
|
+
```python
|
|
1181
|
+
# Get custom field value from an issue
|
|
1182
|
+
issue = jira.issue('PROJ-123')
|
|
1183
|
+
custom_field_id = 'customfield_10001'
|
|
1184
|
+
|
|
1185
|
+
if hasattr(issue.fields, custom_field_id):
|
|
1186
|
+
value = getattr(issue.fields, custom_field_id)
|
|
1187
|
+
print(f'Custom field value: {value}')
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
#### Set Custom Field Value
|
|
1191
|
+
|
|
1192
|
+
```python
|
|
1193
|
+
# Set custom field value
|
|
1194
|
+
issue = jira.issue('PROJ-123')
|
|
1195
|
+
issue.update(fields={'customfield_10001': 'New custom value'})
|
|
1196
|
+
|
|
1197
|
+
print('Custom field updated successfully')
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
### Permissions
|
|
1201
|
+
|
|
1202
|
+
#### Get My Permissions
|
|
1203
|
+
|
|
1204
|
+
```python
|
|
1205
|
+
# Get permissions for current user
|
|
1206
|
+
permissions = jira.my_permissions(projectKey='PROJ')
|
|
1207
|
+
|
|
1208
|
+
print('Permissions for project PROJ:')
|
|
1209
|
+
for key, permission in permissions['permissions'].items():
|
|
1210
|
+
if permission['havePermission']:
|
|
1211
|
+
print(f' {key}: Granted')
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
#### Check Specific Permission
|
|
1215
|
+
|
|
1216
|
+
```python
|
|
1217
|
+
# Check if user has specific permission
|
|
1218
|
+
permissions = jira.my_permissions(
|
|
1219
|
+
projectKey='PROJ',
|
|
1220
|
+
permissions='CREATE_ISSUES,EDIT_ISSUES'
|
|
1221
|
+
)
|
|
1222
|
+
|
|
1223
|
+
can_create = permissions['permissions']['CREATE_ISSUES']['havePermission']
|
|
1224
|
+
can_edit = permissions['permissions']['EDIT_ISSUES']['havePermission']
|
|
1225
|
+
|
|
1226
|
+
print(f'Can create issues: {can_create}')
|
|
1227
|
+
print(f'Can edit issues: {can_edit}')
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
## Common Patterns
|
|
1231
|
+
|
|
1232
|
+
### Batch Operations
|
|
1233
|
+
|
|
1234
|
+
```python
|
|
1235
|
+
# Create multiple issues efficiently
|
|
1236
|
+
def create_multiple_issues(jira, issues_data):
|
|
1237
|
+
"""Create multiple issues using bulk operation"""
|
|
1238
|
+
return jira.create_issues(field_list=issues_data)
|
|
1239
|
+
|
|
1240
|
+
# Usage
|
|
1241
|
+
issues_data = [
|
|
1242
|
+
{
|
|
1243
|
+
'project': {'key': 'PROJ'},
|
|
1244
|
+
'summary': f'Task {i}',
|
|
1245
|
+
'issuetype': {'name': 'Task'},
|
|
1246
|
+
}
|
|
1247
|
+
for i in range(1, 11)
|
|
1248
|
+
]
|
|
1249
|
+
|
|
1250
|
+
created_issues = create_multiple_issues(jira, issues_data)
|
|
1251
|
+
|
|
1252
|
+
for result in created_issues:
|
|
1253
|
+
if 'issue' in result:
|
|
1254
|
+
print(f"Created: {result['issue'].key}")
|
|
1255
|
+
else:
|
|
1256
|
+
print(f"Error: {result['error']}")
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
### Rate Limiting
|
|
1260
|
+
|
|
1261
|
+
```python
|
|
1262
|
+
import time
|
|
1263
|
+
|
|
1264
|
+
def rate_limited_operation(items, operation, rate_limit=10, delay=1):
|
|
1265
|
+
"""Execute operations with rate limiting"""
|
|
1266
|
+
results = []
|
|
1267
|
+
|
|
1268
|
+
for i, item in enumerate(items):
|
|
1269
|
+
result = operation(item)
|
|
1270
|
+
results.append(result)
|
|
1271
|
+
|
|
1272
|
+
# Add delay every rate_limit operations
|
|
1273
|
+
if (i + 1) % rate_limit == 0 and i < len(items) - 1:
|
|
1274
|
+
print(f'Processed {i + 1} items, waiting {delay}s...')
|
|
1275
|
+
time.sleep(delay)
|
|
1276
|
+
|
|
1277
|
+
return results
|
|
1278
|
+
|
|
1279
|
+
# Usage
|
|
1280
|
+
def update_issue(issue_key):
|
|
1281
|
+
issue = jira.issue(issue_key)
|
|
1282
|
+
issue.update(fields={'labels': ['batch-updated']})
|
|
1283
|
+
return issue_key
|
|
1284
|
+
|
|
1285
|
+
issue_keys = ['PROJ-1', 'PROJ-2', 'PROJ-3', 'PROJ-4', 'PROJ-5']
|
|
1286
|
+
results = rate_limited_operation(issue_keys, update_issue, rate_limit=2, delay=1)
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
### Retry Logic
|
|
1290
|
+
|
|
1291
|
+
```python
|
|
1292
|
+
import time
|
|
1293
|
+
from jira import JIRAError
|
|
1294
|
+
|
|
1295
|
+
def with_retry(operation, max_retries=3, delay=1):
|
|
1296
|
+
"""Execute operation with retry logic"""
|
|
1297
|
+
for attempt in range(1, max_retries + 1):
|
|
1298
|
+
try:
|
|
1299
|
+
return operation()
|
|
1300
|
+
except JIRAError as e:
|
|
1301
|
+
if attempt == max_retries:
|
|
1302
|
+
raise
|
|
1303
|
+
|
|
1304
|
+
print(f'Attempt {attempt} failed: {e.text}')
|
|
1305
|
+
print(f'Retrying in {delay}s...')
|
|
1306
|
+
time.sleep(delay)
|
|
1307
|
+
delay *= 2 # Exponential backoff
|
|
1308
|
+
|
|
1309
|
+
# Usage
|
|
1310
|
+
issue = with_retry(lambda: jira.issue('PROJ-123'))
|
|
1311
|
+
print(f'Retrieved: {issue.key}')
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
### Context Manager for Connection
|
|
1315
|
+
|
|
1316
|
+
```python
|
|
1317
|
+
from contextlib import contextmanager
|
|
1318
|
+
|
|
1319
|
+
@contextmanager
|
|
1320
|
+
def jira_connection(server, email, api_token):
|
|
1321
|
+
"""Context manager for Jira connection"""
|
|
1322
|
+
jira = JIRA(
|
|
1323
|
+
server=server,
|
|
1324
|
+
basic_auth=(email, api_token)
|
|
1325
|
+
)
|
|
1326
|
+
try:
|
|
1327
|
+
yield jira
|
|
1328
|
+
finally:
|
|
1329
|
+
jira.close()
|
|
1330
|
+
|
|
1331
|
+
# Usage
|
|
1332
|
+
with jira_connection(
|
|
1333
|
+
'https://your-domain.atlassian.net',
|
|
1334
|
+
'your@email.com',
|
|
1335
|
+
'your_token'
|
|
1336
|
+
) as jira:
|
|
1337
|
+
issues = jira.search_issues('project = PROJ', maxResults=10)
|
|
1338
|
+
for issue in issues:
|
|
1339
|
+
print(f'{issue.key}: {issue.fields.summary}')
|
|
1340
|
+
```
|
|
1341
|
+
|
|
1342
|
+
## Complete Application Example
|
|
1343
|
+
|
|
1344
|
+
```python
|
|
1345
|
+
import os
|
|
1346
|
+
from jira import JIRA, JIRAError
|
|
1347
|
+
|
|
1348
|
+
def main():
|
|
1349
|
+
# Initialize Jira client
|
|
1350
|
+
jira = JIRA(
|
|
1351
|
+
server=os.environ.get('JIRA_SERVER', 'https://your-domain.atlassian.net'),
|
|
1352
|
+
basic_auth=(
|
|
1353
|
+
os.environ.get('JIRA_EMAIL'),
|
|
1354
|
+
os.environ.get('JIRA_API_TOKEN')
|
|
1355
|
+
)
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1358
|
+
try:
|
|
1359
|
+
# Get current user
|
|
1360
|
+
current_user = jira.current_user()
|
|
1361
|
+
print(f'Logged in as: {current_user}')
|
|
1362
|
+
|
|
1363
|
+
# Create a new issue
|
|
1364
|
+
new_issue = jira.create_issue(
|
|
1365
|
+
project='PROJ',
|
|
1366
|
+
summary='Implement new feature',
|
|
1367
|
+
description='This is a new feature request from the API',
|
|
1368
|
+
issuetype={'name': 'Story'},
|
|
1369
|
+
priority={'name': 'High'}
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
print(f'Created issue: {new_issue.key}')
|
|
1373
|
+
|
|
1374
|
+
# Add a comment
|
|
1375
|
+
comment = jira.add_comment(
|
|
1376
|
+
new_issue.key,
|
|
1377
|
+
'Starting work on this issue'
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
print(f'Comment added: {comment.id}')
|
|
1381
|
+
|
|
1382
|
+
# Search for issues
|
|
1383
|
+
jql = 'project = PROJ AND status = "To Do" ORDER BY created DESC'
|
|
1384
|
+
issues = jira.search_issues(jql, maxResults=10)
|
|
1385
|
+
|
|
1386
|
+
print(f'\nFound {issues.total} issues:')
|
|
1387
|
+
for issue in issues:
|
|
1388
|
+
print(f' {issue.key}: {issue.fields.summary}')
|
|
1389
|
+
print(f' Status: {issue.fields.status.name}')
|
|
1390
|
+
print(f' Priority: {issue.fields.priority.name}')
|
|
1391
|
+
|
|
1392
|
+
# Get available transitions
|
|
1393
|
+
transitions = jira.transitions(new_issue.key)
|
|
1394
|
+
print(f'\nAvailable transitions for {new_issue.key}:')
|
|
1395
|
+
for t in transitions:
|
|
1396
|
+
print(f" {t['id']}: {t['name']}")
|
|
1397
|
+
|
|
1398
|
+
except JIRAError as e:
|
|
1399
|
+
print(f'JIRA Error: {e.status_code} - {e.text}')
|
|
1400
|
+
except Exception as e:
|
|
1401
|
+
print(f'Error: {e}')
|
|
1402
|
+
finally:
|
|
1403
|
+
jira.close()
|
|
1404
|
+
|
|
1405
|
+
if __name__ == '__main__':
|
|
1406
|
+
main()
|
|
1407
|
+
```
|
|
1408
|
+
|
|
1409
|
+
## Advanced Features
|
|
1410
|
+
|
|
1411
|
+
### Custom JQL Functions
|
|
1412
|
+
|
|
1413
|
+
```python
|
|
1414
|
+
# Use JQL functions for dynamic queries
|
|
1415
|
+
recent_issues = jira.search_issues('created >= startOfWeek()')
|
|
1416
|
+
my_overdue = jira.search_issues('assignee = currentUser() AND duedate < now()')
|
|
1417
|
+
unresolved_bugs = jira.search_issues('resolution = Unresolved AND type = Bug')
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
### Issue Links
|
|
1421
|
+
|
|
1422
|
+
```python
|
|
1423
|
+
# Create issue link
|
|
1424
|
+
jira.create_issue_link(
|
|
1425
|
+
type='Blocks',
|
|
1426
|
+
inwardIssue='PROJ-123',
|
|
1427
|
+
outwardIssue='PROJ-124'
|
|
1428
|
+
)
|
|
1429
|
+
|
|
1430
|
+
# Get issue links
|
|
1431
|
+
issue = jira.issue('PROJ-123')
|
|
1432
|
+
for link in issue.fields.issuelinks:
|
|
1433
|
+
if hasattr(link, 'outwardIssue'):
|
|
1434
|
+
print(f'Blocks: {link.outwardIssue.key}')
|
|
1435
|
+
if hasattr(link, 'inwardIssue'):
|
|
1436
|
+
print(f'Blocked by: {link.inwardIssue.key}')
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
### Work Logs
|
|
1440
|
+
|
|
1441
|
+
```python
|
|
1442
|
+
# Add work log to issue
|
|
1443
|
+
worklog = jira.add_worklog(
|
|
1444
|
+
issue='PROJ-123',
|
|
1445
|
+
timeSpent='2h 30m',
|
|
1446
|
+
comment='Worked on implementation'
|
|
1447
|
+
)
|
|
1448
|
+
|
|
1449
|
+
# Get work logs
|
|
1450
|
+
issue = jira.issue('PROJ-123')
|
|
1451
|
+
worklogs = jira.worklogs(issue)
|
|
1452
|
+
|
|
1453
|
+
for wl in worklogs:
|
|
1454
|
+
print(f'{wl.author.displayName}: {wl.timeSpent}')
|
|
1455
|
+
```
|
|
1456
|
+
|
|
1457
|
+
## Response Format Examples
|
|
1458
|
+
|
|
1459
|
+
### Issue Object
|
|
1460
|
+
|
|
1461
|
+
```python
|
|
1462
|
+
{
|
|
1463
|
+
'key': 'PROJ-123',
|
|
1464
|
+
'id': '10002',
|
|
1465
|
+
'fields': {
|
|
1466
|
+
'summary': 'Issue summary',
|
|
1467
|
+
'description': 'Issue description',
|
|
1468
|
+
'status': {'name': 'In Progress'},
|
|
1469
|
+
'priority': {'name': 'High'},
|
|
1470
|
+
'assignee': {
|
|
1471
|
+
'displayName': 'John Doe',
|
|
1472
|
+
'emailAddress': 'john@example.com',
|
|
1473
|
+
'accountId': '5b10a2844c20165700ede21g'
|
|
1474
|
+
},
|
|
1475
|
+
'reporter': {'displayName': 'Jane Smith'},
|
|
1476
|
+
'created': '2025-11-01T10:00:00.000+0000',
|
|
1477
|
+
'updated': '2025-11-07T15:30:00.000+0000',
|
|
1478
|
+
'labels': ['backend', 'api']
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
```
|
|
1482
|
+
|
|
1483
|
+
### Search Results
|
|
1484
|
+
|
|
1485
|
+
```python
|
|
1486
|
+
{
|
|
1487
|
+
'startAt': 0,
|
|
1488
|
+
'maxResults': 50,
|
|
1489
|
+
'total': 127,
|
|
1490
|
+
'issues': [<Issue object>, <Issue object>, ...]
|
|
1491
|
+
}
|
|
1492
|
+
```
|