Skip to content

Commit 8cd3227

Browse files
authored
Merge pull request #2 from atlassian-api/master
Updating
2 parents f4a9594 + 3e1dd5c commit 8cd3227

15 files changed

+543
-26
lines changed

atlassian/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.12.7
1+
1.12.16

atlassian/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,17 @@
77
from .crowd import Crowd
88
from .service_desk import ServiceDesk
99
from .marketplace import MarketPlace
10+
from .jira8 import Jira8
1011

11-
__all__ = ['Confluence', 'Jira', 'Bitbucket', 'Portfolio', 'Bamboo', 'Stash', 'Crowd', 'ServiceDesk', 'MarketPlace']
12+
__all__ = [
13+
'Confluence',
14+
'Jira',
15+
'Bitbucket',
16+
'Portfolio',
17+
'Bamboo',
18+
'Stash',
19+
'Crowd',
20+
'ServiceDesk',
21+
'MarketPlace',
22+
'Jira8'
23+
]

atlassian/bitbucket.py

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,66 @@ def project_users_with_administrator_permissions(self, key):
9191
project_administrators.append(user)
9292
return project_administrators
9393

94+
def project_grant_user_permissions(self, project_key, username, permission):
95+
"""
96+
Grant the specified project permission to an specific user
97+
:param project_key: project key involved
98+
:param username: user name to be granted
99+
:param permission: the project permissions available are 'PROJECT_ADMIN', 'PROJECT_WRITE' and 'PROJECT_READ'
100+
:return:
101+
"""
102+
url = 'rest/api/1.0/projects/{project_key}/permissions/users?permission={permission}&name={username}'.format(
103+
project_key=project_key,
104+
permission=permission,
105+
username=username)
106+
return self.put(url)
107+
108+
def project_grant_group_permissions(self, project_key, groupname, permission):
109+
"""
110+
Grant the specified project permission to an specific group
111+
:param project_key: project key involved
112+
:param groupname: group to be granted
113+
:param permission: the project permissions available are 'PROJECT_ADMIN', 'PROJECT_WRITE' and 'PROJECT_READ'
114+
:return:
115+
"""
116+
url = 'rest/api/1.0/projects/{project_key}/permissions/groups?permission={permission}&name={groupname}'.format(
117+
project_key=project_key,
118+
permission=permission,
119+
groupname=groupname)
120+
return self.put(url)
121+
122+
def repo_grant_user_permissions(self, project_key, repo_key, username, permission):
123+
"""
124+
Grant the specified repository permission to an specific user
125+
:param project_key: project key involved
126+
:param repo_key: repository key involved (slug)
127+
:param username: user name to be granted
128+
:param permission: the repository permissions available are 'REPO_ADMIN', 'REPO_WRITE' and 'REPO_READ'
129+
:return:
130+
"""
131+
url = 'rest/api/1.0/projects/{project_key}/repos/{repo_key}/permissions/users?permission={permission}&name={username}'.format(
132+
project_key=project_key,
133+
repo_key=repo_key,
134+
permission=permission,
135+
username=username)
136+
return self.put(url)
137+
138+
def repo_grant_group_permissions(self, project_key, repo_key, groupname, permission):
139+
"""
140+
Grant the specified repository permission to an specific group
141+
:param project_key: project key involved
142+
:param repo_key: repository key involved (slug)
143+
:param groupname: group to be granted
144+
:param permission: the repository permissions available are 'REPO_ADMIN', 'REPO_WRITE' and 'REPO_READ'
145+
:return:
146+
"""
147+
url = 'rest/api/1.0/projects/{project_key}/repos/{repo_key}/permissions/groups?permission={permission}&name={groupname}'.format(
148+
project_key=project_key,
149+
repo_key=repo_key,
150+
permission=permission,
151+
groupname=groupname)
152+
return self.put(url)
153+
94154
def project_groups(self, key, limit=99999, filter_str=None):
95155
"""
96156
Get Project Groups
@@ -398,8 +458,7 @@ def get_pull_requests_changes(self, project, repository, pull_request_id):
398458
project=project,
399459
repository=repository,
400460
pullRequestId=pull_request_id)
401-
params = {}
402-
params['start'] = 0
461+
params = {'start': 0}
403462
response = self.get(url, params=params)
404463
if 'values' not in response:
405464
return []
@@ -425,8 +484,7 @@ def get_pull_requests_commits(self, project, repository, pull_request_id):
425484
project=project,
426485
repository=repository,
427486
pullRequestId=pull_request_id)
428-
params = {}
429-
params['start'] = 0
487+
params = {'start': 0}
430488
response = self.get(url, params=params)
431489
if 'values' not in response:
432490
return []
@@ -450,8 +508,7 @@ def add_pull_request_comment(self, project, repository, pull_request_id, text):
450508
project=project,
451509
repository=repository,
452510
pullRequestId=pull_request_id)
453-
body = {}
454-
body['text'] = text
511+
body = {'text': text}
455512
return self.post(url, data=body)
456513

457514
def get_pullrequest(self, project, repository, pull_request_id):
@@ -877,3 +934,42 @@ def disable_branching_model(self, project, repository):
877934
project=project,
878935
repository=repository)
879936
return self.delete(url)
937+
938+
def get_assignable_users_for_issue(self, issue_key, username=None, start=0, limit=50):
939+
"""
940+
Provide assignable users for issue
941+
:param issue_key:
942+
:param username: OPTIONAL: Can be used to chaeck if user can be assigned
943+
:param start: OPTIONAL: The start point of the collection to return. Default: 0.
944+
:param limit: OPTIONAL: The limit of the number of users to return, this may be restricted by
945+
fixed system limits. Default by built-in method: 50
946+
:return:
947+
"""
948+
url = 'rest/api/2/user/assignable/search?issueKey={issue_key}&startAt={start}&maxResults={limit}'.format(
949+
issue_key=issue_key,
950+
start=start,
951+
limit=limit)
952+
if username:
953+
url += '&username={username}'.format(username=username)
954+
return self.get(url)
955+
956+
def get_issue_changelog(self, issue_key):
957+
"""
958+
Get issue related change log
959+
:param issue_key:
960+
:return:
961+
"""
962+
url = 'rest/api/2/issue/{}?expand=changelog'.format(issue_key)
963+
return (self.get(url) or {}).get('changelog')
964+
965+
def assign_issue(self, issue, assignee=None):
966+
"""Assign an issue to a user. None will set it to unassigned. -1 will set it to Automatic.
967+
:param issue: the issue ID or key to assign
968+
:type issue: int or str
969+
:param assignee: the user to assign the issue to
970+
:type assignee: str
971+
:rtype: bool
972+
"""
973+
url = 'rest/api/2/issue/{issue}/assignee'.format(issue=issue)
974+
data = {'name': assignee}
975+
return self.put(url, data=data)

atlassian/confluence.py

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from requests import HTTPError
44
import logging
55
import os
6+
import time
67

78
log = logging.getLogger(__name__)
89

@@ -278,25 +279,29 @@ def remove_page(self, page_id, status=None, recursive=False):
278279
params['status'] = status
279280
return self.delete(url, params=params)
280281

281-
def create_page(self, space, title, body, parent_id=None, type='page'):
282+
def create_page(self, space, title, body, parent_id=None, type='page',
283+
representation='storage'):
282284
"""
283285
Create page from scratch
284286
:param space:
285287
:param title:
286288
:param body:
287289
:param parent_id:
288290
:param type:
291+
:param representation: OPTIONAL: either Confluence 'storage' or 'wiki' markup format
289292
:return:
290293
"""
291294
log.info('Creating {type} "{space}" -> "{title}"'.format(space=space, title=title, type=type))
295+
if representation not in ['wiki', 'storage']:
296+
raise ValueError("Wrong value for representation, it should be either wiki or storage")
292297
url = 'rest/api/content/'
293298
data = {
294299
'type': type,
295300
'title': title,
296301
'space': {'key': space},
297-
'body': {'storage': {
302+
'body': {representation: {
298303
'value': body,
299-
'representation': 'storage'}}}
304+
'representation': representation}}}
300305
if parent_id:
301306
data['ancestors'] = [{'type': type, 'id': parent_id}]
302307
return self.post(url, data=data)
@@ -325,8 +330,8 @@ def add_comment(self, page_id, text):
325330
data = {'type': 'comment',
326331
'container': {'id': page_id, 'type': 'page', 'status': 'current'},
327332
'body': {'storage': {'value': text, 'representation': 'storage'}}}
328-
return self.post('rest/api/content/', data=data)
329-
333+
return self.post('rest/api/content/', data=data)
334+
330335
def attach_file(self, filename, page_id=None, title=None, space=None, comment=None):
331336
"""
332337
Attach (upload) a file to a page, if it exists it will update the
@@ -630,6 +635,28 @@ def get_space(self, space_key, expand='description.plain,homepage'):
630635
expand=expand)
631636
return self.get(url)
632637

