├── doc
├── source
│ ├── _static
│ │ └── tmp.txt
│ ├── _templates
│ │ └── tmp.txt
│ ├── query.rst
│ ├── client.rst
│ ├── template.rst
│ ├── workitem.rst
│ ├── projectarea.rst
│ ├── installation.rst
│ ├── models.rst
│ ├── index.rst
│ ├── advanced_usage.rst
│ ├── introduction.rst
│ ├── workitem_attr.rst
│ ├── conf.py
│ └── quickstart.rst
├── requirements.txt
├── Makefile
└── make.bat
├── MANIFEST.in
├── setup.cfg
├── rtcclient
├── exception.py
├── __init__.py
├── templates
│ └── issue_example.template
├── utils.py
├── models.py
├── project_area.py
└── query.py
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── tests
├── conftest.py
├── fixtures
│ ├── administrators.xml
│ ├── changesets.xml
│ ├── comments.xml
│ ├── projectareas.xml
│ ├── issue_example.template
│ ├── roles.xml
│ ├── priorities.xml
│ ├── severities.xml
│ ├── states.xml
│ ├── filedagainsts.xml
│ ├── teamareas.xml
│ ├── itemtypes.xml
│ ├── foundins.xml
│ ├── members.xml
│ ├── actions.xml
│ ├── plannedfors.xml
│ ├── attachment.xml
│ ├── includedinbuilds.xml
│ ├── savedqueries.xml
│ └── parent.xml
├── test_template.py
├── test_base.py
├── utils_test.py
└── test_projectarea.py
├── examples
└── how_to
│ ├── workitem
│ ├── add_comment.py
│ └── get_workitem.py
│ └── query
│ └── query_workitems.py
├── .gitignore
├── .github
└── workflows
│ ├── release.yml
│ └── ci.yml
├── tox.ini
├── pyproject.toml
├── README.rst
└── LICENSE
/doc/source/_static/tmp.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/source/_templates/tmp.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/requirements.txt:
--------------------------------------------------------------------------------
1 | xmltodict
2 | six
3 | lxml
4 |
--------------------------------------------------------------------------------
/doc/source/query.rst:
--------------------------------------------------------------------------------
1 | .. _query_api:
2 |
3 | Query
4 | =====
5 |
6 | .. autoclass:: rtcclient.query.Query
7 | :members:
8 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include AUTHORS
2 | include ChangeLog
3 |
4 | exclude .gitignore
5 | exclude .gitreview
6 |
7 | global-exclude *.pyc
8 |
--------------------------------------------------------------------------------
/doc/source/client.rst:
--------------------------------------------------------------------------------
1 | .. _client_api:
2 |
3 | Client
4 | ======
5 |
6 | .. autoclass:: rtcclient.client.RTCClient
7 | :members:
8 |
--------------------------------------------------------------------------------
/doc/source/template.rst:
--------------------------------------------------------------------------------
1 | .. _template_api:
2 |
3 | Template
4 | ========
5 |
6 | .. autoclass:: rtcclient.template.Templater
7 | :members:
8 |
--------------------------------------------------------------------------------
/doc/source/workitem.rst:
--------------------------------------------------------------------------------
1 | .. _workitem_api:
2 |
3 | Workitem
4 | ========
5 |
6 | .. autoclass:: rtcclient.workitem.Workitem
7 | :members:
8 |
--------------------------------------------------------------------------------
/doc/source/projectarea.rst:
--------------------------------------------------------------------------------
1 | .. _projectarea_api:
2 |
3 | ProjectArea
4 | ===========
5 |
6 | .. autoclass:: rtcclient.project_area.ProjectArea
7 | :members:
8 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [build_sphinx]
2 | all_files = 1
3 | build-dir = doc/build
4 | source-dir = doc/source
5 | builders = html,man,linkcheck
6 |
7 | [bdist_wheel]
8 | universal=0
9 |
--------------------------------------------------------------------------------
/rtcclient/exception.py:
--------------------------------------------------------------------------------
1 | class RTCException(Exception):
2 | """Base exception class for all errors
3 | """
4 |
5 | pass
6 |
7 |
8 | class BadValue(RTCException):
9 | pass
10 |
11 |
12 | class NotFound(RTCException):
13 | pass
14 |
15 |
16 | class EmptyAttrib(RTCException):
17 | pass
18 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: git://github.com/pre-commit/mirrors-autopep8
3 | rev: v1.3
4 | hooks:
5 | - id: autopep8
6 | exclude: doc/*/*
7 |
8 | - repo: git://github.com/pre-commit/pre-commit-hooks
9 | rev: v0.7.0
10 | hooks:
11 | - id: check-json
12 | - id: check-xml
13 | - id: check-yaml
14 | - id: debug-statements
15 | - id: flake8
16 | - id: requirements-txt-fixer
17 | - id: trailing-whitespace
18 | exclude: doc/*/*
19 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yaml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Set the version of Python and other tools you might need
9 | build:
10 | os: ubuntu-22.04
11 | tools:
12 | python: "3.11"
13 |
14 | # Build documentation in the docs/ directory with Sphinx
15 | sphinx:
16 | configuration: doc/source/conf.py
17 |
18 | # We recommend specifying your dependencies to enable reproducible builds:
19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
20 | python:
21 | install:
22 | - requirements: doc/requirements.txt
23 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from rtcclient.client import RTCClient
3 | import requests
4 | from utils_test import _search_path
5 |
6 |
7 | @pytest.fixture(scope="function")
8 | def rtcclient(mocker):
9 | mock_resp = mocker.MagicMock(spec=requests.Response)
10 | mock_resp.status_code = 200
11 | mock_resp.headers = {"set-cookie": "cookie-id"}
12 |
13 | mocked_headers = mocker.patch("rtcclient.client.RTCClient._get_headers")
14 | mocked_headers.return_value = mock_resp
15 |
16 | return RTCClient(url="http://test.url:9443/jazz",
17 | username="tester1@email.com",
18 | password="password",
19 | searchpath=_search_path)
20 |
--------------------------------------------------------------------------------
/tests/fixtures/administrators.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | tester1@email.com
4 | tester1
5 | mailto:tester1%40email.com
6 |
7 | 2009-08-17T10:08:03.721Z
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/how_to/workitem/add_comment.py:
--------------------------------------------------------------------------------
1 | from rtcclient.client import RTCClient
2 | from rtcclient.utils import setup_basic_logging
3 |
4 | if __name__ == "__main__":
5 | # you can remove this if you don't need logging
6 | # default logging for console output
7 | setup_basic_logging()
8 |
9 | url = "https://your_domain:9443/jazz"
10 | username = "your_username"
11 | password = "your_password"
12 | # If your rtc server is too old (such as Rational Team Concert 5.0.1, 5.0.2),
13 | # please set old_rtc_authentication to True.
14 | # Other kwargs, such as ends_with_jazz, old_rtc_authentication
15 | myclient = RTCClient(url, username, password, old_rtc_authentication=False)
16 |
17 | # change workitem id here
18 | workitem_id = 123456
19 | wk = myclient.getWorkitem(workitem_id)
20 | wk.addComment("changeme: add comments here")
21 |
--------------------------------------------------------------------------------
/tests/fixtures/changesets.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/tests/fixtures/comments.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2015-07-27T02:35:47.391Z
4 |
5 | comment test
6 |
7 |
8 | 2015-07-27T10:48:55.197Z
9 |
10 | add comment test2
11 |
12 |
--------------------------------------------------------------------------------
/tests/fixtures/projectareas.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ProjectArea1
4 | Demo for test: Project Area One
5 |
6 | true
7 | true
8 |
9 |
10 | ProjectArea2
11 | Demo for test: Project Area Two
12 |
13 | true
14 | false
15 |
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
59 | # Intellij
60 | .idea
61 |
62 | # virtual env
63 | venv/
64 | .venv/
65 |
66 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_example.template:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 |
7 |
8 | {{ description }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ title }}
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/how_to/workitem/get_workitem.py:
--------------------------------------------------------------------------------
1 | from rtcclient.client import RTCClient
2 | from rtcclient.utils import setup_basic_logging
3 |
4 | if __name__ == "__main__":
5 | # you can remove this if you don't need logging
6 | # default logging for console output
7 | setup_basic_logging()
8 |
9 | url = "https://your_domain:9443/jazz"
10 | username = "your_username"
11 | password = "your_password"
12 | # If your rtc server is too old (such as Rational Team Concert 5.0.1, 5.0.2),
13 | # please set old_rtc_authentication to True.
14 | # Other kwargs, such as ends_with_jazz, old_rtc_authentication
15 | myclient = RTCClient(url, username, password, old_rtc_authentication=False)
16 |
17 | # get all workitems
18 | # If both projectarea_id and projectarea_name are None, all the workitems
19 | # in all ProjectAreas will be returned
20 | workitems_list = myclient.getWorkitems(projectarea_id=None,
21 | projectarea_name=None)
22 |
23 | # get a workitem with its id
24 | workitem_id = 123456
25 | wk = myclient.getWorkitem(workitem_id)
26 |
--------------------------------------------------------------------------------
/tests/fixtures/roles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://test.url:9443/jazz/process/project-areas/_vsoCMN-TEd6VxeRBsGx82Q/roles/Product%20Owner
5 |
6 | Product Owner
7 | Product Owner
8 |
9 | The person responsible for managing the Product Backlog.
10 |
11 |
12 |
13 |
14 | http://test.url:9443/jazz/process/project-areas/_vsoCMN-TEd6VxeRBsGx82Q/roles/Test%20Team
15 |
16 | Test Team
17 | Test Team
18 | A member of the cross-functional team.
19 |
20 |
21 |
22 | http://test.url:9443/jazz/process/project-areas/_vsoCMN-TEd6VxeRBsGx82Q/roles/default
23 |
24 | default
25 | default
26 |
27 | The default role, implicitly assigned to each user. This role cannot be assigned, removed, or reordered.
28 |
29 |
30 |
--------------------------------------------------------------------------------
/doc/source/installation.rst:
--------------------------------------------------------------------------------
1 | .. _install:
2 |
3 | Installation
4 | ============
5 |
6 | This part of the documentation covers the installation of rtcclient.
7 | The first step to using any software package is getting it properly installed.
8 |
9 |
10 | Distribute & Pip
11 | ----------------
12 |
13 | Installing rtcclient is simple with `pip `_, just run
14 | this in your terminal::
15 |
16 | $ pip install rtcclient
17 |
18 | or, with `easy_install `_::
19 |
20 | $ easy_install rtcclient
21 |
22 |
23 | Get from the Source Code
24 | ------------------------
25 |
26 | RTCClient is actively developed on GitHub, where the code is
27 | `always available `_.
28 |
29 | You can either clone the public repository and checkout released tags
30 | (e.g. tag 0.1.dev95)::
31 |
32 | $ git clone git://github.com/dixudx/rtcclient.git
33 | $ cd rtcclient
34 | $ git checkout tags/0.1.dev95
35 |
36 | Once you have a copy of the source, you can embed it in your Python package,
37 | or install it into your site-packages easily::
38 |
39 | $ python setup.py install
40 |
--------------------------------------------------------------------------------
/rtcclient/__init__.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | try:
4 | requests.packages.urllib3.disable_warnings()
5 | except ImportError:
6 | pass
7 |
8 | try:
9 | import urlparse
10 | from urllib import quote as urlquote # noqa: F401
11 | from urllib import urlencode
12 | from urllib import unquote as urlunquote
13 | except ImportError:
14 | # Python3
15 | import urllib.parse as urlparse # noqa: F401
16 | from urllib.parse import quote as urlquote # noqa: F401
17 | from urllib.parse import urlencode # noqa: F401
18 | from urllib.parse import unquote as urlunquote # noqa: F401
19 |
20 | try: # pragma no cover
21 | import xmltodict
22 | _xml_to_dict_version = [int(x) for x in xmltodict.__version__.split('.')]
23 | # on xmltodict >= 0.13 it returns a dict
24 | if _xml_to_dict_version[0] == 0 and _xml_to_dict_version[1] >= 13:
25 | OrderedDict = dict
26 | else:
27 | from collections import OrderedDict
28 | except ImportError: # pragma no cover
29 | try:
30 | from ordereddict import OrderedDict
31 | except ImportError:
32 | OrderedDict = dict
33 |
34 | from rtcclient.client import RTCClient # noqa: F401
35 |
--------------------------------------------------------------------------------
/tests/fixtures/priorities.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | priority.literal.l01
4 | Unassigned
5 |
6 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/enumeration/unassigned.gif
7 |
8 |
9 |
10 | priority.literal.l11
11 | High
12 |
13 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/enumeration/high.gif
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/fixtures/severities.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | severity.literal.l1
4 | Unclassified
5 |
6 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/enumeration/unassigned2.gif
7 |
8 |
9 |
10 | severity.literal.l2
11 | Normal
12 |
13 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/enumeration/normal.gif
14 |
15 |
16 |
--------------------------------------------------------------------------------
/rtcclient/templates/issue_example.template:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 | {{ description }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{ title }}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*.*.*'
7 |
8 | jobs:
9 | release:
10 | name: Release
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v3
15 |
16 | - name: Set up Python
17 | uses: actions/setup-python@v4
18 | with:
19 | python-version: "3.10"
20 | - name: Display Python version
21 | run: python --version
22 |
23 | - name: Setup Poetry
24 | uses: abatilo/actions-poetry@v2
25 |
26 | - name: Build project for distribution
27 | run: poetry build
28 |
29 | - name: Check Version
30 | id: check-version
31 | run: |
32 | [[ "$(poetry version --short)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || echo prerelease=true >> $GITHUB_OUTPUT
33 |
34 | - name: Create Release
35 | uses: ncipollo/release-action@v1
36 | with:
37 | artifacts: "dist/*"
38 | token: ${{ secrets.GITHUB_TOKEN }}
39 | draft: false
40 | prerelease: steps.check-version.outputs.prerelease == 'true'
41 |
42 | - name: Publish to PyPI
43 | env:
44 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
45 | run: poetry publish
46 |
--------------------------------------------------------------------------------
/tests/fixtures/states.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | default_workflow.state.s1
4 | Closed
5 |
6 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/workflow/close.gif
7 |
8 | inprogress
9 |
10 |
11 | default_workflow.state.s2
12 | In Progress
13 |
14 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/workflow/inprogress.gif
15 |
16 | closed
17 |
18 |
--------------------------------------------------------------------------------
/doc/source/models.rst:
--------------------------------------------------------------------------------
1 | .. _models_api:
2 |
3 | Models
4 | ======
5 |
6 |
7 | .. autoclass:: rtcclient.models.Role
8 | :members:
9 |
10 |
11 | .. autoclass:: rtcclient.models.Member
12 | :members:
13 |
14 |
15 | .. autoclass:: rtcclient.models.Administrator
16 | :members:
17 |
18 |
19 | .. autoclass:: rtcclient.models.ItemType
20 | :members:
21 |
22 |
23 | .. autoclass:: rtcclient.models.TeamArea
24 | :members:
25 |
26 |
27 | .. autoclass:: rtcclient.models.PlannedFor
28 | :members:
29 |
30 |
31 | .. autoclass:: rtcclient.models.FiledAgainst
32 | :members:
33 |
34 |
35 | .. autoclass:: rtcclient.models.FoundIn
36 | :members:
37 |
38 |
39 | .. autoclass:: rtcclient.models.Severity
40 | :members:
41 |
42 |
43 | .. autoclass:: rtcclient.models.Priority
44 | :members:
45 |
46 | .. autoclass:: rtcclient.models.Action
47 | :members:
48 |
49 |
50 | .. autoclass:: rtcclient.models.State
51 | :members:
52 |
53 |
54 | .. autoclass:: rtcclient.models.Comment
55 | :members:
56 |
57 |
58 | .. autoclass:: rtcclient.models.SavedQuery
59 | :members:
60 |
61 |
62 | .. autoclass:: rtcclient.models.IncludedInBuild
63 | :members:
64 |
65 |
66 | .. autoclass:: rtcclient.models.ChangeSet
67 | :members:
68 |
69 | .. autoclass:: rtcclient.models.Change
70 | :members:
71 |
--------------------------------------------------------------------------------
/tests/fixtures/filedagainsts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Unassigned
5 | Unassigned
6 |
7 |
8 | 0
9 | true
10 |
11 |
12 |
13 | Category 1
14 | Category 1
15 | Category to organize your work items.
16 |
17 | 0
18 | false
19 |
20 |
--------------------------------------------------------------------------------
/tests/fixtures/teamareas.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Team1
4 |
5 | false
6 |
7 |
8 |
9 |
10 |
11 | Team2
12 |
13 | false
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/fixtures/itemtypes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | defect
4 | Defect
5 |
6 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/workitemtype/bug.gif
7 |
8 |
9 | defect_task
10 |
11 |
12 |
13 | task
14 | Task
15 |
16 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/workitemtype/task.gif
17 |
18 |
19 | task
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/fixtures/foundins.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sprint1
4 |
5 |
6 | true
7 | true
8 |
9 |
10 | 2009-11-05T11:36:00.596Z
11 |
12 |
13 |
14 | Sprint2
15 |
16 |
17 | false
18 | false
19 |
20 |
21 | 2015-07-21T01:46:12.096Z
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tests/fixtures/members.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | tester1@email.com
4 | tester1
5 | mailto:tester1%40email.com
6 |
7 | 2009-11-24T19:14:14.595Z
8 |
9 |
10 |
11 | tester2@email.com
12 | tester2
13 | mailto:tester2%40email.com
14 |
15 | 2013-04-22T06:24:34.661Z
16 |
17 |
18 |
19 | tester3@email.com
20 | tester3
21 | mailto:tester3%40email.com
22 |
23 | 2010-05-13T20:34:05.138Z
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/fixtures/actions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | default_workflow.action.a1
4 |
5 | Close
6 |
7 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/workflow/close.gif
8 |
9 |
10 |
11 | default_workflow.action.a2
12 |
13 | Start Working
14 |
15 | http://test.url:9443/jazz/service/com.ibm.team.workitem.common.internal.model.IImageContentService/processattachment/_CuZu0HUwEeKicpXBddtqNA/workflow/inprogress.gif
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | minversion = 3.18
3 | envlist = test,pycodestyle,flake
4 | skipsdist = True
5 | isolated_build = True
6 |
7 | [testenv:test]
8 | description = Invoke pytest to run automated tests
9 | sitepackages = True
10 | isolated_build = True
11 | allowlist_externals =
12 | poetry
13 | setenv =
14 | TOXINIDIR = {toxinidir}
15 | VIRTUAL_ENV = {envdir}
16 | commands =
17 | poetry run pytest -v {posargs}
18 |
19 | [testenv:pycodestyle]
20 | description = Check for PEP8 compliance of code.
21 | skip_install = true
22 | isolated_build = True
23 | allowlist_externals =
24 | poetry
25 | commands =
26 | poetry run pycodestyle rtcclient tests --show-source --show-pep8 -v {posargs}
27 |
28 | [testenv:flake]
29 | description = Check for PEP8 compliance of code with flake.
30 | skip_install = true
31 | isolated_build = True
32 | allowlist_externals =
33 | poetry
34 | commands =
35 | poetry run flake8 rtcclient tests/ --count --select=E9,F63,F7,F82 --show-source --statistics
36 | poetry run flake8 rtcclient tests/ --count --max-complexity=20 --max-line-length=127 --statistics
37 |
38 | [testenv:format]
39 | description = Autoformat code.
40 | skip_install = true
41 | isolated_build = True
42 | allowlist_externals =
43 | poetry
44 | commands =
45 | poetry run yapf --style google --recursive --in-place rtcclient tests
46 |
47 | [pycodestyle]
48 | count = False
49 | ignore = E226,E302,E41,W504,E722
50 | max-line-length = 160
51 | statistics = True
52 | exclude = .venv,.tox,dist,doc,build,*.egg,.git,.eggs,__init__.py,__pycache__,.pytest_cache
53 |
--------------------------------------------------------------------------------
/examples/how_to/query/query_workitems.py:
--------------------------------------------------------------------------------
1 | from rtcclient.client import RTCClient
2 | from rtcclient.utils import setup_basic_logging
3 |
4 | if __name__ == "__main__":
5 | # you can remove this if you don't need logging
6 | # default logging for console output
7 | setup_basic_logging()
8 |
9 | url = "https://your_domain:9443/jazz"
10 | username = "your_username"
11 | password = "your_password"
12 | projectarea_name = "your_projectarea_name"
13 | # If your rtc server is too old (such as Rational Team Concert 5.0.1, 5.0.2),
14 | # please set old_rtc_authentication to True.
15 | # Other kwargs, such as ends_with_jazz, old_rtc_authentication
16 | myclient = RTCClient(url, username, password, old_rtc_authentication=False)
17 |
18 | # query starts here
19 | myquery = myclient.query
20 |
21 | # customize your query string
22 | # below query string means: query all the workitems whose title
23 | # is "use case 1"
24 | myquerystr = 'dc:title="use case 1"'
25 |
26 | # to create complex query string, fields are appended with " and ", an example
27 | # myquerystr = 'dc:title="use case 1" and dc:type="workitem_type"'
28 |
29 | # specify the returned properties: title, id, state, owner
30 | # This is optional. All properties will be returned if not specified
31 | returned_prop = "dc:title,dc:identifier,rtc_cm:state,rtc_cm:ownedBy"
32 |
33 | queried_wis = myquery.queryWorkitems(query_str=myquerystr,
34 | projectarea_name=projectarea_name,
35 | returned_properties=returned_prop)
36 |
--------------------------------------------------------------------------------
/tests/fixtures/plannedfors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1.0
4 | Release 1.0
5 |
6 | 2009-11-02T06:00:00.000Z
7 | 2009-12-12T06:00:00.000Z
8 |
9 | true
10 | true
11 |
12 |
13 |
14 |
15 | 1.0 S1
16 | Sprint 1 (1.0)
17 |
18 | 2013-02-12T06:00:00.000Z
19 | 2013-03-04T06:00:00.000Z
20 |
21 | true
22 | false
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | # Run this workflow every time a new commit pushed to upstream/fork repository.
4 | # Run workflow on fork repository will help contributors find and resolve issues before sending a PR.
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | jobs:
10 | codeformat:
11 | name: check-code-format
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: autoyapf
16 | id: autoyapf
17 | uses: mritunjaysharma394/autoyapf@v2
18 | with:
19 | args: --style google --recursive --in-place .
20 | - name: Check for modified files
21 | run: |
22 | if git diff-index --quiet HEAD --; then
23 | exit 0
24 | else
25 | git diff;
26 | exit 1
27 | fi
28 |
29 | ci:
30 | name: run-unit-tests
31 | runs-on: ${{ matrix.os }}
32 | strategy:
33 | matrix:
34 | os: [ubuntu-latest]
35 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
36 | steps:
37 | - uses: actions/checkout@v4
38 | - name: Set up Python
39 | uses: actions/setup-python@v5
40 | with:
41 | python-version: ${{ matrix.python-version }}
42 | - name: Setup Poetry
43 | uses: abatilo/actions-poetry@v2
44 | - name: Display Python version
45 | run: python --version
46 | - name: Install dependencies
47 | run: |
48 | poetry install --with devel
49 | - name: Check lock file
50 | run: poetry check --lock
51 | - name: Lint with flake8
52 | run: |
53 | poetry run tox -e flake
54 | - name: Test with pytest
55 | run: |
56 | poetry run tox -e test
57 |
--------------------------------------------------------------------------------
/tests/fixtures/attachment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 22
4 | cgobench1.go
5 | cgobench1.go
6 | application/octet-stream
7 | 351
8 | 2017-05-15T06:12:11.264Z
9 |
10 | 2017-05-15T06:12:11.440Z
11 |
12 |
13 |
14 |
15 | 21
16 | cgobench2.go
17 | cgobench2.go
18 | application/octet-stream
19 | 351
20 | 2017-05-15T03:30:04.686Z
21 |
22 | 2017-05-15T03:30:04.690Z
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "rtcclient"
3 | version = "0.10.0"
4 | homepage = "https://github.com/dixudx/rtcclient"
5 | description = "RTCClient for Rational Team Concert"
6 | authors = ["Di Xu "]
7 | readme = "README.rst"
8 | license = "Apache License Version 2.0"
9 | keywords = ["rtcclient", "Rational Team Concert", "RTC"]
10 | classifiers=[
11 | "Development Status :: 5 - Production/Stable",
12 | "Topic :: Utilities",
13 | "Environment :: Console",
14 | "Intended Audience :: Developers",
15 | "Intended Audience :: System Administrators",
16 | "Intended Audience :: Information Technology",
17 | "License :: OSI Approved :: Apache Software License",
18 | "Operating System :: OS Independent",
19 | "Programming Language :: Python",
20 | "Programming Language :: Python :: 3",
21 | "Programming Language :: Python :: 3.6",
22 | "Programming Language :: Python :: 3.7",
23 | "Programming Language :: Python :: 3.8",
24 | "Programming Language :: Python :: 3.9",
25 | "Programming Language :: Python :: 3.10",
26 | ]
27 | packages = [
28 | { include = "rtcclient" },
29 | { include = "tests", format = "sdist" },
30 | ]
31 |
32 | [tool.poetry.dependencies]
33 | python = ">=3.7.0,<4.0"
34 | PySocks = ">=1.5.6"
35 | jinja2 = ">=2.2"
36 | requests = ">=2.10.0"
37 | six = "*"
38 | xmltodict = "^0.13.0"
39 | lxml = "^5.0.0"
40 |
41 |
42 | [tool.poetry.group.devel]
43 | optional = true
44 | [tool.poetry.group.devel.dependencies]
45 | pip = "^23.0.1"
46 | pycodestyle = "*"
47 | pytest = "^7.2.0"
48 | pytest-env = "*"
49 | pytest-mock = ">=0.6.0"
50 | flake8 = [
51 | { version = "^6.0.0", python = ">=3.8.1" },
52 | { version = "^5.0.4", python = "<3.8.1" }
53 | ]
54 | yapf = "*"
55 | tox = "*"
56 | toml = "*"
57 |
58 |
59 | [build-system]
60 | requires = ["poetry-core>=1.0.0"]
61 | build-backend = "poetry.core.masonry.api"
62 |
--------------------------------------------------------------------------------
/tests/fixtures/includedinbuilds.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | _2NXr8Fx3EeWfxsy-c6nRWw
4 | 20150916-0836
5 |
6 |
7 | 2015-09-16T13:35:51.342Z
8 | 2015-09-16T13:36:01.122Z
9 | 2015-09-16T13:43:20.183Z
10 | MANUAL
11 | COMPLETED
12 | OK
13 |
14 |
15 |
16 |
17 | _b0KuAFuPEeWfxsy-c6nRWw
18 | 20150915-0452
19 |
20 |
21 | 2015-09-15T09:52:10.975Z
22 | 2015-09-15T09:52:19.544Z
23 | 2015-09-15T10:03:05.743Z
24 | MANUAL
25 | COMPLETED
26 | OK
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/tests/fixtures/savedqueries.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Closed created by me
5 |
6 | Work items I have created which have been resolved
7 |
8 |
9 | 2014-02-22T01:23:47.490Z
10 |
11 |
12 |
13 |
14 | Open Track Build Items
15 |
16 | All unresolved Track Build Items
17 |
18 |
19 | 2014-02-22T01:23:47.495Z
20 |
21 |
22 |
23 |
24 | Open Adoptions
25 |
26 |
27 |
28 |
29 | 2014-02-22T01:23:47.499Z
30 |
31 |
32 |
--------------------------------------------------------------------------------
/doc/source/index.rst:
--------------------------------------------------------------------------------
1 | .. rtcclient documentation master file, created by
2 | sphinx-quickstart on Mon Aug 17 17:21:28 2015.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to rtcclient's documentation!
7 | =====================================
8 |
9 | IBM® Rational Team Concert™, is built on the Jazz platform, allowing
10 | application development teams to use one tool to plan across teams, code,
11 | run standups, plan sprints, and track work. For more info, please refer
12 | to here_.
13 |
14 | .. _here: http://www.ibm.com/developerworks/downloads/r/rtc/
15 |
16 | **IMPORTANT NOTE: This is NOT an official-released Python-based RTC Client.**
17 |
18 | This library can help you:
19 |
20 | * Interacts with an RTC server to retrieve objects which contain the detailed information/configuration, including **Project Areas**, **Team Areas**, **Workitems** and etc;
21 | * Creates all kinds of **Workitems** through self-customized templates or copies from some existing **Workitems**;
22 | * Performs some actions on the retrieved **Workitems**, including get/add **Comments**, get/add/remove **Subscribers**/**Children**/**Parent** and etc;
23 | * Query **Workitems** using specified filtered rules or directly from your saved queries;
24 | * Logs all the activities and messages during your operation;
25 |
26 |
27 | Python & Rational Team Concert Versions
28 | ---------------------------------------
29 |
30 | This project has been tested against multiple Python versions, such as "3.7", "3.8", "3.9", "3.10" and "3.11".
31 |
32 | Please install **rtcclient** with version >= 0.9.0, which works well with ``Rational Team Concert`` 6.0.6.1, **5.0.1**, **5.0.2** and ``ELM`` 7.0.
33 |
34 |
35 | Important Links
36 | ---------------
37 |
38 | * Support and bug-reports: https://github.com/dixudx/rtcclient/issues?q=is%3Aopen+sort%3Acomments-desc
39 |
40 | * Project source code: https://github.com/dixudx/rtcclient
41 |
42 | * Project documentation: https://readthedocs.org/projects/rtcclient/
43 |
44 |
45 | User Guide
46 | ==========
47 |
48 | .. toctree::
49 | :maxdepth: 2
50 |
51 | authors
52 | introduction
53 | workitem_attr
54 | installation
55 | quickstart
56 | advanced_usage
57 |
58 |
59 | API Documentation
60 | =================
61 |
62 | .. toctree::
63 | :maxdepth: 2
64 |
65 | client
66 | projectarea
67 | workitem
68 | query
69 | template
70 | models
71 |
72 |
73 | Indices and tables
74 | ==================
75 |
76 | * :ref:`genindex`
77 | * :ref:`modindex`
78 | * :ref:`search`
79 |
--------------------------------------------------------------------------------
/rtcclient/utils.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import logging
3 | from xml.parsers.expat import ExpatError
4 |
5 | import six
6 | import xmltodict
7 | from lxml import etree
8 |
9 | from rtcclient.exception import RTCException, BadValue
10 |
11 |
12 | def setup_basic_logging():
13 | logging.basicConfig(level=logging.DEBUG,
14 | format="%(asctime)s %(levelname)s %(name)s: "
15 | "%(message)s")
16 |
17 |
18 | def token_expire_handler(func):
19 |
20 | @functools.wraps(func)
21 | def wrapper(*args, **kwargs):
22 | rtc_obj = args[0].get_rtc_obj()
23 |
24 | if not hasattr(rtc_obj, "headers") or rtc_obj.headers is None:
25 | # still in the initialization or relogin
26 | # directly call the method
27 | return func(*args, **kwargs)
28 | else:
29 | # check whether token expires
30 | try:
31 | resp = func(*args, **kwargs)
32 | xmltodict.parse(resp.content)
33 | return resp
34 | except ExpatError as excp:
35 | if "invalid token" in str(excp):
36 | # expires
37 | try:
38 | rtc_obj.relogin()
39 | except RTCException:
40 | raise RTCException("Relogin Failed: "
41 | "Invalid username or password")
42 | kwargs["headers"]["Cookie"] = rtc_obj.headers["Cookie"]
43 | return func(*args, **kwargs)
44 | else:
45 | # not expires
46 | # raise the actual exception
47 | raise ExpatError(excp)
48 |
49 | return wrapper
50 |
51 |
52 | def capitalize(keyword):
53 | """Only capitalize the first character and make the left unchanged
54 |
55 | :param keyword: the input string
56 | :return: the capitalized string
57 | """
58 |
59 | if keyword is None:
60 | raise BadValue("Invalid value. None is not supported")
61 |
62 | if isinstance(keyword, six.string_types):
63 | if len(keyword) > 1:
64 | return keyword[0].upper() + keyword[1:]
65 | else:
66 | return keyword.capitalize()
67 | else:
68 | raise BadValue("Input value %s is not string type" % keyword)
69 |
70 |
71 | def remove_empty_elements(docs):
72 | root = etree.fromstring(bytes(docs, 'utf-8'))
73 | for element in root.xpath("//*[not(node())]"):
74 | if "rdf:resource" not in str(etree.tostring(element)):
75 | element.getparent().remove(element)
76 |
77 | return etree.tostring(root)
78 |
--------------------------------------------------------------------------------
/doc/source/advanced_usage.rst:
--------------------------------------------------------------------------------
1 | .. _advanced_usage:
2 |
3 |
4 | Advanced Usage
5 | ==============
6 |
7 | This document covers some of rtcclient more advanced features.
8 |
9 |
10 | .. _query_syntax:
11 |
12 | Query Syntax [2]_
13 | -----------------
14 |
15 | The following section describes the basic query syntax.
16 |
17 |
18 | **Comparison Operators**
19 |
20 | * ``=`` : test for equality of a term,
21 | * ``!=`` : test for inequality of a term,
22 | * ``<`` : test less-than,
23 | * ``>`` : test greater-than,
24 | * ``<=`` : test less-than or equal,
25 | * ``>=`` : test greater-than or equal,
26 | * ``in`` : test for equality of any of the terms.
27 |
28 |
29 | **Boolean Operators**
30 |
31 | * ``and`` : conjunction
32 |
33 |
34 | **Query Modifiers**
35 |
36 | * ``/sort`` : set the sort order for returned items
37 |
38 |
39 | **BNF**
40 |
41 | ::
42 |
43 | query ::= (term (boolean_op term)*)+ modifiers
44 | term ::= (identifier operator)? value+ | (identifier "in")? in_val
45 | operator ::= "=" | "!=" | "<" | ">" | "<=" | ">="
46 | boolean_op ::= "and"
47 | modifiers ::= sort?
48 | sort ::= "/sort" "=" identifier
49 | identifier ::= word (":" word)?
50 | in_val ::= "[" value ("," value)* "]"
51 | value ::= (integer | string)
52 | word ::= /any sequence of letters and numbers, starting with a letter/
53 | string ::= '"' + /any sequence of characters/ + '"'
54 | integer ::= /any sequence of integers/
55 |
56 |
57 | **Notes**
58 |
59 | 1. a word consists of any character with the Unicode class Alpha (alpha-numeric) as well as the characters ".", "-" and "_".
60 | 2. a string may include the quote character if preceded by the escape character "\", as in "my \"quoted\" example".
61 |
62 |
63 | .. _query_compose:
64 |
65 | Compose your Query String
66 | -------------------------
67 |
68 | Based on the above :ref:`query syntax `, it is easy to compose
69 | your own query string.
70 |
71 | **Important Note**: For the `identifier` in :ref:`query syntax `, please refer
72 | to :ref:`field alias ` and
73 | :ref:`Built-in Attributes `.
74 |
75 | Here are several examples.
76 |
77 | **Example 1**: Query all the defects with tags "bvt" whose state is not "Closed"
78 |
79 | Note: here defects' state "default_workflow.state.s1" means "Closed". This
80 | may vary in your customized workitem type.
81 |
82 | >>> query_str = ('dc:type="defect" and '
83 | 'rtc_cm:state!="default_workflow.state.s1" and '
84 | 'dc:subject="bvt"')
85 |
86 | **Example 2**: Query all the defects which are modified after 18:42:30 on Dec. 02, 2008
87 |
88 | Note: here defects' state "default_workflow.state.s1" means "Closed".
89 |
90 | >>> query_str = 'dc:type="defect" and dc:modified>="12-02-2008T18:42:30"'
91 |
92 | **Example 3**: Query all the defects with tags "bvt" or "testautomation"
93 |
94 | >>> query_str = 'dc:type="defect" and dc:subject in ["bvt", "testautomation"]'
95 |
96 | **Example 4**: Query all the defects owned/created/modified by "tester@email.com"
97 |
98 | >>> user_url = "https://your_domain:9443/jts/users/tester@email.com"
99 | >>> query_str = 'dc:type="defect" and rtc_cm:ownedBy="%s"' % user_url
100 | >>> query_str = 'dc:type="defect" and dc:creator="%s"' % user_url
101 | >>> query_str = 'dc:type="defect" and rtc_cm:modifiedBy="%s"' % user_url
102 |
103 | Note: please replace `your_domain` with your actual RTC server domain.
104 |
105 | **Example 5**: Query all the defects whose severity are "Critical"
106 |
107 | >>> projectarea_name="My ProjectArea"
108 | >>> severity = myclient.getSeverity("Critical",
109 | projectarea_name=projectarea_name)
110 | >>> query_str = 'dc:type="defect" and oslc_cm:severity="%s"' % severity.url
111 |
112 | **Example 6**: Query all the defects whose priority are "High"
113 |
114 | >>> projectarea_name="My ProjectArea"
115 | >>> priority = myclient.getPriority("High",
116 | projectarea_name=projectarea_name)
117 | >>> query_str = 'dc:type="defect" and oslc_cm:priority="%s"' % priority.url
118 |
119 | **Example 7**: Query all the defects whose FiledAgainst are "FiledAgainstDemo"
120 |
121 | >>> projectarea_name="My ProjectArea"
122 | >>> filedagainst = myclient.getFiledAgainst("FiledAgainstDemo",
123 | projectarea_name=projectarea_name)
124 | >>> query_str = 'dc:type="defect" and rtc_cm:filedAgainst="%s"' % filedagainst.url
125 |
126 | .. [2] `Change Management Query Syntax `_
127 |
--------------------------------------------------------------------------------
/tests/test_template.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import pytest
3 | import utils_test
4 | from rtcclient.exception import BadValue
5 | from jinja2 import exceptions as jinja2_excp
6 | import xmltodict
7 |
8 |
9 | class TestTemplater:
10 |
11 | @pytest.fixture(autouse=True)
12 | def myrtcclient(self, rtcclient):
13 | myclient = rtcclient
14 | return myclient
15 |
16 | @pytest.fixture(autouse=True)
17 | def mytemplater(self, myrtcclient):
18 | return myrtcclient.templater
19 |
20 | def test_init_templater(self, mytemplater, myrtcclient):
21 | assert mytemplater.url == "http://test.url:9443/jazz"
22 | assert mytemplater.searchpath == utils_test._search_path
23 | assert mytemplater.rtc_obj == myrtcclient
24 | assert str(mytemplater) == "".join(
25 | ["Templater for RTC Server ", "at http://test.url:9443/jazz"])
26 |
27 | def test_list_fields(self, mytemplater):
28 | # invalid template names
29 | invalid_names = [None, True, False]
30 | for invalid_name in invalid_names:
31 | with pytest.raises(BadValue):
32 | mytemplater.listFields(invalid_name)
33 |
34 | # nonexistent template names
35 | fake_names = ["fake_name1", "fake_name2"]
36 | for fake_name in fake_names:
37 | with pytest.raises(jinja2_excp.TemplateNotFound):
38 | mytemplater.listFields(fake_name)
39 |
40 | # existent template
41 | fields = mytemplater.listFields(utils_test.template_name)
42 | fields_set = set([
43 | "severity", "title", "teamArea", "description", "filedAgainst",
44 | "priority", "ownedBy", "plannedFor"
45 | ])
46 | assert fields == fields_set
47 |
48 | def test_render(self, mytemplater):
49 | # invalid template names
50 | invalid_names = [None, True, False]
51 | for invalid_name in invalid_names:
52 | with pytest.raises(BadValue):
53 | mytemplater.render(invalid_name)
54 |
55 | def test_get_template(self, mytemplater, mocker):
56 | # invalid template names
57 | invalid_names = [None, True, False, "", u"", 123.4]
58 | for invalid_name in invalid_names:
59 | with pytest.raises(BadValue):
60 | mytemplater.getTemplate(invalid_name,
61 | template_name=None,
62 | template_folder=None,
63 | keep=False,
64 | encoding="UTF-8")
65 |
66 | # valid template name
67 | mocked_get = mocker.patch("requests.get")
68 | mock_resp = mocker.MagicMock(spec=requests.Response)
69 | mock_resp.status_code = 200
70 | mock_resp.content = utils_test.workitem1_raw
71 | mocked_get.return_value = mock_resp
72 |
73 | copied_from_valid_names = [161, "161", u"161"]
74 | for copied_from in copied_from_valid_names:
75 | template_161 = mytemplater.getTemplate(copied_from,
76 | template_name=None,
77 | template_folder=None,
78 | keep=False,
79 | encoding="UTF-8")
80 | assert (list(xmltodict.parse(template_161).items()).sort() == list(
81 | utils_test.template_ordereddict.items()).sort())
82 |
83 | def test_get_templates_exception(self, mytemplater):
84 | # invalid workitem ids
85 | invalid_names = [
86 | None, True, False, "", u"", "test", u"test", 123, 123.4
87 | ]
88 | for invalid_name in invalid_names:
89 | with pytest.raises(BadValue):
90 | mytemplater.getTemplates(invalid_name,
91 | template_folder=None,
92 | template_names=None,
93 | keep=False,
94 | encoding="UTF-8")
95 |
96 | # invalid template names
97 | invalid_names = [True, False, "", u"", "test", u"test"]
98 | for invalid_name in invalid_names:
99 | with pytest.raises(BadValue):
100 | mytemplater.getTemplates(["valid_id_1", "valid_id_2"],
101 | template_folder=None,
102 | template_names=invalid_name,
103 | keep=False,
104 | encoding="UTF-8")
105 |
106 | # unequal length
107 | with pytest.raises(BadValue):
108 | mytemplater.getTemplates(["valid_id_1", "valid_id_2"],
109 | template_folder=None,
110 | template_names=["valid_name"],
111 | keep=False,
112 | encoding="UTF-8")
113 |
--------------------------------------------------------------------------------
/tests/test_base.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | from rtcclient.base import RTCBase
4 |
5 |
6 | class BaseTestRTC(RTCBase):
7 |
8 | def __str__(self):
9 | return "Test Base"
10 |
11 | def get_rtc_obj(self):
12 | pass
13 |
14 |
15 | class TestRTCBase:
16 |
17 | test_urls = [
18 | "http://test.url:9443/////", "http://test.url:9443/jazz////",
19 | "http://test.url:9443/jazz"
20 | ]
21 |
22 | valid_test_urls = [
23 | "http://test.url:9443", "http://test.url:9443/jazz",
24 | "http://test.url:9443/jazz"
25 | ]
26 |
27 | def test_validate_url(self):
28 | for idx, test_url in enumerate(self.test_urls):
29 | test_rtc = BaseTestRTC(test_url)
30 | assert (
31 | test_rtc.validate_url(test_url) == self.valid_test_urls[idx])
32 |
33 | def test_validate_url_cls(self):
34 | for idx, test_url in enumerate(self.test_urls):
35 | assert (
36 | BaseTestRTC.validate_url(test_url) == self.valid_test_urls[idx])
37 |
38 | def test_str(self):
39 | for test_url in self.test_urls:
40 | test_rtc = BaseTestRTC(test_url)
41 | assert str(test_rtc) == "Test Base"
42 |
43 | def test_repr(self):
44 | for test_url in self.test_urls:
45 | test_rtc = BaseTestRTC(test_url)
46 | assert repr(test_rtc) == ""
47 |
48 | def test_getattr(self):
49 | for idx, test_url in enumerate(self.test_urls):
50 | test_rtc = BaseTestRTC(test_url)
51 | assert test_rtc.url == self.valid_test_urls[idx]
52 | assert test_rtc["url"] == self.valid_test_urls[idx]
53 |
54 | def test_get_resp(self, mocker):
55 | # actually the GET method is inherited from requests.get
56 | # no need for more tests
57 | mocked_get = mocker.patch("requests.get")
58 | test_url = "http://test.url:9443/jazz"
59 | test_rtc = BaseTestRTC(test_url)
60 |
61 | mock_resp = mocker.MagicMock(spec=requests.Response)
62 | mock_resp.status_code = 200
63 | mock_resp.json.return_value = {'get-test': "test"}
64 | mocked_get.return_value = mock_resp
65 |
66 | resp = test_rtc.get(test_rtc.url,
67 | verify=False,
68 | headers=test_rtc.CONTENT_XML,
69 | proxies=None,
70 | timeout=30)
71 | mocked_get.assert_called_once_with(test_rtc.url,
72 | verify=False,
73 | headers=test_rtc.CONTENT_XML,
74 | proxies=None,
75 | timeout=30)
76 | assert resp == mock_resp
77 |
78 | def test_post_resp(self, mocker):
79 | # actually the POST method is inherited from requests.post
80 | # no need for more tests
81 | mocked_post = mocker.patch("requests.post")
82 | test_url = "http://test.url:9443/jazz"
83 | test_rtc = BaseTestRTC(test_url)
84 |
85 | mock_resp = mocker.MagicMock(spec=requests.Response)
86 | mock_resp.status_code = 200
87 | mock_resp.json.return_value = {'post-test': "post"}
88 | mocked_post.return_value = mock_resp
89 |
90 | post_data = {"data": "test"}
91 | resp = test_rtc.post(test_rtc.url,
92 | data=post_data,
93 | json=None,
94 | verify=False,
95 | proxies=None,
96 | headers=test_rtc.CONTENT_XML,
97 | timeout=30)
98 | mocked_post.assert_called_once_with(test_rtc.url,
99 | data=post_data,
100 | json=None,
101 | verify=False,
102 | proxies=None,
103 | headers=test_rtc.CONTENT_XML,
104 | timeout=30)
105 | assert resp == mock_resp
106 |
107 | def test_put_resp(self, mocker):
108 | # actually the PUT method is inherited from requests.put
109 | # no need for more tests
110 | mocked_put = mocker.patch("requests.put")
111 | test_url = "http://test.url:9443/jazz"
112 | test_rtc = BaseTestRTC(test_url)
113 |
114 | mock_resp = mocker.MagicMock(spec=requests.Response)
115 | mock_resp.status_code = 200
116 | mock_resp.json.return_value = {'post-test': "post"}
117 | mocked_put.return_value = mock_resp
118 |
119 | post_data = {"data": "test"}
120 | resp = test_rtc.put(test_rtc.url,
121 | data=post_data,
122 | json=None,
123 | verify=False,
124 | proxies=None,
125 | headers=test_rtc.CONTENT_XML,
126 | timeout=30)
127 | mocked_put.assert_called_once_with(test_rtc.url,
128 | data=post_data,
129 | json=None,
130 | verify=False,
131 | proxies=None,
132 | headers=test_rtc.CONTENT_XML,
133 | timeout=30)
134 | assert resp == mock_resp
135 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | rtcclient
2 | =========
3 |
4 | .. image:: https://readthedocs.org/projects/rtcclient/badge/?version=latest
5 | :target: https://readthedocs.org/projects/rtcclient
6 |
7 | .. image:: https://img.shields.io/pypi/v/rtcclient.svg
8 | :target: https://pypi.python.org/pypi/rtcclient
9 |
10 | .. image:: https://github.com/dixudx/rtcclient/actions/workflows/ci.yml/badge.svg
11 | :target: https://github.com/dixudx/rtcclient
12 |
13 | .. image:: https://img.shields.io/badge/slack-rtcclient-blue.svg
14 | :target: https://rtcclient.slack.com
15 |
16 | .. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
17 | :target: https://saythanks.io/to/dixudx
18 |
19 | A Python-based Client/API for Rational Team Concert (RTC)
20 |
21 | About this library
22 | ------------------
23 |
24 | IBM® Rational Team Concert™, is built on the Jazz platform, allowing
25 | application development teams to use one tool to plan across teams, code,
26 | run standups, plan sprints, and track work. For more info, please refer
27 | to here_.
28 |
29 | .. _here: http://www.ibm.com/developerworks/downloads/r/rtc/
30 |
31 | **IMPORTANT NOTE: This is NOT an official-released Python-based RTC Client.**
32 |
33 | This library can help you:
34 |
35 | * Interacts with an RTC server to retrieve objects which contain the detailed information/configuration, including **Project Areas**, **Team Areas**, **Workitems** and etc;
36 | * Creates all kinds of **Workitems** through self-customized templates or copies from some existing **Workitems**;
37 | * Performs some actions on the retrieved **Workitems**, including get/add **Comments**, get/add/remove **Subscribers**/**Children**/**Parent**, get/upload **Attachments** and etc;
38 | * Query **Workitems** using specified filtered rules or directly from your saved queries;
39 | * Logs all the activities and messages during your operation;
40 |
41 |
42 | Python & Rational Team Concert Versions
43 | ---------------------------------------
44 |
45 | This project has been tested against multiple Python versions, such as "3.7", "3.8", "3.9", "3.10" and "3.11".
46 |
47 | Please install **rtcclient** with version **>= 0.9.0**, which works well with ``Rational Team Concert`` **6.0.6.1**, **5.0.1**, **5.0.2** and ``ELM`` **7.0**.
48 |
49 | Important Links
50 | ---------------
51 |
52 | Support and bug-reports:
53 | https://github.com/dixudx/rtcclient/issues?q=is%3Aopen+sort%3Acomments-desc
54 |
55 | Project source code: https://github.com/dixudx/rtcclient
56 |
57 | Project documentation: https://readthedocs.org/projects/rtcclient/
58 |
59 |
60 | Installation
61 | ------------
62 |
63 | To install rtcclient, simply:
64 |
65 | .. code-block:: bash
66 |
67 | $ pip install rtcclient
68 |
69 |
70 | Example
71 | -------
72 |
73 | RTCClient is intended to map the objects in RTC (e.g. Project Areas,
74 | Team Areas, Workitems) into easily managed Python objects:
75 |
76 | .. code-block:: python
77 |
78 | >>> from rtcclient.utils import setup_basic_logging
79 | >>> from rtcclient import RTCClient
80 | # you can remove this if you don't need logging
81 | # default debug logging for console output
82 | >>> setup_basic_logging()
83 | # url ends with jazz
84 | >>> url = "https://your_domain:9443/jazz"
85 | >>> username = "your_username"
86 | >>> password = "your_password"
87 | # If your rtc server is behind a proxy, remember to set "proxies" explicitly.
88 | # If your url ends with ccm, set ends_with_jazz to False.
89 | # Please refer to issue #68 for detailed explanation
90 | # If your rtc server is too old (such as Rational Team Concert 5.0.1, 5.0.2), please set old_rtc_authentication to True
91 | >>> myclient = RTCClient(url, username, password, ends_with_jazz=True, old_rtc_authentication=False)
92 | # it will be faster if returned properties is specified
93 | # see in below query example
94 | >>> wk = myclient.getWorkitem(123456) # get a workitem whose id is 123456
95 | # get all workitems
96 | # If both projectarea_id and projectarea_name are None, all the workitems
97 | # in all ProjectAreas will be returned
98 | >>> workitems_list = myclient.getWorkitems(projectarea_id=None,
99 | projectarea_name=None)
100 | >>> myquery = myclient.query # query class
101 | >>> projectarea_name = "your_projectarea_name"
102 | # customize your query string
103 | # below query string means: query all the workitems with title "use case 1"
104 | >>> myquerystr = 'dc:title="use case 1"'
105 | # specify the returned properties: title, id, state, owner
106 | # This is optional. All properties will be returned if not specified
107 | >>> returned_prop = "dc:title,dc:identifier,rtc_cm:state,rtc_cm:ownedBy"
108 | >>> queried_wis = myquery.queryWorkitems(query_str=myquerystr,
109 | projectarea_name=projectarea_name,
110 | returned_properties=returned_prop)
111 |
112 |
113 | Testing
114 | -------
115 |
116 | Using a virtualenv is recommended. Setuptools will automatically fetch
117 | missing test dependencies.
118 |
119 | If you have installed the tox_ on your system already, you can run
120 | the tests using pytest_ with the following command:
121 |
122 | .. _tox: https://pypi.python.org/pypi/tox
123 | .. _pytest: http://pytest.org/latest/
124 |
125 | .. code-block:: bash
126 |
127 | virtualenv
128 | source .venv/bin/active
129 | (venv) tox -e test
130 | (venv) tox -e flake
131 | (venv) tox -e pycodestyle
132 |
133 |
134 | Testing with Poetry
135 | -------------------
136 |
137 | When using poetry_ , all dependencies and test environment are managed by this tool even when using tox_.
138 |
139 | If you have already globally installed poetry_ on your system, you can run
140 | the tests using pytest_ with the following command:
141 |
142 | .. _poetry: https://python-poetry.org/
143 |
144 | .. code-block:: bash
145 |
146 | poetry install --with devel
147 | poetry run tox -e test
148 | poetry run tox -e flake
149 | poetry run tox -e pycodestyle
150 |
--------------------------------------------------------------------------------
/doc/source/introduction.rst:
--------------------------------------------------------------------------------
1 | .. _introduction:
2 |
3 | Introduction
4 | ============
5 |
6 | In this section, some common terminologies are introduced. For more information,
7 | please visit `Rational Collaborative Lifecycle Management Solution `_
8 |
9 |
10 | Project Area
11 | ------------
12 |
13 | Project Area is, quite simply, an area in the repository where information
14 | about the project is stored.
15 |
16 | In each of the Collaborative Lifecycle Management (CLM) applications,
17 | teams perform their work within the context of a project area.
18 | A project area is an area in the repository where information about one
19 | or more software projects is stored. **A project area defines the project
20 | deliverables, team structure, process, and schedule**. You access all project
21 | artifacts, such as iteration plans, work items, requirements, test cases,
22 | and files under source control within the context of a project area.
23 | Each project area has a process, which governs how members work.
24 |
25 | For example, the project area process defines:
26 |
27 | * User roles
28 | * Permissions assigned to roles
29 | * Timelines and iterations
30 | * Operation behavior (preconditions and follow-up actions) for Change and Configuration Management and Quality Management
31 | * Work item types and their state transition models (for Change and Configuration Management and Quality Management)
32 |
33 | A project area is stored as a top-level or root item in a repository.
34 | A project area references project artifacts and stores the relationships
35 | between these artifacts. Access to a project area and its artifacts is
36 | controlled by access control settings and permissions. A project area
37 | cannot be deleted from the repository; however, it can be archived,
38 | which places it in an inactive state.
39 |
40 |
41 | Team Area
42 | ---------
43 |
44 | You can create a team area to assign users in particular roles for work on a
45 | timeline or a particular set of deliverables. You can create a team area
46 | within an existing project area or another team area to establish a team
47 | hierarchy.
48 |
49 |
50 | Component [3]_
51 | --------------
52 |
53 | A configuration or set of configurations may be divided into components
54 | representing some user-defined set of object versions and/or
55 | sub-configurations; for example, components might be used to represent
56 | physical components or software modules. A provider is not required to
57 | implement components; they are used only as a way of limiting the scope of
58 | the closure over links. Components might or might not be resources; they
59 | might be dynamic sets of object versions chosen by other criteria such as
60 | property values. A provider can also treat each configuration and
61 | sub-configuration in a hierarchy as being separate components.
62 |
63 |
64 | Change set [3]_
65 | ---------------
66 |
67 | A set of changes to be made to one or more configurations, where each
68 | change is described in terms of members (direct or indirect) that should
69 | be added to, replaced in, or removed from some configurations.
70 |
71 |
72 | Role
73 | ----
74 |
75 | Each project area and each team area can define a set of roles.
76 | The defined roles are visible in the area where they're declared and in all
77 | child areas. Roles defined in the project area can be assigned to users for the
78 | whole project area or they can be assigned in any team area. Roles defined in
79 | a team area can similarly be assigned in that team or in any child team.
80 | The ordering of roles in this section determines how they will be ordered in
81 | other sections of the editor, but it does not affect the process runtime.
82 |
83 |
84 | Administrator
85 | -------------
86 |
87 | If you require permissions, contact an administrator.
88 | Project administrators can modify and save this project area and its team areas.
89 |
90 |
91 | PlannedFor
92 | ----------
93 |
94 | In modern software development, a release is divided into a series of
95 | fixed-length development periods, typically ranging from two to six weeks,
96 | called iterations. Planning an iteration involves scheduling the work to be
97 | done during an iteration and assigning individual work items to members of the
98 | team.
99 |
100 | Iteration planning takes place in the context of a project area.
101 | Each project area has a development line that is divided into development
102 | phases or iterations. For each iteration, you can create an iteration plan.
103 |
104 | The project `plannedfor` defines a start and end date along with an iteration
105 | breakdown.
106 |
107 |
108 | Workitem
109 | --------
110 |
111 | You can use work items to manage and track not only your work, but also
112 | the work of your team.
113 |
114 |
115 | Workitem Type
116 | -------------
117 |
118 |
119 | A workitem type is a classification of work items that has a specific set of
120 | attributes. Each predefined process template includes the work item types that
121 | allow users to work in that process. For example, the Scrum process includes
122 | work item types such as `Epic`, `Story`, `Adoption Item`, `Task`, and
123 | `Retrospective`, which support an agile development model. The Formal Project
124 | Management process, which supports a more traditional development model,
125 | includes workitem types such as `Project Change Request`, `Business Need`, and
126 | `Risk`. Some work item types, such as `Defect` and `Task`, are used by
127 | multiple processes.
128 |
129 |
130 | Workitem Type Category
131 | ----------------------
132 |
133 | Each work item type belongs to a work item category. Multiple work item types
134 | can belong to the same work item category. The work item types of a work item
135 | type category share workflow and custom attributes.
136 | When you create a work item type, you must associate it with a category.
137 | If you intend to define a unique workflow for the new work item type,
138 | create a new category and associate it with the work item type.
139 | Otherwise, you can associate the work item type with an existing category.
140 |
141 | .. [3] `SCM Data Model `_
142 |
--------------------------------------------------------------------------------
/tests/utils_test.py:
--------------------------------------------------------------------------------
1 | import os
2 | import xmltodict
3 | from rtcclient import OrderedDict
4 |
5 | _path = os.path.realpath(os.path.dirname(__file__))
6 | _search_path = os.path.join(_path, "fixtures")
7 |
8 |
9 | def read_fixture(file_name):
10 | file_path = os.path.join(_search_path, file_name)
11 | with open(file_path, mode="r") as fh:
12 | return fh.read()
13 |
14 |
15 | pa1 = (xmltodict.parse(read_fixture("projectareas.xml")).get(
16 | "oslc_cm:Collection").get("rtc_cm:Project")[0])
17 |
18 | pa2 = (xmltodict.parse(read_fixture("projectareas.xml")).get(
19 | "oslc_cm:Collection").get("rtc_cm:Project")[1])
20 |
21 | ta1 = (xmltodict.parse(read_fixture("teamareas.xml")).get(
22 | "oslc_cm:Collection").get("rtc_cm:Team")[0])
23 |
24 | ta2 = (xmltodict.parse(read_fixture("teamareas.xml")).get(
25 | "oslc_cm:Collection").get("rtc_cm:Team")[1])
26 |
27 | plannedfor1 = (xmltodict.parse(read_fixture("plannedfors.xml")).get(
28 | "oslc_cm:Collection").get("rtc_cm:Iteration")[0])
29 |
30 | plannedfor2 = (xmltodict.parse(read_fixture("plannedfors.xml")).get(
31 | "oslc_cm:Collection").get("rtc_cm:Iteration")[1])
32 |
33 | severity1 = (xmltodict.parse(read_fixture("severities.xml")).get(
34 | "oslc_cm:Collection").get("rtc_cm:Literal")[0])
35 |
36 | severity2 = (xmltodict.parse(read_fixture("severities.xml")).get(
37 | "oslc_cm:Collection").get("rtc_cm:Literal")[1])
38 |
39 | priority1 = (xmltodict.parse(read_fixture("priorities.xml")).get(
40 | "oslc_cm:Collection").get("rtc_cm:Literal")[0])
41 |
42 | priority2 = (xmltodict.parse(read_fixture("priorities.xml")).get(
43 | "oslc_cm:Collection").get("rtc_cm:Literal")[1])
44 |
45 | foundin1 = (xmltodict.parse(read_fixture("foundins.xml")).get(
46 | "oslc_cm:Collection").get("rtc_cm:Deliverable")[0])
47 |
48 | foundin2 = (xmltodict.parse(read_fixture("foundins.xml")).get(
49 | "oslc_cm:Collection").get("rtc_cm:Deliverable")[1])
50 |
51 | filedagainst1 = (xmltodict.parse(read_fixture("filedagainsts.xml")).get(
52 | "oslc_cm:Collection").get("rtc_cm:Category")[0])
53 |
54 | filedagainst2 = (xmltodict.parse(read_fixture("filedagainsts.xml")).get(
55 | "oslc_cm:Collection").get("rtc_cm:Category")[1])
56 |
57 | workitem1 = (xmltodict.parse(read_fixture("workitems.xml")).get(
58 | "oslc_cm:Collection").get("oslc_cm:ChangeRequest")[0])
59 |
60 | workitem1_origin = OrderedDict()
61 | workitem1_origin["oslc_cm:ChangeRequest"] = workitem1
62 | workitem1_raw = xmltodict.unparse(workitem1_origin)
63 |
64 | workitem2 = (xmltodict.parse(read_fixture("workitems.xml")).get(
65 | "oslc_cm:Collection").get("oslc_cm:ChangeRequest")[1])
66 | workitem2_origin = OrderedDict()
67 | workitem2_origin["oslc_cm:ChangeRequest"] = workitem2
68 | workitem2_raw = xmltodict.unparse(workitem2_origin)
69 |
70 | template_name = "issue_example.template"
71 | template_raw = read_fixture(template_name)
72 | template_ordereddict = xmltodict.parse(template_raw)
73 |
74 | member1 = (xmltodict.parse(
75 | read_fixture("members.xml")).get("oslc_cm:Collection").get("rtc_cm:User")[0]
76 | )
77 |
78 | member2 = (xmltodict.parse(
79 | read_fixture("members.xml")).get("oslc_cm:Collection").get("rtc_cm:User")[1]
80 | )
81 |
82 | member3 = (xmltodict.parse(
83 | read_fixture("members.xml")).get("oslc_cm:Collection").get("rtc_cm:User")[2]
84 | )
85 |
86 | itemtype1 = (xmltodict.parse(read_fixture("itemtypes.xml")).get(
87 | "oslc_cm:Collection").get("rtc_cm:Type")[0])
88 |
89 | itemtype2 = (xmltodict.parse(read_fixture("itemtypes.xml")).get(
90 | "oslc_cm:Collection").get("rtc_cm:Type")[1])
91 |
92 | admin = (xmltodict.parse(read_fixture("administrators.xml")).get(
93 | "oslc_cm:Collection").get("rtc_cm:User"))
94 |
95 | comment1 = (xmltodict.parse(read_fixture("comments.xml")).get(
96 | "oslc_cm:Collection").get("rtc_cm:Comment")[0])
97 |
98 | comment2 = (xmltodict.parse(read_fixture("comments.xml")).get(
99 | "oslc_cm:Collection").get("rtc_cm:Comment")[1])
100 |
101 | action1 = (xmltodict.parse(read_fixture("actions.xml")).get(
102 | "oslc_cm:Collection").get("rtc_cm:Action")[0])
103 |
104 | action2 = (xmltodict.parse(read_fixture("actions.xml")).get(
105 | "oslc_cm:Collection").get("rtc_cm:Action")[1])
106 |
107 | role1 = xmltodict.parse(
108 | read_fixture("roles.xml")).get("jp06:roles").get("jp06:role")[0]
109 |
110 | role2 = xmltodict.parse(
111 | read_fixture("roles.xml")).get("jp06:roles").get("jp06:role")[1]
112 |
113 | role3 = xmltodict.parse(
114 | read_fixture("roles.xml")).get("jp06:roles").get("jp06:role")[2]
115 |
116 | state1 = (xmltodict.parse(read_fixture("states.xml")).get(
117 | "oslc_cm:Collection").get("rtc_cm:Status")[0])
118 |
119 | state2 = (xmltodict.parse(read_fixture("states.xml")).get(
120 | "oslc_cm:Collection").get("rtc_cm:Status")[1])
121 |
122 | savedquery1 = (xmltodict.parse(read_fixture("savedqueries.xml")).get(
123 | "oslc_cm:Collection").get("rtc_cm:Query")[0])
124 |
125 | savedquery2 = (xmltodict.parse(read_fixture("savedqueries.xml")).get(
126 | "oslc_cm:Collection").get("rtc_cm:Query")[1])
127 |
128 | savedquery3 = (xmltodict.parse(read_fixture("savedqueries.xml")).get(
129 | "oslc_cm:Collection").get("rtc_cm:Query")[2])
130 |
131 | includedinbuild1 = (xmltodict.parse(read_fixture("includedinbuilds.xml")).get(
132 | "oslc_cm:Collection").get("oslc_auto:AutomationResult")[0])
133 |
134 | includedinbuild2 = (xmltodict.parse(read_fixture("includedinbuilds.xml")).get(
135 | "oslc_cm:Collection").get("oslc_auto:AutomationResult")[1])
136 |
137 | children1 = (xmltodict.parse(read_fixture("children.xml")).get(
138 | "oslc_cm:Collection").get("oslc_cm:ChangeRequest")[0])
139 |
140 | children2 = (xmltodict.parse(read_fixture("children.xml")).get(
141 | "oslc_cm:Collection").get("oslc_cm:ChangeRequest")[1])
142 |
143 | parent = (xmltodict.parse(read_fixture("parent.xml")).get(
144 | "oslc_cm:Collection").get("oslc_cm:ChangeRequest"))
145 |
146 | changeset1 = (xmltodict.parse(read_fixture("changesets.xml")).get(
147 | "oslc_cm:Collection").get("rtc_cm:Reference")[0])
148 |
149 | changeset2 = (xmltodict.parse(read_fixture("changesets.xml")).get(
150 | "oslc_cm:Collection").get("rtc_cm:Reference")[1])
151 |
152 | changeset3 = (xmltodict.parse(read_fixture("changesets.xml")).get(
153 | "oslc_cm:Collection").get("rtc_cm:Reference")[2])
154 |
155 | attachment1 = (xmltodict.parse(read_fixture("attachment.xml")).get(
156 | "oslc_cm:Collection").get("rtc_cm:Attachment")[0])
157 | attachment2 = (xmltodict.parse(read_fixture("attachment.xml")).get(
158 | "oslc_cm:Collection").get("rtc_cm:Attachment")[1])
159 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " applehelp to make an Apple Help Book"
34 | @echo " devhelp to make HTML files and a Devhelp project"
35 | @echo " epub to make an epub"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39 | @echo " text to make text files"
40 | @echo " man to make manual pages"
41 | @echo " texinfo to make Texinfo files"
42 | @echo " info to make Texinfo files and run them through makeinfo"
43 | @echo " gettext to make PO message catalogs"
44 | @echo " changes to make an overview of all changed/added/deprecated items"
45 | @echo " xml to make Docutils-native XML files"
46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47 | @echo " linkcheck to check all external links for integrity"
48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49 | @echo " coverage to run coverage check of the documentation (if enabled)"
50 |
51 | clean:
52 | rm -rf $(BUILDDIR)/*
53 |
54 | html:
55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
56 | @echo
57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
58 |
59 | dirhtml:
60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
61 | @echo
62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
63 |
64 | singlehtml:
65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
66 | @echo
67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
68 |
69 | pickle:
70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
71 | @echo
72 | @echo "Build finished; now you can process the pickle files."
73 |
74 | json:
75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
76 | @echo
77 | @echo "Build finished; now you can process the JSON files."
78 |
79 | htmlhelp:
80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
81 | @echo
82 | @echo "Build finished; now you can run HTML Help Workshop with the" \
83 | ".hhp project file in $(BUILDDIR)/htmlhelp."
84 |
85 | qthelp:
86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
87 | @echo
88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rtcclient.qhcp"
91 | @echo "To view the help file:"
92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rtcclient.qhc"
93 |
94 | applehelp:
95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
96 | @echo
97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
98 | @echo "N.B. You won't be able to view it unless you put it in" \
99 | "~/Library/Documentation/Help or install it in your application" \
100 | "bundle."
101 |
102 | devhelp:
103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
104 | @echo
105 | @echo "Build finished."
106 | @echo "To view the help file:"
107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/rtcclient"
108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rtcclient"
109 | @echo "# devhelp"
110 |
111 | epub:
112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
113 | @echo
114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
115 |
116 | latex:
117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
118 | @echo
119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
121 | "(use \`make latexpdf' here to do that automatically)."
122 |
123 | latexpdf:
124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
125 | @echo "Running LaTeX files through pdflatex..."
126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
128 |
129 | latexpdfja:
130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
131 | @echo "Running LaTeX files through platex and dvipdfmx..."
132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
134 |
135 | text:
136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
137 | @echo
138 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
139 |
140 | man:
141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
142 | @echo
143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
144 |
145 | texinfo:
146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
147 | @echo
148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
149 | @echo "Run \`make' in that directory to run these through makeinfo" \
150 | "(use \`make info' here to do that automatically)."
151 |
152 | info:
153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
154 | @echo "Running Texinfo files through makeinfo..."
155 | make -C $(BUILDDIR)/texinfo info
156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
157 |
158 | gettext:
159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
160 | @echo
161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
162 |
163 | changes:
164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
165 | @echo
166 | @echo "The overview file is in $(BUILDDIR)/changes."
167 |
168 | linkcheck:
169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
170 | @echo
171 | @echo "Link check complete; look for any errors in the above output " \
172 | "or in $(BUILDDIR)/linkcheck/output.txt."
173 |
174 | doctest:
175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
176 | @echo "Testing of doctests in the sources finished, look at the " \
177 | "results in $(BUILDDIR)/doctest/output.txt."
178 |
179 | coverage:
180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
181 | @echo "Testing of coverage in the sources finished, look at the " \
182 | "results in $(BUILDDIR)/coverage/python.txt."
183 |
184 | xml:
185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
186 | @echo
187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
188 |
189 | pseudoxml:
190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
191 | @echo
192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
193 |
--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
10 | set I18NSPHINXOPTS=%SPHINXOPTS% source
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | echo. coverage to run coverage check of the documentation if enabled
41 | goto end
42 | )
43 |
44 | if "%1" == "clean" (
45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
46 | del /q /s %BUILDDIR%\*
47 | goto end
48 | )
49 |
50 |
51 | REM Check if sphinx-build is available and fallback to Python version if any
52 | %SPHINXBUILD% 2> nul
53 | if errorlevel 9009 goto sphinx_python
54 | goto sphinx_ok
55 |
56 | :sphinx_python
57 |
58 | set SPHINXBUILD=python -m sphinx.__init__
59 | %SPHINXBUILD% 2> nul
60 | if errorlevel 9009 (
61 | echo.
62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
63 | echo.installed, then set the SPHINXBUILD environment variable to point
64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
65 | echo.may add the Sphinx directory to PATH.
66 | echo.
67 | echo.If you don't have Sphinx installed, grab it from
68 | echo.http://sphinx-doc.org/
69 | exit /b 1
70 | )
71 |
72 | :sphinx_ok
73 |
74 |
75 | if "%1" == "html" (
76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
77 | if errorlevel 1 exit /b 1
78 | echo.
79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
80 | goto end
81 | )
82 |
83 | if "%1" == "dirhtml" (
84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
85 | if errorlevel 1 exit /b 1
86 | echo.
87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
88 | goto end
89 | )
90 |
91 | if "%1" == "singlehtml" (
92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
93 | if errorlevel 1 exit /b 1
94 | echo.
95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
96 | goto end
97 | )
98 |
99 | if "%1" == "pickle" (
100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
101 | if errorlevel 1 exit /b 1
102 | echo.
103 | echo.Build finished; now you can process the pickle files.
104 | goto end
105 | )
106 |
107 | if "%1" == "json" (
108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
109 | if errorlevel 1 exit /b 1
110 | echo.
111 | echo.Build finished; now you can process the JSON files.
112 | goto end
113 | )
114 |
115 | if "%1" == "htmlhelp" (
116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
117 | if errorlevel 1 exit /b 1
118 | echo.
119 | echo.Build finished; now you can run HTML Help Workshop with the ^
120 | .hhp project file in %BUILDDIR%/htmlhelp.
121 | goto end
122 | )
123 |
124 | if "%1" == "qthelp" (
125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
129 | .qhcp project file in %BUILDDIR%/qthelp, like this:
130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\rtcclient.qhcp
131 | echo.To view the help file:
132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\rtcclient.ghc
133 | goto end
134 | )
135 |
136 | if "%1" == "devhelp" (
137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
138 | if errorlevel 1 exit /b 1
139 | echo.
140 | echo.Build finished.
141 | goto end
142 | )
143 |
144 | if "%1" == "epub" (
145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
146 | if errorlevel 1 exit /b 1
147 | echo.
148 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
149 | goto end
150 | )
151 |
152 | if "%1" == "latex" (
153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
154 | if errorlevel 1 exit /b 1
155 | echo.
156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
157 | goto end
158 | )
159 |
160 | if "%1" == "latexpdf" (
161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
162 | cd %BUILDDIR%/latex
163 | make all-pdf
164 | cd %~dp0
165 | echo.
166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdfja" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf-ja
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "text" (
181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
182 | if errorlevel 1 exit /b 1
183 | echo.
184 | echo.Build finished. The text files are in %BUILDDIR%/text.
185 | goto end
186 | )
187 |
188 | if "%1" == "man" (
189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
190 | if errorlevel 1 exit /b 1
191 | echo.
192 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
193 | goto end
194 | )
195 |
196 | if "%1" == "texinfo" (
197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
198 | if errorlevel 1 exit /b 1
199 | echo.
200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
201 | goto end
202 | )
203 |
204 | if "%1" == "gettext" (
205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
206 | if errorlevel 1 exit /b 1
207 | echo.
208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
209 | goto end
210 | )
211 |
212 | if "%1" == "changes" (
213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
214 | if errorlevel 1 exit /b 1
215 | echo.
216 | echo.The overview file is in %BUILDDIR%/changes.
217 | goto end
218 | )
219 |
220 | if "%1" == "linkcheck" (
221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
222 | if errorlevel 1 exit /b 1
223 | echo.
224 | echo.Link check complete; look for any errors in the above output ^
225 | or in %BUILDDIR%/linkcheck/output.txt.
226 | goto end
227 | )
228 |
229 | if "%1" == "doctest" (
230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
231 | if errorlevel 1 exit /b 1
232 | echo.
233 | echo.Testing of doctests in the sources finished, look at the ^
234 | results in %BUILDDIR%/doctest/output.txt.
235 | goto end
236 | )
237 |
238 | if "%1" == "coverage" (
239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
240 | if errorlevel 1 exit /b 1
241 | echo.
242 | echo.Testing of coverage in the sources finished, look at the ^
243 | results in %BUILDDIR%/coverage/python.txt.
244 | goto end
245 | )
246 |
247 | if "%1" == "xml" (
248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
249 | if errorlevel 1 exit /b 1
250 | echo.
251 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
252 | goto end
253 | )
254 |
255 | if "%1" == "pseudoxml" (
256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
257 | if errorlevel 1 exit /b 1
258 | echo.
259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
260 | goto end
261 | )
262 |
263 | :end
264 |
--------------------------------------------------------------------------------
/doc/source/workitem_attr.rst:
--------------------------------------------------------------------------------
1 | .. _workitemattrs:
2 |
3 | Workitem Attributes [1]_
4 | ========================
5 |
6 | Attributes identify the information that you want to capture when users create
7 | and modify work items. Attributes are similar to fields in records. Work item
8 | types include all the built-in attributes that are listed in below Table.
9 | Note, however, that not every ready-to-use work item presentation is configured
10 | to display all of the built-in attributes in the Rational Team Concert™ Eclipse
11 | and web clients. You can customize the attributes that a work item type
12 | contains and the presentations that are used to display these attributes.
13 | For example, you can customize attributes to add behavior. Such behaviors can
14 | include validating an attribute value, or setting an attribute value that is
15 | based on other attribute values.
16 |
17 | All the attributes of the :class:`rtcclient.workitem.Workitem` can be accessed
18 | through **dot notation** and **dictionary**.
19 |
20 |
21 | .. _workitemattrs_table:
22 |
23 | Built-in Attributes
24 | -------------------
25 |
26 | Table1. Built-in Attributes
27 |
28 | +--------------------+-------------+-------------------+----------------------------------------------+
29 | | Name | Type | ID | Description |
30 | +====================+=============+===================+==============================================+
31 | | Archived | Boolean | archived | Specifies whether the work item is archived. |
32 | +--------------------+-------------+-------------------+----------------------------------------------+
33 | | Comments | Comments | comments | Comments about the work item. |
34 | +--------------------+-------------+-------------------+----------------------------------------------+
35 | | Corrected Estimate | Duration | correctedEstimate | Correction to the original time estimate |
36 | | | | | (as specified by the Estimate attribute) to |
37 | | | | | resolve the work item. |
38 | +--------------------+-------------+-------------------+----------------------------------------------+
39 | | Created By | Contributor | creator | User who created the work item. |
40 | +--------------------+-------------+-------------------+----------------------------------------------+
41 | | Creation Date | Timestamp | created | Date when the work item was created. |
42 | +--------------------+-------------+-------------------+----------------------------------------------+
43 | | Description | Large HTML | description | Detailed description of the work item. |
44 | | | | | For example, the description for a defect |
45 | | | | | might include a list of steps to follow to |
46 | | | | | reproduce the defect. Any descriptions that |
47 | | | | | are longer than 32 KB are truncated, and the |
48 | | | | | entire description is added as an attachment.|
49 | +--------------------+-------------+-------------------+----------------------------------------------+
50 | | Due Date | Timestamp | due | Date by which the resolution of the work |
51 | | | | | item is due. |
52 | +--------------------+-------------+-------------------+----------------------------------------------+
53 | | Estimate | Duration | estimate | Estimated amount of time that it takes to |
54 | | | | | resolve the work item. |
55 | +--------------------+-------------+-------------------+----------------------------------------------+
56 | | Filed Against | Category | filedAgainst | Category that identifies the component or |
57 | | | | | functional area that the work item belongs |
58 | | | | | to. For example, your project might have GUI,|
59 | | | | | Build, and Documentation categories. |
60 | | | | | Each category is associated with a team area;|
61 | | | | | that team is responsible for responding to |
62 | | | | | the work item. |
63 | +--------------------+-------------+-------------------+----------------------------------------------+
64 | | Found In | Deliverable | foundIn | Release in which the issue described in the |
65 | | | | | work item was identified. |
66 | +--------------------+-------------+-------------------+----------------------------------------------+
67 | | Id | Integer | identifier | Identification number that is associated |
68 | | | | | with the work item. |
69 | +--------------------+-------------+-------------------+----------------------------------------------+
70 | | Modified By | Contributor | modifiedBy | User who last modified the work item. |
71 | +--------------------+-------------+-------------------+----------------------------------------------+
72 | | Modified Date | Timestamp | modified | Date when the work item was last modified. |
73 | +--------------------+-------------+-------------------+----------------------------------------------+
74 | | Owned By | Contributor | ownedBy | Owner of the work item. |
75 | +--------------------+-------------+-------------------+----------------------------------------------+
76 | | Planned For | Iteration | plannedFor | Iteration for which the work item is planned.|
77 | +--------------------+-------------+-------------------+----------------------------------------------+
78 | | Priority | Priority | priority | Ranked importance of a work item. For |
79 | | | | | example, `Low`, `Medium`, or `High`. |
80 | +--------------------+-------------+-------------------+----------------------------------------------+
81 | | Project Area | ProjectArea | projectArea | Area in the repository where information |
82 | | | | | about the project is stored. |
83 | +--------------------+-------------+-------------------+----------------------------------------------+
84 | | Resolution | Small String| resolution | How the work item was resolved. |
85 | +--------------------+-------------+-------------------+----------------------------------------------+
86 | | Resolution Date | Timestamp | resolved | Date when the work item was resolved. |
87 | +--------------------+-------------+-------------------+----------------------------------------------+
88 | | Resolved By | Contributor | resolvedBy | User who resolved the work item. |
89 | +--------------------+-------------+-------------------+----------------------------------------------+
90 |
91 |
92 | Table2. Built-in Attributes (cont'd)
93 |
94 | +--------------------+-------------+-------------------+----------------------------------------------+
95 | | Name | Type | ID | Description |
96 | +====================+=============+===================+==============================================+
97 | | Restricted Access | UUID | contextId | Scope of access to the work item. |
98 | +--------------------+-------------+-------------------+----------------------------------------------+
99 | | Severity | Severity | severity | Indication of the impact of the work item. |
100 | | | | | For example, `Minor`, `Normal`, `Major`, or |
101 | | | | | `Critical`. |
102 | +--------------------+-------------+-------------------+----------------------------------------------+
103 | | Start Date | Timestamp | startDate | Date when work began on the work item. |
104 | +--------------------+-------------+-------------------+----------------------------------------------+
105 | | Status | Small String| state | Status of the work item. For example, `New`, |
106 | | | | | `In Progress`, or `Resolved`. |
107 | +--------------------+-------------+-------------------+----------------------------------------------+
108 | | Subscribed By |Subscriptions| subscribers | Users who are subscribed to the work item. |
109 | +--------------------+-------------+-------------------+----------------------------------------------+
110 | | Summary | Medium HTML | title | Brief headline that identifies the work item.|
111 | +--------------------+-------------+-------------------+----------------------------------------------+
112 | | Tags | Tag | subject | Tags that are used for organizing and |
113 | | | | | querying on work items. |
114 | +--------------------+-------------+-------------------+----------------------------------------------+
115 | | Time Spent | Duration | timeSpent | Length of time that was spent to resolve the |
116 | | | | | work item. |
117 | +--------------------+-------------+-------------------+----------------------------------------------+
118 | | Type | Type | type | Type of work item. Commonly available types |
119 | | | | | are `Defect`, `Task`, and `Story`. |
120 | +--------------------+-------------+-------------------+----------------------------------------------+
121 |
122 | .. [1] `Workitem Customization Overview `_
123 |
--------------------------------------------------------------------------------
/doc/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # rtcclient documentation build configuration file, created by
4 | # sphinx-quickstart on Mon Aug 17 17:21:28 2015.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | import os
16 | import sys
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | # sys.path.insert(0, os.path.abspath('.'))
22 | sys.path.insert(0, os.path.abspath('../..'))
23 |
24 | # -- General configuration ------------------------------------------------
25 |
26 | # If your documentation needs a minimal Sphinx version, state it here.
27 | # needs_sphinx = '1.0'
28 |
29 | # Add any Sphinx extension module names here, as strings. They can be
30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
31 | # ones.
32 | extensions = [
33 | 'sphinx.ext.autodoc',
34 | 'sphinx.ext.doctest',
35 | 'sphinx.ext.intersphinx',
36 | 'sphinx.ext.todo',
37 | 'sphinx.ext.coverage',
38 | 'sphinx.ext.ifconfig',
39 | 'sphinx.ext.viewcode',
40 | ]
41 |
42 | # Add any paths that contain templates here, relative to this directory.
43 | templates_path = ['_templates']
44 |
45 | # The suffix(es) of source filenames.
46 | # You can specify multiple suffix as a list of string:
47 | # source_suffix = ['.rst', '.md']
48 | source_suffix = '.rst'
49 |
50 | # The encoding of source files.
51 | # source_encoding = 'utf-8-sig'
52 |
53 | # The master toctree document.
54 | master_doc = 'index'
55 |
56 | # General information about the project.
57 | project = u'rtcclient'
58 | copyright = u'2023, Di Xu'
59 | author = u'Di Xu'
60 |
61 | # The version info for the project you're documenting, acts as replacement for
62 | # |version| and |release|, also used in various other places throughout the
63 | # built documents.
64 | #
65 | # The short X.Y version.
66 | version = '0.9'
67 | # The full version, including alpha/beta/rc tags.
68 | release = '0.9.0'
69 |
70 | # The language for content autogenerated by Sphinx. Refer to documentation
71 | # for a list of supported languages.
72 | #
73 | # This is also used if you do content translation via gettext catalogs.
74 | # Usually you set "language" from the command line for these cases.
75 | language = 'en'
76 |
77 | # There are two options for replacing |today|: either, you set today to some
78 | # non-false value, then it is used:
79 | # today = ''
80 | # Else, today_fmt is used as the format for a strftime call.
81 | # today_fmt = '%B %d, %Y'
82 |
83 | # List of patterns, relative to source directory, that match files and
84 | # directories to ignore when looking for source files.
85 | exclude_patterns = []
86 |
87 | # The reST default role (used for this markup: `text`) to use for all
88 | # documents.
89 | # default_role = None
90 |
91 | # If true, '()' will be appended to :func: etc. cross-reference text.
92 | # add_function_parentheses = True
93 |
94 | # If true, the current module name will be prepended to all description
95 | # unit titles (such as .. function::).
96 | # add_module_names = True
97 |
98 | # If true, sectionauthor and moduleauthor directives will be shown in the
99 | # output. They are ignored by default.
100 | # show_authors = False
101 |
102 | # The name of the Pygments (syntax highlighting) style to use.
103 | pygments_style = 'sphinx'
104 |
105 | # A list of ignored prefixes for module index sorting.
106 | # modindex_common_prefix = []
107 |
108 | # If true, keep warnings as "system message" paragraphs in the built documents.
109 | # keep_warnings = False
110 |
111 | # If true, `todo` and `todoList` produce output, else they produce nothing.
112 | todo_include_todos = True
113 |
114 | # -- Options for HTML output ----------------------------------------------
115 |
116 | # The theme to use for HTML and HTML Help pages. See the documentation for
117 | # a list of builtin themes.
118 | html_theme = 'alabaster'
119 |
120 | # Theme options are theme-specific and customize the look and feel of a theme
121 | # further. For a list of options available for each theme, see the
122 | # documentation.
123 | # html_theme_options = {}
124 |
125 | # Add any paths that contain custom themes here, relative to this directory.
126 | # html_theme_path = []
127 |
128 | # The name for this set of Sphinx documents. If None, it defaults to
129 | # " v documentation".
130 | # html_title = None
131 |
132 | # A shorter title for the navigation bar. Default is the same as html_title.
133 | # html_short_title = None
134 |
135 | # The name of an image file (relative to this directory) to place at the top
136 | # of the sidebar.
137 | # html_logo = None
138 |
139 | # The name of an image file (within the static path) to use as favicon of the
140 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
141 | # pixels large.
142 | # html_favicon = None
143 |
144 | # Add any paths that contain custom static files (such as style sheets) here,
145 | # relative to this directory. They are copied after the builtin static files,
146 | # so a file named "default.css" will overwrite the builtin "default.css".
147 | html_static_path = ['_static']
148 |
149 | # Add any extra paths that contain custom files (such as robots.txt or
150 | # .htaccess) here, relative to this directory. These files are copied
151 | # directly to the root of the documentation.
152 | # html_extra_path = []
153 |
154 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
155 | # using the given strftime format.
156 | # html_last_updated_fmt = '%b %d, %Y'
157 |
158 | # If true, SmartyPants will be used to convert quotes and dashes to
159 | # typographically correct entities.
160 | # html_use_smartypants = True
161 |
162 | # Custom sidebar templates, maps document names to template names.
163 | # html_sidebars = {}
164 |
165 | # Additional templates that should be rendered to pages, maps page names to
166 | # template names.
167 | # html_additional_pages = {}
168 |
169 | # If false, no module index is generated.
170 | # html_domain_indices = True
171 |
172 | # If false, no index is generated.
173 | # html_use_index = True
174 |
175 | # If true, the index is split into individual pages for each letter.
176 | # html_split_index = False
177 |
178 | # If true, links to the reST sources are added to the pages.
179 | # html_show_sourcelink = True
180 |
181 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
182 | # html_show_sphinx = True
183 |
184 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
185 | # html_show_copyright = True
186 |
187 | # If true, an OpenSearch description file will be output, and all pages will
188 | # contain a tag referring to it. The value of this option must be the
189 | # base URL from which the finished HTML is served.
190 | # html_use_opensearch = ''
191 |
192 | # This is the file name suffix for HTML files (e.g. ".xhtml").
193 | # html_file_suffix = None
194 |
195 | # Language to be used for generating the HTML full-text search index.
196 | # Sphinx supports the following languages:
197 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
198 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
199 | # html_search_language = 'en'
200 |
201 | # A dictionary with options for the search language support, empty by default.
202 | # Now only 'ja' uses this config value
203 | # html_search_options = {'type': 'default'}
204 |
205 | # The name of a javascript file (relative to the configuration directory) that
206 | # implements a search results scorer. If empty, the default will be used.
207 | # html_search_scorer = 'scorer.js'
208 |
209 | # Output file base name for HTML help builder.
210 | htmlhelp_basename = 'rtcclientdoc'
211 |
212 | # -- Options for LaTeX output ---------------------------------------------
213 |
214 | latex_elements = {
215 | # The paper size ('letterpaper' or 'a4paper').
216 | # 'papersize': 'letterpaper',
217 |
218 | # The font size ('10pt', '11pt' or '12pt').
219 | # 'pointsize': '10pt',
220 |
221 | # Additional stuff for the LaTeX preamble.
222 | # 'preamble': '',
223 |
224 | # Latex figure (float) alignment
225 | # 'figure_align': 'htbp',
226 | }
227 |
228 | # Grouping the document tree into LaTeX files. List of tuples
229 | # (source start file, target name, title,
230 | # author, documentclass [howto, manual, or own class]).
231 | latex_documents = [
232 | (master_doc, 'rtcclient.tex', u'rtcclient Documentation', u'Di Xu',
233 | 'manual'),
234 | ]
235 |
236 | # The name of an image file (relative to this directory) to place at the top of
237 | # the title page.
238 | # latex_logo = None
239 |
240 | # For "manual" documents, if this is true, then toplevel headings are parts,
241 | # not chapters.
242 | # latex_use_parts = False
243 |
244 | # If true, show page references after internal links.
245 | # latex_show_pagerefs = False
246 |
247 | # If true, show URL addresses after external links.
248 | # latex_show_urls = False
249 |
250 | # Documents to append as an appendix to all manuals.
251 | # latex_appendices = []
252 |
253 | # If false, no module index is generated.
254 | # latex_domain_indices = True
255 |
256 | # -- Options for manual page output ---------------------------------------
257 |
258 | # One entry per manual page. List of tuples
259 | # (source start file, name, description, authors, manual section).
260 | man_pages = [(master_doc, 'rtcclient', u'rtcclient Documentation', [author], 1)]
261 |
262 | # If true, show URL addresses after external links.
263 | # man_show_urls = False
264 |
265 | # -- Options for Texinfo output -------------------------------------------
266 |
267 | # Grouping the document tree into Texinfo files. List of tuples
268 | # (source start file, target name, title, author,
269 | # dir menu entry, description, category)
270 | texinfo_documents = [
271 | (master_doc, 'rtcclient', u'rtcclient Documentation', author, 'rtcclient',
272 | 'One line description of project.', 'Miscellaneous'),
273 | ]
274 |
275 | # Documents to append as an appendix to all manuals.
276 | # texinfo_appendices = []
277 |
278 | # If false, no module index is generated.
279 | # texinfo_domain_indices = True
280 |
281 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
282 | # texinfo_show_urls = 'footnote'
283 |
284 | # If true, do not generate a @detailmenu in the "Top" node's menu.
285 | # texinfo_no_detailmenu = False
286 |
287 | # Example configuration for intersphinx: refer to the Python standard library.
288 | intersphinx_mapping = {'https://docs.python.org/': None}
289 |
--------------------------------------------------------------------------------
/rtcclient/models.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import re
4 |
5 | import xmltodict
6 |
7 | from rtcclient import urlunquote, OrderedDict
8 | from rtcclient.base import FieldBase
9 |
10 |
11 | class Role(FieldBase):
12 | """The role in the project area or team area"""
13 |
14 | log = logging.getLogger("models.Role")
15 |
16 | def __str__(self):
17 | return self.label
18 |
19 |
20 | class Member(FieldBase):
21 | """The member in the project area"""
22 |
23 | log = logging.getLogger("models.Member")
24 |
25 | def __init__(self, url, rtc_obj, raw_data=None, skip_full_attributes=True):
26 | FieldBase.__init__(self,
27 | url,
28 | rtc_obj,
29 | raw_data=raw_data,
30 | skip_full_attributes=skip_full_attributes)
31 | # add a new attribute mainly for the un-recorded member use
32 | self.email = urlunquote(self.url.split("/")[-1])
33 |
34 | def __str__(self):
35 | if hasattr(self, "title"):
36 | return self.title
37 | return self.email
38 |
39 | def _initialize(self):
40 | pass
41 |
42 | def __initialize(self):
43 | pass
44 |
45 |
46 | class Administrator(Member):
47 | """The administrator of the project area"""
48 |
49 | log = logging.getLogger("models.Administrator")
50 |
51 |
52 | class ItemType(FieldBase):
53 | """The workitem type"""
54 |
55 | log = logging.getLogger("models.ItemType")
56 |
57 | def __str__(self):
58 | return self.title
59 |
60 |
61 | class TeamArea(FieldBase):
62 | """The team area"""
63 |
64 | log = logging.getLogger("models.TeamArea")
65 |
66 | def __str__(self):
67 | return self.title
68 |
69 |
70 | class PlannedFor(FieldBase):
71 | """The project plannedfor defines a start and end date along with an
72 | iteration breakdown
73 | """
74 |
75 | log = logging.getLogger("models.PlannedFor")
76 |
77 | def __str__(self):
78 | return self.title
79 |
80 |
81 | class FiledAgainst(FieldBase):
82 | """Category that identifies the component or functional area that the
83 | work item belongs to.
84 | """
85 |
86 | log = logging.getLogger("models.FiledAgainst")
87 |
88 | def __str__(self):
89 | return self.title
90 |
91 |
92 | class FoundIn(FieldBase):
93 | """Release in which the issue described in the work item was identified.
94 | """
95 |
96 | log = logging.getLogger("models.FoundIn")
97 |
98 | def __str__(self):
99 | return self.title
100 |
101 |
102 | class Severity(FieldBase):
103 | """Indication of the impact of the work item"""
104 |
105 | log = logging.getLogger("models.Severity")
106 |
107 | def __str__(self):
108 | return self.title
109 |
110 |
111 | class Priority(FieldBase):
112 | """Ranked importance of a work item"""
113 |
114 | log = logging.getLogger("models.Priority")
115 |
116 | def __str__(self):
117 | return self.title
118 |
119 |
120 | class Action(FieldBase):
121 | """The action to change the state of the workitem"""
122 |
123 | log = logging.getLogger("models.Action")
124 |
125 | def __str__(self):
126 | return self.title
127 |
128 |
129 | class State(FieldBase):
130 | """Status of the work item. For example, New, In Progress, or Resolved."""
131 |
132 | log = logging.getLogger("models.State")
133 |
134 | def __str__(self):
135 | return self.title
136 |
137 |
138 | class Comment(FieldBase):
139 | """Comment about the work item"""
140 |
141 | log = logging.getLogger("models.Comment")
142 |
143 | def __init__(self, url, rtc_obj, raw_data=None, skip_full_attributes=True):
144 | self.id = url.split("/")[-1]
145 | FieldBase.__init__(self,
146 | url,
147 | rtc_obj,
148 | raw_data,
149 | skip_full_attributes=skip_full_attributes)
150 |
151 | def __str__(self):
152 | return self.id
153 |
154 |
155 | class SavedQuery(FieldBase):
156 | """User saved query"""
157 |
158 | log = logging.getLogger("models.SavedQuery")
159 |
160 | def __init__(self, url, rtc_obj, raw_data=None, skip_full_attributes=True):
161 | self.id = url.split("/")[-1]
162 | FieldBase.__init__(self,
163 | url,
164 | rtc_obj,
165 | raw_data,
166 | skip_full_attributes=skip_full_attributes)
167 |
168 | def __str__(self):
169 | return self.title
170 |
171 |
172 | class IncludedInBuild(FieldBase):
173 | """Which build includes the certain workitem"""
174 |
175 | log = logging.getLogger("models.IncludedInBuild")
176 |
177 | def __str__(self):
178 | return self.label
179 |
180 |
181 | class ChangeSet(FieldBase):
182 | """ChangeSet"""
183 |
184 | log = logging.getLogger("models.ChangeSet")
185 |
186 | def __str__(self):
187 | return self.label
188 |
189 | def getChanges(self):
190 | """Get all :class:`rtcclient.models.Change` objects in this changeset
191 |
192 | :return: a :class:`list` contains all the
193 | :class:`rtcclient.models.Change` objects
194 | :rtype: list
195 | """
196 |
197 | identifier = self.url.split("/")[-1]
198 | resource_url = "/".join([
199 | "%s" % self.rtc_obj.url, "resource/itemOid",
200 | "com.ibm.team.scm.ChangeSet",
201 | "%s?_mediaType=text/xml" % identifier
202 | ])
203 | resp = self.get(resource_url,
204 | verify=self.rtc_obj.verify,
205 | proxies=self.rtc_obj.proxies,
206 | headers=self.rtc_obj.headers)
207 | raw_data = xmltodict.parse(resp.content).get("scm:ChangeSet")
208 | common_changes = dict()
209 | changes = raw_data.get("changes")
210 | for (key, value) in raw_data.items():
211 | if key.startswith("@"):
212 | continue
213 | if "changes" != key:
214 | common_changes[key] = value
215 | return self._handle_changes(changes, common_changes)
216 |
217 | def _handle_changes(self, changes, common_changes):
218 | change_objs = list()
219 |
220 | if isinstance(changes, OrderedDict):
221 | # only one single change
222 | changes.update(common_changes)
223 | change_objs.append(Change(None, self.rtc_obj, raw_data=changes))
224 |
225 | elif isinstance(changes, list):
226 | # multiple changes
227 | for change in changes:
228 | change.update(common_changes)
229 | change_objs.append(Change(None, self.rtc_obj, raw_data=change))
230 |
231 | return change_objs
232 |
233 |
234 | class Change(FieldBase):
235 | """Change"""
236 |
237 | log = logging.getLogger("models.Change")
238 |
239 | def __init__(self, url, rtc_obj, raw_data=None, skip_full_attributes=True):
240 | FieldBase.__init__(self,
241 | url,
242 | rtc_obj,
243 | raw_data,
244 | skip_full_attributes=skip_full_attributes)
245 |
246 | def __str__(self):
247 | return self.internalId
248 |
249 | def fetchBeforeStateFile(self, file_folder):
250 | """Fetch the initial file (before the change) to a folder
251 |
252 | If the file is newly added, then `None` will be returned.
253 |
254 | :param file_folder: the folder to store the file
255 | :return: the :class:`string` object
256 | :rtype: string
257 | """
258 |
259 | if u"true" == self.before:
260 | self.log.info("This file is newly added. No previous file")
261 | else:
262 | self.log.info("Fetching initial file of this Change<%s>:" % self)
263 | return self._fetchFile(self.before, file_folder, override=False)
264 |
265 | def fetchAfterStateFile(self, file_folder):
266 | """Fetch the final file (after the change) to a folder
267 |
268 | If the file has been deleted, then `None` will be returned.
269 |
270 | :param file_folder: the folder to store the file
271 | :return: the :class:`string` object
272 | :rtype: string
273 | """
274 |
275 | if u"true" == self.after:
276 | self.log.info("This file has been deleted successfully.")
277 | else:
278 | self.log.info("Fetching final file of this Change<%s>:" % self)
279 | return self._fetchFile(self.after, file_folder)
280 |
281 | def fetchCurrentFile(self, file_folder):
282 | """Fetch the current/final file (after the change) to a folder
283 |
284 | If the file has been deleted, then `None` will be returned.
285 |
286 | :param file_folder: the folder to store the file
287 | :return: the :class:`string` object
288 | :rtype: string
289 | """
290 |
291 | return self.fetchAfterStateFile(file_folder)
292 |
293 | def _fetchFile(self, state_id, file_folder, override=True):
294 | if self.raw_data['item']['@xsi:type'] == 'scm:FolderHandle':
295 | return
296 |
297 | file_url = "/".join([
298 | "{0}/service",
299 | ("com.ibm.team.filesystem.service.internal."
300 | "rest.IFilesystemContentService"), "-",
301 | ("{1}?itemId={2}&stateId={3}"
302 | "&platformLineDelimiter=CRLF")
303 | ])
304 |
305 | file_url = file_url.format(self.rtc_obj.url, self.component, self.item,
306 | state_id)
307 |
308 | self.log.debug("Start fetching file from %s ..." % file_url)
309 |
310 | resp = self.get(file_url,
311 | verify=self.rtc_obj.verify,
312 | headers=self.rtc_obj.headers)
313 | file_name = re.findall(r".+filename\*=UTF-8''(.+)",
314 | resp.headers["content-disposition"])[0]
315 | file_path = os.path.join(file_folder, file_name)
316 |
317 | if not override and os.path.exists(file_path):
318 | return
319 |
320 | with open(file_path, "wb") as file_content:
321 | file_content.write(resp.content)
322 |
323 | self.log.info("Successfully Fetching '%s' to '%s'" %
324 | (file_name, file_path))
325 | return file_path
326 |
327 |
328 | class Attachment(FieldBase):
329 | """Attachment of the work item"""
330 |
331 | log = logging.getLogger("models.Attachment")
332 |
333 | def __init__(self, url, rtc_obj, raw_data=None, skip_full_attributes=True):
334 | FieldBase.__init__(self,
335 | url,
336 | rtc_obj,
337 | raw_data,
338 | skip_full_attributes=skip_full_attributes)
339 |
340 | def __str__(self):
341 | return self.identifier + ": " + self.title
342 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {2015} {Di Xu}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/rtcclient/project_area.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import six
4 | import xmltodict
5 |
6 | from rtcclient import exception
7 | from rtcclient.base import FieldBase
8 | from rtcclient.models import Role
9 |
10 |
11 | class ProjectArea(FieldBase):
12 | """A wrapped class to perform all the operations in a Project Area
13 |
14 | :param url: the project area url
15 | :param rtc_obj: a reference to the
16 | :class:`rtcclient.client.RTCClient` object
17 | :param raw_data: the raw data ( OrderedDict ) of the request response
18 |
19 | """
20 |
21 | log = logging.getLogger("project_area.ProjectArea")
22 |
23 | def __init__(self, url, rtc_obj, raw_data, skip_full_attributes=True):
24 | FieldBase.__init__(self,
25 | url,
26 | rtc_obj,
27 | raw_data,
28 | skip_full_attributes=skip_full_attributes)
29 | self.id = self.url.split("/")[-1]
30 |
31 | def __str__(self):
32 | return self.title
33 |
34 | def _initialize(self):
35 | """Request to get response"""
36 |
37 | self.log.error("For ProjectArea, raw_data is mandatory")
38 | raise exception.EmptyAttrib("Please input raw_data")
39 |
40 | def getRoles(self):
41 | """Get all :class:`rtcclient.models.Role` objects in this project
42 | area
43 |
44 | If no :class:`Roles` are retrieved, `None` is returned.
45 |
46 | :return: a :class:`list` that contains all
47 | :class:`rtcclient.models.Role` objects
48 | :rtype: list
49 | """
50 |
51 | # no need to retrieve all the entries from _get_paged_resources
52 | # role raw data is very simple that contains no other links
53 |
54 | self.log.info("Get all the roles in ", self)
55 | roles_url = "/".join(
56 | [self.rtc_obj.url,
57 | "process/project-areas/%s/roles" % self.id])
58 | resp = self.get(roles_url,
59 | verify=self.rtc_obj.verify,
60 | proxies=self.rtc_obj.proxies,
61 | headers=self.rtc_obj.headers)
62 |
63 | roles_list = list()
64 | raw_data = xmltodict.parse(resp.content)
65 | roles_raw = raw_data['jp06:roles']['jp06:role']
66 | if not roles_raw:
67 | self.log.warning("There are no roles in ", self)
68 | return None
69 |
70 | for role_raw in roles_raw:
71 | role = Role(role_raw.get("jp06:url"),
72 | self.rtc_obj,
73 | raw_data=role_raw)
74 | roles_list.append(role)
75 | return roles_list
76 |
77 | def getRole(self, label):
78 | """Get the :class:`rtcclient.models.Role` object by the label name
79 |
80 | :param label: the label name of the role
81 | :return: the :class:`rtcclient.models.Role` object
82 | :rtype: :class:`rtcclient.models.Role`
83 | """
84 |
85 | if not isinstance(label, six.string_types) or not label:
86 | excp_msg = "Please specify a valid role label"
87 | self.log.error(excp_msg)
88 | raise exception.BadValue(excp_msg)
89 |
90 | roles = self.getRoles()
91 | if roles is not None:
92 | for role in roles:
93 | if role.label == label:
94 | self.log.info("Get in ", role,
95 | self)
96 | return role
97 |
98 | excp_msg = "No role's label is %s in " % (label, self)
99 | self.log.error(excp_msg)
100 | raise exception.NotFound(excp_msg)
101 |
102 | def getMembers(self, returned_properties=None):
103 | """Get all the :class:`rtcclient.models.Member` objects in this
104 | project area
105 |
106 | If no :class:`Members` are retrieved, `None` is returned.
107 |
108 | :param returned_properties: the returned properties that you want.
109 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
110 | :return: a :class:`list` that contains all
111 | :class:`rtcclient.models.Member` objects
112 | :rtype: list
113 | """
114 |
115 | return self._getMembers(returned_properties=returned_properties)
116 |
117 | def getMember(self, email, returned_properties=None):
118 | """Get the :class:`rtcclient.models.Member` object by the
119 | email address
120 |
121 | :param email: the email address (e.g. somebody@gmail.com)
122 | :param returned_properties: the returned properties that you want.
123 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
124 | :return: the :class:`rtcclient.models.Member` object
125 | :rtype: rtcclient.models.Member
126 | """
127 |
128 | if not isinstance(email, six.string_types) or "@" not in email:
129 | excp_msg = "Please specify a valid email address name"
130 | self.log.error(excp_msg)
131 | raise exception.BadValue(excp_msg)
132 |
133 | self.log.debug("Try to get Member whose email is %s>", email)
134 | members = self._getMembers(returned_properties=returned_properties,
135 | email=email)
136 | if members is not None:
137 | member = members[0]
138 | self.log.info("Get in ", member, self)
139 | return member
140 |
141 | excp_msg = "No member's email is %s in " % (email, self)
142 | self.log.error(excp_msg)
143 | raise exception.NotFound(excp_msg)
144 |
145 | def _getMembers(self, returned_properties=None, email=None):
146 | self.log.warning("If you are not listed, please contact your RTC "
147 | "administrators to add you as a team member")
148 | rp = returned_properties
149 | filter_rule = None
150 | if email is not None:
151 | fmember_rule = ("rtc_cm:userId", None, email)
152 | filter_rule = self.rtc_obj._add_filter_rule(filter_rule,
153 | fmember_rule)
154 | return self.rtc_obj._get_paged_resources("Member",
155 | projectarea_id=self.id,
156 | page_size='100',
157 | returned_properties=rp,
158 | filter_rule=filter_rule)
159 |
160 | def getItemTypes(self, returned_properties=None):
161 | """Get all the :class:`rtcclient.models.ItemType` objects
162 | in this project area
163 |
164 | If no :class:`ItemTypes` are retrieved, `None` is returned.
165 |
166 | :param returned_properties: the returned properties that you want.
167 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
168 | :return: a :class:`list` that contains all
169 | :class:`rtcclient.models.ItemType` objects
170 | :rtype: list
171 | """
172 |
173 | return self._getItemTypes(returned_properties=returned_properties)
174 |
175 | def getItemType(self, title, returned_properties=None):
176 | """Get the :class:`rtcclient.models.ItemType` object by the title
177 |
178 | :param title: the title (e.g. Story/Epic/..)
179 | :param returned_properties: the returned properties that you want.
180 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
181 | :return: the :class:`rtcclient.models.ItemType` object
182 | :rtype: rtcclient.models.ItemType
183 | """
184 |
185 | if not isinstance(title, six.string_types) or not title:
186 | excp_msg = "Please specify a valid email address name"
187 | self.log.error(excp_msg)
188 | raise exception.BadValue(excp_msg)
189 |
190 | self.log.debug("Try to get ", title)
191 | itemtypes = self._getItemTypes(returned_properties=returned_properties,
192 | title=title)
193 | if itemtypes is not None:
194 | itemtype = itemtypes[0]
195 | self.log.info("Get in ", itemtype,
196 | self)
197 | return itemtype
198 |
199 | excp_msg = "No itemtype's name is %s in " % (title,
200 | self)
201 | self.log.error(excp_msg)
202 | raise exception.NotFound(excp_msg)
203 |
204 | def _getItemTypes(self, returned_properties=None, title=None):
205 | rp = returned_properties
206 | filter_rule = None
207 | if title is not None:
208 | fit_rule = ("dc:title", None, title)
209 | filter_rule = self.rtc_obj._add_filter_rule(filter_rule, fit_rule)
210 | return self.rtc_obj._get_paged_resources("ItemType",
211 | projectarea_id=self.id,
212 | page_size='10',
213 | returned_properties=rp,
214 | filter_rule=filter_rule)
215 |
216 | def getAdministrators(self, returned_properties=None):
217 | """Get all the :class:`rtcclient.models.Administrator` objects in this
218 | project area
219 |
220 | If no :class:`Administrators` are retrieved,
221 | `None` is returned.
222 |
223 | :param returned_properties: the returned properties that you want.
224 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
225 | :return: a :class:`list` that contains all
226 | :class:`rtcclient.models.Administrator` objects
227 | :rtype: list
228 | """
229 |
230 | return self._getAdministrators(returned_properties=returned_properties)
231 |
232 | def getAdministrator(self, email, returned_properties=None):
233 | """Get the :class:`rtcclient.models.Administrator` object
234 | by the email address
235 |
236 | :param email: the email address (e.g. somebody@gmail.com)
237 | :param returned_properties: the returned properties that you want.
238 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
239 | :return: the :class:`rtcclient.models.Administrator` object
240 | :rtype: rtcclient.models.Administrator
241 | """
242 |
243 | if not isinstance(email, six.string_types) or "@" not in email:
244 | excp_msg = "Please specify a valid email address name"
245 | self.log.error(excp_msg)
246 | raise exception.BadValue(excp_msg)
247 |
248 | self.log.debug("Try to get Administrator whose email is %s", email)
249 | rp = returned_properties
250 | administrators = self._getAdministrators(returned_properties=rp,
251 | email=email)
252 | if administrators is not None:
253 | administrator = administrators[0]
254 | self.log.info("Get in ",
255 | administrator, self)
256 | return administrator
257 |
258 | msg = "No administrator's email is %s in " % (email,
259 | self)
260 | self.log.error(msg)
261 | raise exception.NotFound(msg)
262 |
263 | def _getAdministrators(self, returned_properties=None, email=None):
264 | rp = returned_properties
265 | filter_rule = None
266 | if email is not None:
267 | fadmin_rule = ("rtc_cm:userId", None, email)
268 | filter_rule = self.rtc_obj._add_filter_rule(filter_rule,
269 | fadmin_rule)
270 | return self.rtc_obj._get_paged_resources("Administrator",
271 | projectarea_id=self.id,
272 | page_size='10',
273 | returned_properties=rp,
274 | filter_rule=filter_rule)
275 |
--------------------------------------------------------------------------------
/rtcclient/query.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import six
4 |
5 | from rtcclient import exception
6 | from rtcclient import urlquote
7 | from rtcclient.base import RTCBase
8 |
9 |
10 | class Query(RTCBase):
11 | """A wrapped class to perform all query-related actions
12 |
13 | :param rtc_obj: a reference to the
14 | :class:`rtcclient.client.RTCClient` object
15 | """
16 |
17 | log = logging.getLogger("query:Query")
18 |
19 | def __init__(self, rtc_obj, skip_full_attributes=True):
20 | """Initialize object"""
21 |
22 | self.rtc_obj = rtc_obj
23 | RTCBase.__init__(self,
24 | self.rtc_obj.url,
25 | skip_full_attributes=skip_full_attributes)
26 |
27 | def __str__(self):
28 | return "Query @ %s" % self.rtc_obj
29 |
30 | def get_rtc_obj(self):
31 | return self.rtc_obj
32 |
33 | def queryWorkitems(self,
34 | query_str,
35 | projectarea_id=None,
36 | projectarea_name=None,
37 | returned_properties=None,
38 | archived=False,
39 | skip_full_attributes=True):
40 | """Query workitems with the query string in a certain
41 | :class:`rtcclient.project_area.ProjectArea`
42 |
43 | At least either of `projectarea_id` and `projectarea_name` is given
44 |
45 | :param query_str: a valid query string
46 | :param projectarea_id: the :class:`rtcclient.project_area.ProjectArea`
47 | id
48 | :param projectarea_name: the
49 | :class:`rtcclient.project_area.ProjectArea` name
50 | :param returned_properties: the returned properties that you want.
51 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
52 | :param archived: (default is False) whether the
53 | :class:`rtcclient.workitem.Workitem` is archived
54 | :return: a :class:`list` that contains the queried
55 | :class:`rtcclient.workitem.Workitem` objects
56 | :rtype: list
57 | """
58 |
59 | pa_id = (self.rtc_obj._pre_get_resource(
60 | projectarea_id=projectarea_id, projectarea_name=projectarea_name))
61 |
62 | self.log.info("Start to query workitems with query string: %s",
63 | query_str)
64 | query_str = urlquote(query_str)
65 | rp = returned_properties
66 |
67 | return (self.rtc_obj._get_paged_resources(
68 | "Query",
69 | projectarea_id=pa_id,
70 | customized_attr=query_str,
71 | page_size="100",
72 | returned_properties=rp,
73 | archived=archived,
74 | skip_full_attributes=skip_full_attributes))
75 |
76 | def getAllSavedQueries(self,
77 | projectarea_id=None,
78 | projectarea_name=None,
79 | creator=None,
80 | saved_query_name=None):
81 | """Get all saved queries created by somebody (optional)
82 | in a certain project area (optional, either `projectarea_id`
83 | or `projectarea_name` is needed if specified)
84 |
85 | If `saved_query_name` is specified, only the saved queries match the
86 | name will be fetched.
87 |
88 | Note: only if `creator` is added as a member, the saved queries
89 | can be found. Otherwise None will be returned.
90 |
91 | WARNING: now the RTC server cannot correctly list all the saved queries
92 | It seems to be a bug of RTC. Recommend using `runSavedQueryByUrl` to
93 | query all the workitems if the query is saved.
94 |
95 | Note: It will run faster when more attributes are specified.
96 |
97 | :param projectarea_id: the :class:`rtcclient.project_area.ProjectArea`
98 | id
99 | :param projectarea_name: the
100 | :class:`rtcclient.project_area.ProjectArea` name
101 | :param creator: the creator email address
102 | :param saved_query_name: the saved query name
103 | :return: a :class:`list` that contains the saved queried
104 | :class:`rtcclient.models.SavedQuery` objects
105 | :rtype: list
106 | """
107 |
108 | pa_id = (self.rtc_obj._pre_get_resource(
109 | projectarea_id=projectarea_id, projectarea_name=projectarea_name))
110 |
111 | filter_rule = None
112 | if creator is not None:
113 | fcreator = self.rtc_obj.getOwnedBy(creator).url
114 | filter_rule = [("dc:creator", "@rdf:resource", fcreator)]
115 | self.log.debug(
116 | "Add rules for fetching all saved queries: "
117 | "created by %s", creator)
118 |
119 | if saved_query_name is not None:
120 | ftitle_rule = ("dc:title", None, saved_query_name)
121 | if filter_rule is None:
122 | filter_rule = [ftitle_rule]
123 | else:
124 | filter_rule.append(ftitle_rule)
125 | self.log.debug(
126 | "Add rules for fetching all saved queries: "
127 | "saved query title is %s", saved_query_name)
128 |
129 | return (self.rtc_obj._get_paged_resources("SavedQuery",
130 | projectarea_id=pa_id,
131 | page_size="100",
132 | filter_rule=filter_rule))
133 |
134 | def getSavedQueriesByName(self,
135 | saved_query_name,
136 | projectarea_id=None,
137 | projectarea_name=None,
138 | creator=None):
139 | """Get all saved queries match the name created by somebody (optional)
140 | in a certain project area (optional, either `projectarea_id`
141 | or `projectarea_name` is needed if specified)
142 |
143 | Note: only if `creator` is added as a member, the saved queries
144 | can be found. Otherwise None will be returned.
145 |
146 | WARNING: now the RTC server cannot correctly list all the saved queries
147 | It seems to be a bug of RTC. Recommend using `runSavedQueryByUrl` to
148 | query all the workitems if the query is saved.
149 |
150 | :param saved_query_name: the saved query name
151 | :param projectarea_id: the :class:`rtcclient.project_area.ProjectArea`
152 | id
153 | :param projectarea_name: the
154 | :class:`rtcclient.project_area.ProjectArea` name
155 | :param creator: the creator email address
156 | :return: a :class:`list` that contains the saved queried
157 | :class:`rtcclient.models.SavedQuery` objects
158 | :rtype: list
159 | """
160 |
161 | self.log.info("Start to fetch all saved queries with the name %s",
162 | saved_query_name)
163 | return self.getAllSavedQueries(projectarea_id=projectarea_id,
164 | projectarea_name=projectarea_name,
165 | creator=creator,
166 | saved_query_name=saved_query_name)
167 |
168 | def getMySavedQueries(self,
169 | projectarea_id=None,
170 | projectarea_name=None,
171 | saved_query_name=None):
172 | """Get all saved queries created by me in a certain project
173 | area (optional, either `projectarea_id` or `projectarea_name` is
174 | needed if specified)
175 |
176 | Note: only if myself is added as a member, the saved queries
177 | can be found. Otherwise None will be returned.
178 |
179 | WARNING: now the RTC server cannot correctly list all the saved queries
180 | It seems to be a bug of RTC. Recommend using `runSavedQueryByUrl` to
181 | query all the workitems if the query is saved.
182 |
183 | :param projectarea_id: the :class:`rtcclient.project_area.ProjectArea`
184 | id
185 | :param projectarea_name: the
186 | :class:`rtcclient.project_area.ProjectArea` name
187 | :param saved_query_name: the saved query name
188 | :return: a :class:`list` that contains the saved queried
189 | :class:`rtcclient.models.SavedQuery` objects
190 | :rtype: list
191 | """
192 |
193 | self.log.info("Start to fetch my saved queries")
194 | return self.getAllSavedQueries(projectarea_id=projectarea_id,
195 | projectarea_name=projectarea_name,
196 | creator=self.rtc_obj.username,
197 | saved_query_name=saved_query_name)
198 |
199 | def runSavedQueryByUrl(self, saved_query_url, returned_properties=None):
200 | """Query workitems using the saved query url
201 |
202 | :param saved_query_url: the saved query url
203 | :param returned_properties: the returned properties that you want.
204 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
205 | :return: a :class:`list` that contains the queried
206 | :class:`rtcclient.workitem.Workitem` objects
207 | :rtype: list
208 | """
209 |
210 | try:
211 | if "=" not in saved_query_url:
212 | raise exception.BadValue()
213 | saved_query_id = saved_query_url.split("=")[-1]
214 | if not saved_query_id:
215 | raise exception.BadValue()
216 | except Exception:
217 | error_msg = "No saved query id is found in the url"
218 | self.log.error(error_msg)
219 | raise exception.BadValue(error_msg)
220 | return self._runSavedQuery(saved_query_id,
221 | returned_properties=returned_properties)
222 |
223 | def runSavedQueryByID(self, saved_query_id, returned_properties=None):
224 | """Query workitems using the saved query id
225 |
226 | This saved query id can be obtained by below two methods:
227 |
228 | 1. :class:`rtcclient.models.SavedQuery` object (e.g.
229 | mysavedquery.id)
230 |
231 | 2. your saved query url (e.g.
232 | https://myrtc:9443/jazz/web/xxx#action=xxxx%id=_mGYe0CWgEeGofp83pg),
233 | where the last "_mGYe0CWgEeGofp83pg" is the saved query id.
234 |
235 | :param saved_query_id: the saved query id
236 | :param returned_properties: the returned properties that you want.
237 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
238 | :return: a :class:`list` that contains the queried
239 | :class:`rtcclient.workitem.Workitem` objects
240 | :rtype: list
241 | """
242 |
243 | if not isinstance(saved_query_id,
244 | six.string_types) or not saved_query_id:
245 | excp_msg = "Please specify a valid saved query id"
246 | self.log.error(excp_msg)
247 | raise exception.BadValue(excp_msg)
248 | return self._runSavedQuery(saved_query_id,
249 | returned_properties=returned_properties)
250 |
251 | def runSavedQuery(self, saved_query_obj, returned_properties=None):
252 | """Query workitems using the :class:`rtcclient.models.SavedQuery`
253 | object
254 |
255 | :param saved_query_obj: the :class:`rtcclient.models.SavedQuery`
256 | object
257 | :param returned_properties: the returned properties that you want.
258 | Refer to :class:`rtcclient.client.RTCClient` for more explanations
259 | :return: a :class:`list` that contains the queried
260 | :class:`rtcclient.workitem.Workitem` objects
261 | :rtype: list
262 | """
263 |
264 | try:
265 | saved_query_id = saved_query_obj.results.split("/")[-2]
266 | except BaseException:
267 | error_msg = "Cannot get the correct saved query id"
268 | self.log.error(error_msg)
269 | raise exception.RTCException(error_msg)
270 | return self._runSavedQuery(saved_query_id,
271 | returned_properties=returned_properties)
272 |
273 | def _runSavedQuery(self, saved_query_id, returned_properties=None):
274 | rp = returned_properties
275 | return (self.rtc_obj._get_paged_resources(
276 | "RunQuery",
277 | page_size="100",
278 | customized_attr=saved_query_id,
279 | returned_properties=rp))
280 |
--------------------------------------------------------------------------------
/tests/test_projectarea.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import pytest
3 | import utils_test
4 | from rtcclient.project_area import ProjectArea
5 | from rtcclient.models import Member, ItemType, Administrator, Role
6 | from rtcclient.exception import BadValue, NotFound, EmptyAttrib
7 |
8 |
9 | class TestProjectArea:
10 |
11 | @pytest.fixture(autouse=True)
12 | def myrtcclient(self, rtcclient):
13 | myclient = rtcclient
14 | return myclient
15 |
16 | def test_init_projectarea_exception(self, myrtcclient):
17 | pa_url = "/".join([
18 | "http://test.url:9443/jazz/oslc",
19 | "projectareas/_0qMJUMfiEd6yW_0tvNlbrw"
20 | ])
21 | with pytest.raises(EmptyAttrib):
22 | ProjectArea(pa_url, myrtcclient, raw_data=None)
23 |
24 | @pytest.fixture(autouse=True)
25 | def mypa(self, myrtcclient):
26 | pa_url = "/".join([
27 | "http://test.url:9443/jazz/oslc",
28 | "projectareas/_CuZu0HUwEeKicpXBddtqNA"
29 | ])
30 | return ProjectArea(pa_url, myrtcclient, utils_test.pa2)
31 |
32 | @pytest.fixture
33 | def mock_get_roles(self, mocker):
34 | mocked_get = mocker.patch("requests.get")
35 | mock_resp = mocker.MagicMock(spec=requests.Response)
36 | mock_resp.status_code = 200
37 | mock_resp.content = utils_test.read_fixture("roles.xml")
38 | mocked_get.return_value = mock_resp
39 | return mocked_get
40 |
41 | def test_get_roles(self, mypa, mock_get_roles, myrtcclient):
42 | # Role1
43 | role1_url = "/".join([
44 | "http://test.url:9443/jazz/process",
45 | "project-areas/_vsoCMN-TEd6VxeRBsGx82Q", "roles/Product%20Owner"
46 | ])
47 | role1 = Role(role1_url, myrtcclient, utils_test.role1)
48 | assert str(role1) == "Product Owner"
49 | assert role1.url == role1_url
50 | assert role1.id == "Product Owner"
51 | assert role1.description == " ".join(
52 | ["The person responsible for", "managing the Product Backlog."])
53 |
54 | # Role2
55 | role2_url = "/".join([
56 | "http://test.url:9443/jazz/process",
57 | "project-areas_vsoCMN-TEd6VxeRBsGx82Q"
58 | "roles/Test%20Team"
59 | ])
60 | role2 = Role(role2_url, myrtcclient, utils_test.role2)
61 |
62 | # Role3
63 | role3_url = "/".join([
64 | "http://test.url:9443/jazz/process",
65 | "project-areas/_vsoCMN-TEd6VxeRBsGx82Q", "roles/default"
66 | ])
67 | role3 = Role(role3_url, myrtcclient, utils_test.role3)
68 |
69 | roles = mypa.getRoles()
70 | assert roles == [role1, role2, role3]
71 |
72 | def test_get_role(self, mypa, mock_get_roles, myrtcclient):
73 | # invalid role labels
74 | invalid_labels = ["", u"", None, True, False]
75 | for invalid_label in invalid_labels:
76 | with pytest.raises(BadValue):
77 | mypa.getRole(invalid_label)
78 |
79 | # Role1
80 | role1_url = "/".join([
81 | "http://test.url:9443/jazz/process"
82 | "project-areas_vsoCMN-TEd6VxeRBsGx82Q", "roles/Product%20Owner"
83 | ])
84 | role1 = Role(role1_url, myrtcclient, utils_test.role1)
85 |
86 | # valid role label
87 | role_valid_names = ["Product Owner", u"Product Owner"]
88 | for role_name in role_valid_names:
89 | role = mypa.getRole(role_name)
90 | assert role == role1
91 |
92 | # undefined role
93 | role_fake_names = ["fake_role", u"fake_role"]
94 | for role_name in role_fake_names:
95 | with pytest.raises(NotFound):
96 | mypa.getRole(role_name)
97 |
98 | @pytest.fixture
99 | def mock_get_members(self, mocker):
100 | mocked_get = mocker.patch("requests.get")
101 | mock_resp = mocker.MagicMock(spec=requests.Response)
102 | mock_resp.status_code = 200
103 | mock_resp.content = utils_test.read_fixture("members.xml")
104 | mocked_get.return_value = mock_resp
105 | return mocked_get
106 |
107 | def test_get_members(self, mypa, mock_get_members, myrtcclient):
108 |
109 | # Member1
110 | m1 = Member("http://test.url:9443/jts/users/tester1%40email.com",
111 | myrtcclient, utils_test.member1)
112 | assert str(m1) == "tester1"
113 | assert m1.email == "tester1@email.com"
114 | assert m1.userId == "tester1@email.com"
115 | assert m1.title == "tester1"
116 | assert m1.emailAddress == "mailto:tester1%40email.com"
117 | assert m1.photo is None
118 | assert m1.modified == "2009-11-24T19:14:14.595Z"
119 | assert m1.modifiedBy == "ADMIN"
120 |
121 | # Member2
122 | m2 = Member("http://test.url:9443/jts/users/tester2%40email.com",
123 | myrtcclient, utils_test.member2)
124 | assert str(m2) == "tester2"
125 | assert m2.email == "tester2@email.com"
126 | assert m2.userId == "tester2@email.com"
127 | assert m2.title == "tester2"
128 | assert m2.emailAddress == "mailto:tester2%40email.com"
129 | assert m2.photo is None
130 | assert m2.modified == "2013-04-22T06:24:34.661Z"
131 | assert m2.modifiedBy == "ADMIN"
132 |
133 | # Member3
134 | m3 = Member("http://test.url:9443/jts/users/tester3%40email.com",
135 | myrtcclient, utils_test.member3)
136 | assert str(m3) == "tester3"
137 | assert m3.email == "tester3@email.com"
138 | assert m3.userId == "tester3@email.com"
139 | assert m3.title == "tester3"
140 | assert m3.emailAddress == "mailto:tester3%40email.com"
141 | assert m3.photo is None
142 | assert m3.modified == "2010-05-13T20:34:05.138Z"
143 | assert m3.modifiedBy == "ADMIN"
144 |
145 | members = mypa.getMembers()
146 | assert members == [m1, m2, m3]
147 |
148 | def test_get_member(self, mypa, myrtcclient, mock_get_members):
149 | # invalid email address
150 | invalid_emails = [
151 | "", u"", None, True, False, "test.com", u"test.com"
152 | "test%40email.com", u"test%40email.com"
153 | ]
154 | for invalid_email in invalid_emails:
155 | with pytest.raises(BadValue):
156 | mypa.getMember(invalid_email)
157 |
158 | # Member1
159 | m1 = Member("http://test.url:9443/jts/users/tester1%40email.com",
160 | myrtcclient, utils_test.member1)
161 |
162 | # valid email address
163 | member_valid_names = ["tester1@email.com", u"tester1@email.com"]
164 | for member_name in member_valid_names:
165 | member = mypa.getMember(member_name)
166 | assert member == m1
167 |
168 | # undefined member
169 | member_fake_names = ["fake@email.com", u"fake@email.com"]
170 | for member_name in member_fake_names:
171 | with pytest.raises(NotFound):
172 | mypa.getMember(member_name)
173 |
174 | @pytest.fixture
175 | def mock_get_itemtypes(self, mocker):
176 | mocked_get = mocker.patch("requests.get")
177 | mock_resp = mocker.MagicMock(spec=requests.Response)
178 | mock_resp.status_code = 200
179 | mock_resp.content = utils_test.read_fixture("itemtypes.xml")
180 | mocked_get.return_value = mock_resp
181 | return mocked_get
182 |
183 | def test_get_itemtypes(self, mypa, mock_get_itemtypes, myrtcclient):
184 | # ItemType1
185 | it1_url = "/".join([
186 | "http://test.url:9443/jazz/oslc",
187 | "types/_CuZu0HUwEeKicpXBddtqNA/defect"
188 | ])
189 | it1 = ItemType(it1_url, myrtcclient, utils_test.itemtype1)
190 | assert it1.url == it1_url
191 | assert str(it1) == "Defect"
192 | assert it1.identifier == "defect"
193 | assert it1.title == "Defect"
194 | assert it1.iconUrl == "".join([
195 | "http://test.url:9443/jazz/service/",
196 | "com.ibm.team.workitem.common.",
197 | "internal.model.IImageContentService/", "processattachment/",
198 | "_CuZu0HUwEeKicpXBddtqNA/workitemtype", "/bug.gif"
199 | ])
200 | assert it1.dimmedIconUrl is None
201 | assert it1.category == "defect_task"
202 | # fake data: pls ignore the value
203 | assert it1.projectArea == ["Defect", "Task"]
204 |
205 | # ItemType2
206 | it2_url = "/".join([
207 | "http://test.url:9443/jazz/oslc",
208 | "types/_CuZu0HUwEeKicpXBddtqNA/task"
209 | ])
210 | it2 = ItemType(it2_url, myrtcclient, utils_test.itemtype2)
211 | assert it2.url == it2_url
212 | assert str(it2) == "Task"
213 | assert it2.identifier == "task"
214 | assert it2.title == "Task"
215 | assert it2.iconUrl == "".join([
216 | "http://test.url:9443/jazz/service/",
217 | "com.ibm.team.workitem.common.",
218 | "internal.model.IImageContentService/", "processattachment/",
219 | "_CuZu0HUwEeKicpXBddtqNA/workitemtype", "/task.gif"
220 | ])
221 | assert it2.dimmedIconUrl is None
222 | assert it2.category == "task"
223 | # fake data: pls ignore the value
224 | assert it2.projectArea == ["Defect", "Task"]
225 |
226 | its = mypa.getItemTypes()
227 | assert its == [it1, it2]
228 |
229 | def test_get_itemtype(self, mypa, myrtcclient, mock_get_itemtypes):
230 | # invalid email address
231 | invalid_titles = ["", u"", None, True, False]
232 | for invalid_title in invalid_titles:
233 | with pytest.raises(BadValue):
234 | mypa.getItemType(invalid_title)
235 |
236 | # ItemType1
237 | it1_url = "/".join([
238 | "http://test.url:9443/jazz/oslc",
239 | "types/_CuZu0HUwEeKicpXBddtqNA/defect"
240 | ])
241 | it1 = ItemType(it1_url, myrtcclient, utils_test.itemtype1)
242 |
243 | itemtype_valid_names = ["Defect", u"Defect"]
244 | for itemtype_name in itemtype_valid_names:
245 | itemtype = mypa.getItemType(itemtype_name)
246 | assert itemtype == it1
247 |
248 | # undefined type
249 | itemtype_fake_names = ["fake_type", u"fake_type"]
250 | for itemtype_name in itemtype_fake_names:
251 | with pytest.raises(NotFound):
252 | mypa.getItemType(itemtype_name)
253 |
254 | @pytest.fixture
255 | def mock_get_admins(self, mocker):
256 | mocked_get = mocker.patch("requests.get")
257 | mock_resp = mocker.MagicMock(spec=requests.Response)
258 | mock_resp.status_code = 200
259 | mock_resp.content = utils_test.read_fixture("administrators.xml")
260 | mocked_get.return_value = mock_resp
261 | return mocked_get
262 |
263 | def test_get_admins(self, mypa, mock_get_admins, myrtcclient):
264 | # Administrator
265 | admin_url = "http://test.url:9443/jts/users/tester1%40email.com"
266 | admin = Administrator(admin_url, myrtcclient, utils_test.admin)
267 | assert str(admin) == "tester1"
268 | assert admin.url == admin_url
269 | assert admin.userId == "tester1@email.com"
270 | assert admin.title == "tester1"
271 | assert admin.emailAddress == "mailto:tester1%40email.com"
272 | assert admin.photo is None
273 | assert admin.modified == "2009-08-17T10:08:03.721Z"
274 | assert admin.modifiedBy == "ADMIN"
275 |
276 | admins = mypa.getAdministrators()
277 | assert admins == [admin]
278 |
279 | def test_get_admin(self, mypa, myrtcclient, mock_get_admins):
280 | # invalid email address
281 | invalid_emails = [
282 | "", u"", None, True, False, "test.com", u"test.com",
283 | "test%40email.com", u"test%40email.com"
284 | ]
285 | for invalid_email in invalid_emails:
286 | with pytest.raises(BadValue):
287 | mypa.getAdministrator(invalid_email)
288 |
289 | # Administrator
290 | ad_url = "http://test.url:9443/jts/users/tester1%40email.com"
291 | ad = Administrator(ad_url, myrtcclient, utils_test.admin)
292 |
293 | # valid email address
294 | admin_valid_names = ["tester1@email.com", u"tester1@email.com"]
295 | for admin_name in admin_valid_names:
296 | admin = mypa.getAdministrator(admin_name)
297 | assert admin == ad
298 |
299 | # undefined admin
300 | admin_fake_names = ["fake@email.com", u"fake@email.com"]
301 | for admin_name in admin_fake_names:
302 | with pytest.raises(NotFound):
303 | mypa.getAdministrator(admin_name)
304 |
--------------------------------------------------------------------------------
/doc/source/quickstart.rst:
--------------------------------------------------------------------------------
1 | .. _quickstart:
2 |
3 | Quick Start
4 | ===========
5 |
6 | Eager to get started? This page gives a good introduction in how to get started
7 | with rtcclient.
8 |
9 | First, make sure that:
10 |
11 | * rtcclient is :ref:`installed `
12 | * rtcclient is up-to-date
13 |
14 |
15 | RTCClient is intended to map the objects in RTC (e.g. Project Areas,
16 | Team Areas, Workitems) into easily managed Python objects
17 |
18 | Let's get started with some simple examples.
19 |
20 |
21 | Setup Logging
22 | -------------
23 |
24 | You can choose to enable logging during the using of rtcclient. Default logging
25 | is for console output. You can also add your own `logging.conf` to store all
26 | the logs to your specified files.
27 |
28 | >>> from rtcclient.utils import setup_basic_logging
29 | # you can remove this if you don't need logging
30 | >>> setup_basic_logging()
31 |
32 |
33 | Add a Connection to the RTC Server
34 | ----------------------------------
35 |
36 | Adding a connection with RTC Server is very simple.
37 |
38 | Begin by importing the `RTCClient` module::
39 |
40 | >>> from rtcclient import RTCClient
41 |
42 | Now, let's input the url, username and password of this to-be-connected
43 | RTC Server. For this example,
44 |
45 | >>> url = "https://your_domain:9443/jazz"
46 | >>> username = "your_username"
47 | >>> password = "your_password"
48 | >>> myclient = RTCClient(url, username, password, ends_with_jazz=True, old_rtc_authentication=False)
49 |
50 | If your url ends with **ccm**, set `ends_with_jazz` to `False`,
51 | refer to **issue #68** for detailed explanation.
52 |
53 | If your rtc server is behind a proxy, remember to set "proxies" explicitly.
54 |
55 | If your rtc server is too old (such as Rational Team Concert 5.0.1, 5.0.2), please set `old_rtc_authentication` to `True`.
56 |
57 | About Proxies
58 | -------------
59 |
60 | If your RTC Server is behind a proxy, you need to set `proxies` explicitly.
61 |
62 | HTTP Proxies
63 | ~~~~~~~~~~~~
64 |
65 | >>> url = "https://your_domain:9443/jazz"
66 | >>> username = "your_username"
67 | >>> password = "your_password"
68 | # example http proxy
69 | >>> proxies = {
70 | 'http': 'http://10.10.1.10:3128',
71 | 'https': 'http://10.10.1.10:1080',
72 | }
73 | >>> myclient = RTCClient(url, username, password, proxies=proxies)
74 |
75 | SOCKS Proxies
76 | ~~~~~~~~~~~~
77 | In addition to basic HTTP proxies, proxies using the SOCKS protocol are also
78 | supported.
79 |
80 | >>> url = "https://your_domain:9443/jazz"
81 | >>> username = "your_username"
82 | >>> password = "your_password"
83 | # example socks proxy
84 | >>> proxies = {
85 | "http": "socks5://127.0.0.1:1080",
86 | "https": "socks5://user:pass@host:port"
87 | }
88 | >>> myclient = RTCClient(url, username, password, proxies=proxies)
89 |
90 |
91 | Get a Workitem
92 | --------------
93 |
94 | You can get a workitem by calling
95 | :class:`rtcclient.workitem.Workitem.getWorkitem`. The attributes of a workitem
96 | can be accessed through **dot notation** and **dictionary**.
97 |
98 | Some common attributes are listed in
99 | :ref:`Built-in Attributes `.
100 |
101 | For example,
102 |
103 | >>> wk = myclient.getWorkitem(123456)
104 | # get a workitem whose id is 123456
105 | # this also works: getting the workitem using the equivalent string
106 | >>> wk2 = myclient.getWorkitem("123456")
107 | # wk equals wk2
108 | >>> wk == wk2
109 | True
110 | >>> wk
111 |
112 | >>> str(wk)
113 | '141488'
114 | >>> wk.identifier
115 | u'141488'
116 | # access the attributes through dictionary
117 | >>> wk["title"]
118 | u'title demo'
119 | # access the attributes through dot notation
120 | >>> wk.title
121 | u'title demo'
122 | >>> wk.state
123 | u'Closed'
124 | >>> wk.description
125 | u'demo description'
126 | >>> wk.creator
127 | u'tester1@email.com'
128 | >>> wk.created
129 | u'2015-07-16T08:02:30.658Z'
130 | >>> wk.comments
131 | [u'comment test 0', u'add comment test 1', u'add comment test 2']
132 |
133 |
134 | About Returned Properties
135 | -------------------------
136 |
137 | You can also customize your preferred properties to be returned
138 | by specifying **returned_properties** when the called methods have
139 | this optional parameter, which can also **GREATLY IMPROVE** the performance
140 | of this client especially when getting or querying lots of workitems.
141 |
142 | For the meanings of these attributes, please refer to
143 | :ref:`Built-in Attributes `.
144 |
145 | Important Note: **returned_properties** is an advanced parameter, the
146 | returned properties can be found in `instance_obj.field_alias.values()`,
147 | e.g. `myworkitem1.field_alias.values()`. If you don't care the performance,
148 | just leave it alone with `None`.
149 |
150 | .. _field_alias:
151 |
152 | >>> import pprint
153 | # print the field alias
154 | >>> pprint.pprint(wk2.field_alias, width=1)
155 | {u'affectedByDefect': u'calm:affectedByDefect',
156 | u'affectsExecutionResult': u'calm:affectsExecutionResult',
157 | u'affectsPlanItem': u'calm:affectsPlanItem',
158 | u'apply_step': u'rtc_cm:apply_step',
159 | u'archived': u'rtc_cm:archived',
160 | u'blocksTestExecutionRecord': u'calm:blocksTestExecutionRecord',
161 | u'comments': u'rtc_cm:comments',
162 | u'contextId': u'rtc_cm:contextId',
163 | u'correctedEstimate': u'rtc_cm:correctedEstimate',
164 | u'created': u'dc:created',
165 | u'creator': u'dc:creator',
166 | u'description': u'dc:description',
167 | u'due': u'rtc_cm:due',
168 | u'elaboratedByArchitectureElement': u'calm:elaboratedByArchitectureElement',
169 | u'estimate': u'rtc_cm:estimate',
170 | u'filedAgainst': u'rtc_cm:filedAgainst',
171 | u'foundIn': u'rtc_cm:foundIn',
172 | u'identifier': u'dc:identifier',
173 | u'implementsRequirement': u'calm:implementsRequirement',
174 | u'modified': u'dc:modified',
175 | u'modifiedBy': u'rtc_cm:modifiedBy',
176 | u'ownedBy': u'rtc_cm:ownedBy',
177 | u'plannedFor': u'rtc_cm:plannedFor',
178 | u'priority': u'oslc_cm:priority',
179 | u'progressTracking': u'rtc_cm:progressTracking',
180 | u'projectArea': u'rtc_cm:projectArea',
181 | u'relatedChangeManagement': u'oslc_cm:relatedChangeManagement',
182 | u'relatedExecutionRecord': u'calm:relatedExecutionRecord',
183 | u'relatedRequirement': u'calm:relatedRequirement',
184 | u'relatedTestCase': u'calm:relatedTestCase',
185 | u'relatedTestPlan': u'calm:relatedTestPlan',
186 | u'relatedTestScript': u'calm:relatedTestScript',
187 | u'relatedTestSuite': u'calm:relatedTestSuite',
188 | u'resolution': u'rtc_cm:resolution',
189 | u'resolved': u'rtc_cm:resolved',
190 | u'resolvedBy': u'rtc_cm:resolvedBy',
191 | u'schedule': u'oslc_pl:schedule',
192 | u'severity': u'oslc_cm:severity',
193 | u'startDate': u'rtc_cm:startDate',
194 | u'state': u'rtc_cm:state',
195 | u'subject': u'dc:subject',
196 | u'subscribers': u'rtc_cm:subscribers',
197 | u'teamArea': u'rtc_cm:teamArea',
198 | u'testedByTestCase': u'calm:testedByTestCase',
199 | u'timeSheet': u'rtc_cm:timeSheet',
200 | u'timeSpent': u'rtc_cm:timeSpent',
201 | u'title': u'dc:title',
202 | u'trackedWorkItem': u'oslc_cm:trackedWorkItem',
203 | u'tracksChanges': u'calm:tracksChanges',
204 | u'tracksRequirement': u'calm:tracksRequirement',
205 | u'tracksWorkItem': u'oslc_cm:tracksWorkItem',
206 | u'type': u'dc:type'}
207 |
208 | Note: these field aliases may differ due to the type of workitems. But most of
209 | the common-used attributes will stay unchanged.
210 |
211 | The `returned_properties` is a string **composed by the above values with
212 | comma separated**.
213 |
214 | It will run faster if `returned_properties` is specified. Because the client
215 | will only get/request the attributes you specified.
216 |
217 | >>> returned_properties = "dc:title,dc:identifier,rtc_cm:state,rtc_cm:ownedBy"
218 | # specify the returned properties: title, identifier, state, owner
219 | # This is optional. All properties will be returned if not specified
220 | >>> wk_rp = myclient.getWorkitem(123456,
221 | returned_properties=returned_properties)
222 | >>> wk_rp.identifier
223 | u'141488'
224 | # access the attributes through dictionary
225 | >>> wk_rp["title"]
226 | # access the attributes through dot notation
227 | u'title demo'
228 | >>> wk_rp.title
229 | u'title demo'
230 | >>> wk_rp.state
231 | u'Closed'
232 | >>> wk_rp.ownedBy
233 | u'tester1@email.com'
234 |
235 |
236 | Add a Comment to a Workitem
237 | ---------------------------
238 |
239 | After getting the :class:`rtcclient.workitem.Workitem` object, you can add a
240 | comment to this workitem by calling :class:`addComment`.
241 |
242 | >>> mycomment = wk.addComment("add comment test 3")
243 | >>> mycomment
244 |
245 | >>> mycomment.created
246 | u'2015-08-22T03:55:00.839Z'
247 | >>> mycomment.creator
248 | u'tester1@email.com'
249 | >>> mycomment.description
250 | u'add comment test 3'
251 | >>> str(mycomment)
252 | '3'
253 |
254 |
255 | Get all Workitems
256 | -----------------
257 |
258 | All workitems can be fetched by calling
259 | :class:`rtcclient.client.RTCClient.getWorkitems`. It will take
260 | a long time to fetch all the workitems in some certain project areas if there
261 | are already many existing workitems.
262 |
263 | If both `projectarea_id` and `projectarea_name` are None, all the workitems
264 | in all project areas will be returned.
265 |
266 | >>> workitems_list = myclient.getWorkitems(projectarea_id=None,
267 | projectarea_name=None,
268 | returned_properties=returned_properties)
269 | # get all workitems in a specific project area
270 | >>> projectarea_name = "my_projectarea_name"
271 | >>> workitems_list2 = myclient.getWorkitems(projectarea_name=projectarea_name,
272 | returned_properties=returned_properties)
273 |
274 |
275 | Query Workitems
276 | ---------------
277 |
278 | After customizing your query string, all the workitems meet the conditions
279 | will be fetched.
280 |
281 | >>> myquery = myclient.query # query class
282 | >>> projectarea_name = "my_projectarea_name"
283 | # customize your query string
284 | # below query string means: query all the workitems with title "use case 1"
285 | >>> myquerystr = 'dc:title="use case 1"'
286 | >>> returned_prop = "dc:title,dc:identifier,rtc_cm:state,rtc_cm:ownedBy"
287 | >>> queried_wis = myquery.queryWorkitems(myquerystr,
288 | projectarea_name=projectarea_name,
289 | returned_properties=returned_prop)
290 |
291 | More detailed and advanced syntax on querying, please refer to
292 | :ref:`query syntax `.
293 |
294 |
295 | Query Workitems by Saved Query
296 | ------------------------------
297 |
298 | You may have created several customized queries through RTC Web GUI or got
299 | some saved queries created by other team members. Using these saved queries
300 |
301 | >>> myquery = myclient.query # query class
302 | >>> saved_query_url = 'http://test.url:9443/jazz/xxxxxxxx&id=xxxxx'
303 | >>> projectarea_name = "my_projectarea_name"
304 | # get all saved queries
305 | # WARNING: now the RTC server cannot correctly list all the saved queries
306 | # It seems to be a bug of RTC. Recommend using `runSavedQueryByUrl` to
307 | # query all the workitems if the query is saved.
308 | >>> allsavedqueries = myquery.getAllSavedQueries(projectarea_name=projectarea_name)
309 | # saved queries created by tester1@email.com
310 | >>> allsavedqueries = myquery.getAllSavedQueries(projectarea_name=projectarea_name,
311 | creator="tester1@email.com")
312 | # my saved queries
313 | >>> mysavedqueries = myquery.getMySavedQueries(projectarea_name=projectarea_name)
314 | >>> mysavedquery = mysavedqueries[0]
315 | >>> returned_prop = "dc:title,dc:identifier,rtc_cm:state,rtc_cm:ownedBy"
316 | >>> queried_wis = myquery.runSavedQuery(mysavedquery,
317 | returned_properties=returned_prop)
318 |
319 |
320 | Query Workitems by Saved Query Url
321 | ----------------------------------
322 |
323 | You can also query all the workitems directly using your saved query's url.
324 |
325 | >>> myquery = myclient.query # query class
326 | >>> saved_query_url = 'http://test.url:9443/jazz/xxxxxxxx&id=xxxxx'
327 | >>> returned_prop = "dc:title,dc:identifier,rtc_cm:state,rtc_cm:ownedBy"
328 | >>> queried_wis = myquery.runSavedQueryByUrl(saved_query_url,
329 | returned_properties=returned_prop)
330 |
--------------------------------------------------------------------------------
/tests/fixtures/parent.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 |
8 | _vsoCMN-TEd6VxeRBsGx82Q
9 |
10 |
11 | 2015-07-27T06:50:40.477Z
12 |
13 |
14 |
15 |
16 |
17 | 141872
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 2015-09-11T08:36:52.599Z
26 |
27 |
28 |
29 |
30 |
31 |
32 | Operational Excellence
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------