638+
def create_space(self, space_key, space_name):
639+
"""
640+
Create space
641+
:param space_key:
642+
:param space_name:
643+
:return:
644+
"""
645+
data = {
646+
'key': space_key,
647+
'name': space_name
648+
}
649+
self.post('rest/api/space', data=data)
650+
651+
def delete_space(self, space_key):
652+
"""
653+
Delete space
654+
:param space_key:
655+
:return:
656+
"""
657+
url = 'rest/api/space/{}'.format(space_key)
658+
return self.delete(url)
659+
633660
def get_user_details_by_username(self, username, expand=None):
634661
"""
635662
Get information about a user through username
@@ -701,6 +728,9 @@ def get_page_as_pdf(self, page_id):
701728
"""
702729
headers = self.form_token_headers
703730
url = 'spaces/flyingpdf/pdfpageexport.action?pageId={pageId}'.format(pageId=page_id)
731+
if self.api_version == 'cloud':
732+
url = self.get_pdf_download_url_for_confluence_cloud(url)
733+
704734
return self.get(url, headers=headers, not_json_response=True)
705735

706736
def export_page(self, page_id):
@@ -761,3 +791,50 @@ def health_check(self):
761791
response = self.get('rest/supportHealthCheck/1.0/check/')
762792
return response
763793

794+
def get_pdf_download_url_for_confluence_cloud(self, url):
795+
"""
796+
Confluence cloud does not return the PDF document when the PDF
797+
export is initiated. Instead it starts a process in the background
798+
and provides a link to download the PDF once the process completes.
799+
This functions polls the long running task page and returns the
800+
download url of the PDF.
801+
:param url: URL to initiate PDF export
802+
:return: Download url for PDF file
803+
"""
804+
download_url = None
805+
try:
806+
long_running_task = True
807+
headers = self.form_token_headers
808+
log.info('Initiate PDF export from Confluence Cloud')
809+
response = self.get(url, headers=headers, not_json_response=True)
810+
response_string = response.decode(encoding='utf-8', errors='strict')
811+
task_id = response_string.split('name="ajs-taskId" content="')[1].split('">')[0]
812+
poll_url = 'runningtaskxml.action?taskId={0}'.format(task_id)
813+
while long_running_task:
814+
long_running_task_response = self.get(poll_url, headers=headers, not_json_response=True)
815+
long_running_task_response_parts = long_running_task_response.decode(encoding='utf-8',
816+
errors='strict').split('\n')
817+
percentage_complete = long_running_task_response_parts[6].strip()
818+
is_successful = long_running_task_response_parts[7].strip()
819+
is_complete = long_running_task_response_parts[8].strip()
820+
log.info('Sleep for 5s.')
821+
time.sleep(5)
822+
log.info('Check if export task has completed.')
823+
if is_complete == '<isComplete>true</isComplete>':
824+
if is_successful == '<isSuccessful>true</isSuccessful>':
825+
log.info(percentage_complete)
826+
log.info('Downloading content...')
827+
log.debug('Extract taskId and download PDF.')
828+
current_status = long_running_task_response_parts[3]
829+
download_url = current_status.split('href=&quot;/wiki/')[1].split('&quot')[0]
830+
long_running_task = False
831+
elif is_successful == '<isSuccessful>false</isSuccessful>':
832+
log.error('PDF conversion not successful.')
833+
return None
834+
else:
835+
log.info(percentage_complete)
836+
except IndexError as e:
837+
log.error(e)
838+
return None
839+
840+
return download_url

0 commit comments

Comments
 (0)