Plugins

Modules in this directory are searched for available stats. Each plugin should contain a single class inheriting from StatsGroup. Stats from this group will be included in the report if enabled in user config. Name of the plugin should match config section type. Attribute order defines the order in the final report.

In addition to built-in plugins it is also possible to define your own stats. In order to enable such custom plugins add path to the python modules into the General section of the config file.

This is the default plugin order:

header

000

google

050

nitrate

100

bugzilla

200

git

300

github

330

gerrit

350

phabricator

360

gitlab

380

pagure

390

trac

400

bodhi

410

koji

420

trello

450

rt

500

redmine

550

jira

600

sentry

650

zammad

680

wiki

700

items

800

footer

900

bodhi

Bodhi stats

Config example:

[bodhi]
type = bodhi
url = https://bodhi.fedoraproject.org/
login = <username>
class did.plugins.bodhi.Bodhi(url)
search(query)

Perform Bodhi query

class did.plugins.bodhi.BodhiStats(option, name=None, parent=None, user=None)

Bodhi work

order = 410
class did.plugins.bodhi.Update(data, format)

Bodhi update

class did.plugins.bodhi.UpdatesCreated(option, name=None, parent=None, user=None, options=None)

Updates created

fetch()

Fetch the stats (to be implemented by respective class).

bugzilla

Bugzilla stats such as filed, fixed or verified bugs

This plugin uses python-bugzilla module to gather the stats. By default reports contain only publicly available issues. Use the bugzilla login command to initialize Bugzilla cookies or get an API key from the Preferences and store it in the config file .config/python-bugzilla/bugzillarc:

[bugzilla.redhat.com]
api_key=YOUR-API-KEY

Config example:

[bz]
type = bugzilla
prefix = BZ
url = https://bugzilla.redhat.com/xmlrpc.cgi
resolutions = notabug, duplicate
Resolutions:

List of resolutions to be displayed at the end of the summary if bug is closed. By default notabug and duplicate are shown. Use all to always display resolution if available or none to turn off the feature completely.

Available options:

--bz-filed

Bugs filed

--bz-patched

Bugs patched

--bz-posted

Bugs posted

--bz-fixed

Bugs fixed

--bz-returned

Bugs returned

--bz-verified

Bugs verified

--bz-commented

Bugs commented

--bz-subscribed

Bugs subscribed

--bz-closed

Bugs closed

--bz

All above

class did.plugins.bugzilla.Bug(bug, history, comments, parent)

Bugzilla search

closed(user)

Moved to CLOSED and not later moved to ASSIGNED

commented(user)

True if comment was added in given time frame

fixed()

Moved to MODIFIED and not later moved to ASSIGNED

property logs

Return relevant who-did-what pairs from the bug history

patched(user)

True if Patch was added to Keywords field by given user

posted()

True if bug was moved to POST in given time frame

returned(user)

Moved to ASSIGNED by given user (but not from NEW)

subscribed(user)

True if CC was added in given time frame

property summary

Bug summary including resolution if enabled

verified()

True if bug was verified in given time frame

class did.plugins.bugzilla.Bugzilla(parent)

Bugzilla investigator

search(query, options)

Perform Bugzilla search

property server

Connection to the server

class did.plugins.bugzilla.BugzillaStats(option, name=None, parent=None, user=None)

Bugzilla stats

order = 200
class did.plugins.bugzilla.ClosedBugs(option, name=None, parent=None, user=None, options=None)

Bugs closed

Bugs which have been moved to the CLOSED state in given time frame and later have not been moved back to the ASSIGNED state (which would suggest the bug was not closed for a proper reason).

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.bugzilla.CommentedBugs(option, name=None, parent=None, user=None, options=None)

Bugs commented

All bugs commented by given user in requested time frame.

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.bugzilla.FiledBugs(option, name=None, parent=None, user=None, options=None)

Bugs filed

Newly created bugs by given user, marked as the Reporter.

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.bugzilla.FixedBugs(option, name=None, parent=None, user=None, options=None)

Bugs fixed

Bugs which have been moved to the MODIFIED state in given time frame and later have not been moved back to the ASSIGNED state (which would suggest an incomplete fix).

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.bugzilla.PatchedBugs(option, name=None, parent=None, user=None, options=None)

Bugs patched

Gathers bugs with keyword Patch added by given user, denoting the patch for the issue is available (e.g. attached to the bug or pushed to a feature git branch).

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.bugzilla.PostedBugs(option, name=None, parent=None, user=None, options=None)

Bugs posted

Bugs with patches posted for review, detected by their status change to POST and given user set as Assignee.

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.bugzilla.ReturnedBugs(option, name=None, parent=None, user=None, options=None)

Bugs returned

Returned bugs are those which were returned by given user to the ASSIGNED status, meaning the fix for the issue is not correct or complete.

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.bugzilla.SubscribedBugs(option, name=None, parent=None, user=None, options=None)

Bugs subscribed

All bugs subscribed by given user in requested time frame.

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.bugzilla.VerifiedBugs(option, name=None, parent=None, user=None, options=None)

Bugs verified

Bugs with QA Contact field set to given user or changed by the given user and having their status changed to VERIFIED.

fetch()

Fetch the stats (to be implemented by respective class).

confluence

Confluence stats such as created pages and comments

Configuration example (GSS authentication):

[confluence]
type = confluence
url = https://docs.jboss.org/

Configuration example (basic authentication):

[jboss]
type = confluence
url = https://docs.jboss.org/
auth_url = https://docs.jboss.org/rest/auth/latest/session
auth_type = basic
auth_username = username
auth_password = password
auth_password_file = ~/.did/confluence_password

Notes:

  • Optional parameter ssl_verify can be used to enable/disable SSL verification (default: true)

  • auth_url parameter is optional. If not provided, url + "/step-auth-gss" will be used for authentication.

  • auth_type parameter is optional, default value is gss.

  • auth_username, auth_password and auth_password_file are only valid for basic authentication, auth_password or auth_password_file must be provided, auth_password has a higher priority.

class did.plugins.confluence.CommentAdded(option, name=None, parent=None, user=None, options=None)
fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.confluence.Confluence

Confluence investigator

static search(query, stats, expand=None)

Perform page/comment search for given stats instance

class did.plugins.confluence.ConfluenceComment(comment)

Confluence comment results

class did.plugins.confluence.ConfluencePage(page)

Confluence page results

class did.plugins.confluence.ConfluenceStats(option, name=None, parent=None, user=None)

Confluence stats

order = 600
property session

Initialize the session

class did.plugins.confluence.PageCreated(option, name=None, parent=None, user=None, options=None)

Created pages

fetch()

Fetch the stats (to be implemented by respective class).

footer

Customizable footer

Config example:

[footer]
type = footer
next = Plans, thoughts, ideas...
status = Status: Green | Yellow | Orange | Red

New line characters can be used in the definition as well. In this way it's possible to define subitems:

[footer]
type = footer
nested = Main topic\n  * Item one\n  * Item two\n  * Item three

Will produce the following output:

* Main topic
  * Item one
  * Item two
  * Item three
class did.plugins.footer.Footer(option, name=None, parent=None, user=None)
order = 900

gerrit

Gerrit stats such as submitted, review or merged changes

Config example:

[gerrit]
type = gerrit
url = https://example.org/gerrit/
prefix = GR
# optional, True by default; set to False if the gerrit server
# does not support wip as search criteria.
wip = True
class did.plugins.gerrit.AbandonedChanges(option, name=None, parent=None, base_url=None, prefix=None)

Changes abandoned

fetch()

Backend for the actual gerrit query.

query_string:

basic query terms, e.g., 'status:abandoned'

common_query_options:

[optional] rest of the query string; if omitted, the default one is used (limit by the current user and since option); if empty, nothing will be added to query_string

limit_since:

[optional] Boolean (defaults to False) post-process the results to eliminate items created after since option.

class did.plugins.gerrit.AddedPatches(option, name=None, parent=None, base_url=None, prefix=None)

Additional patches added to existing changes

fetch()

Backend for the actual gerrit query.

query_string:

basic query terms, e.g., 'status:abandoned'

common_query_options:

[optional] rest of the query string; if omitted, the default one is used (limit by the current user and since option); if empty, nothing will be added to query_string

limit_since:

[optional] Boolean (defaults to False) post-process the results to eliminate items created after since option.

class did.plugins.gerrit.Change(ticket, prefix, changelog=None)

Request gerrit change

class did.plugins.gerrit.Gerrit(baseurl, prefix)

curl -s 'https://REPOURL/gerrit/changes/?q=is:abandoned+age:7d'

get_changelog(chg)
get_query_result(url)
static join_URL_frags(base, query)
search(query)
class did.plugins.gerrit.GerritStats(option, name=None, parent=None, user=None)

Gerrit

order = 350
class did.plugins.gerrit.GerritUnit(option, name=None, parent=None, base_url=None, prefix=None)

General mother class offering general services for querying Gerrit repo.

fetch(query_string='', common_query_options=None, limit_since=False)

Backend for the actual gerrit query.

query_string:

basic query terms, e.g., 'status:abandoned'

common_query_options:

[optional] rest of the query string; if omitted, the default one is used (limit by the current user and since option); if empty, nothing will be added to query_string

limit_since:

[optional] Boolean (defaults to False) post-process the results to eliminate items created after since option.

static get_gerrit_date(instr)
class did.plugins.gerrit.MergedChanges(option, name=None, parent=None, base_url=None, prefix=None)

Changes successfully merged

fetch()

Backend for the actual gerrit query.

query_string:

basic query terms, e.g., 'status:abandoned'

common_query_options:

[optional] rest of the query string; if omitted, the default one is used (limit by the current user and since option); if empty, nothing will be added to query_string

limit_since:

[optional] Boolean (defaults to False) post-process the results to eliminate items created after since option.

class did.plugins.gerrit.ReviewedChanges(option, name=None, parent=None, base_url=None, prefix=None)

Review of a change (for reviewers)

fetch()

Backend for the actual gerrit query.

query_string:

basic query terms, e.g., 'status:abandoned'

common_query_options:

[optional] rest of the query string; if omitted, the default one is used (limit by the current user and since option); if empty, nothing will be added to query_string

limit_since:

[optional] Boolean (defaults to False) post-process the results to eliminate items created after since option.

class did.plugins.gerrit.SubmitedChanges(option, name=None, parent=None, base_url=None, prefix=None)

Changes submitted for review

fetch()

Backend for the actual gerrit query.

query_string:

basic query terms, e.g., 'status:abandoned'

common_query_options:

[optional] rest of the query string; if omitted, the default one is used (limit by the current user and since option); if empty, nothing will be added to query_string

limit_since:

[optional] Boolean (defaults to False) post-process the results to eliminate items created after since option.

class did.plugins.gerrit.WIPChanges(option, name=None, parent=None, base_url=None, prefix=None)

Work in progress changes

fetch()

Backend for the actual gerrit query.

query_string:

basic query terms, e.g., 'status:abandoned'

common_query_options:

[optional] rest of the query string; if omitted, the default one is used (limit by the current user and since option); if empty, nothing will be added to query_string

limit_since:

[optional] Boolean (defaults to False) post-process the results to eliminate items created after since option.

git

Git commits

Config example:

[tools]
type = git
did = ~/git/did
edd = ~/git/edd
fmf = ~/git/fmf

[tests]
type = git
fedora = ~/tests/fedora/*
rhel = ~/tests/rhel/*

Note that using an * you can enable multiple git repositories at once. Non git directories from the expansion are silently ignored.

class did.plugins.git.GitCommits(option, name=None, parent=None, path=None)

Git commits

fetch()

Fetch the stats (to be implemented by respective class).

header()

Show summary header.

class did.plugins.git.GitRepo(path)

Git repository investigator

commits(user, options)

List commits for given user.

class did.plugins.git.GitStats(option, name=None, parent=None, user=None)

Git stats group

order = 300

github

GitHub stats such as created and closed issues

Config example:

[github]
type = github
url = https://api.github.com/
token = <authentication-token>
login = <username>

The authentication token is optional. However, unauthenticated queries are limited. For more details see GitHub API docs. Use login to override the default email address for searching. See the Config documentation for details on using aliases.

Alternatively to token you can use token_file to have the token stored in a file rather than in your did config file.

class did.plugins.github.GitHub(url, token)

GitHub Investigator

search(query)

Perform GitHub query

class did.plugins.github.GitHubStats(option, name=None, parent=None, user=None)

GitHub work

order = 330
class did.plugins.github.Issue(data, parent)

GitHub Issue

class did.plugins.github.IssueCommented(option, name=None, parent=None, user=None, options=None)

Issues commented

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.github.IssuesClosed(option, name=None, parent=None, user=None, options=None)

Issues closed

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.github.IssuesCreated(option, name=None, parent=None, user=None, options=None)

Issues created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.github.PullRequestsClosed(option, name=None, parent=None, user=None, options=None)

Pull requests closed

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.github.PullRequestsCommented(option, name=None, parent=None, user=None, options=None)

Pull requests commented

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.github.PullRequestsCreated(option, name=None, parent=None, user=None, options=None)

Pull requests created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.github.PullRequestsReviewed(option, name=None, parent=None, user=None, options=None)

Pull requests reviewed

fetch()

Fetch the stats (to be implemented by respective class).

gitlab

GitLab stats such as created and closed issues

Config example:

[gitlab]
type = gitlab
url = https://gitlab.com/
token = <authentication-token>
token_file = <authentication-token-file>
login = <username>
ssl_verify = true

The authentication token is required. Create it in the GitLab web interface (select api as the desired scope). See the GitLab API documentation for details.

Use login to override user name detected from the email address. See the Config documentation for details on using aliases. Use ssl_verify to enable/disable SSL verification (default: true)

class did.plugins.gitlab.GitLab(url, token, ssl_verify=True)

GitLab Investigator

get_project(project_id)
get_project_issue(project_id, issue_id)
get_project_issues(project_id)
get_project_mr(project_id, mr_id)
get_project_mrs(project_id)
get_user(username)
search(user, since, until, target_type, action_name)

Perform GitLab query

user_events(user_id, since, until)
class did.plugins.gitlab.GitLabStats(option, name=None, parent=None, user=None)

GitLab work

order = 380
class did.plugins.gitlab.Issue(data, parent)

GitLab Issue

iid()
class did.plugins.gitlab.IssuesClosed(option, name=None, parent=None, user=None, options=None)

Issue closed

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.gitlab.IssuesCommented(option, name=None, parent=None, user=None, options=None)

Issue commented

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.gitlab.IssuesCreated(option, name=None, parent=None, user=None, options=None)

Issue created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.gitlab.MergeRequest(data, parent)
iid()
class did.plugins.gitlab.MergeRequestsApproved(option, name=None, parent=None, user=None, options=None)

Merge requests approved

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.gitlab.MergeRequestsClosed(option, name=None, parent=None, user=None, options=None)

Merge requests closed

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.gitlab.MergeRequestsCommented(option, name=None, parent=None, user=None, options=None)

MergeRequests commented

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.gitlab.MergeRequestsCreated(option, name=None, parent=None, user=None, options=None)

Merge requests created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.gitlab.Note(data, parent)
iid()

google

Google stats such as attended events, completed tasks or sent emails

Config example:

[google]
type = google
client_id = <client_id>
client_secret = <client_secret>
apps = calendar,tasks
storage = ~/.did/google-api-credentials.json

Make sure you have additional dependencies of the google plugin installed on your system:

sudo dnf install python3-google-api-client python3-oauth2client  # dnf
pip install did[google]                                          # pip

To retrieve data via Google API, you will need to create access credentials (client_id and client_secret) first. Perform the following steps to create such a pair:

  1. Open https://console.developers.google.com/flows/enableapi?apiid=calendar,tasks

  2. You will need to create new project first, select organization and location for it

  3. Enable both APIs (tasks and calendar) on the next page after you confirm

  4. From the left tab go to 'APIs & Services' and 'OAuth consent screen'

  5. In What data will you be accessing?, choose User data

  6. In there create new and fill at least an app name and emails

  7. On the next page select all scopes or at least all relevant to calendar and tasks, you will need to go through all the pages

  8. Save it and go to 'Credentials' tab

  9. Select 'Create credentials' and choose 'OAuth client ID'

  10. Choose app type, doesn't matter but Desktop is likely the correct choice and add a name

  11. With that created you will be presented with client_id and client_secret which you can save into your config file

The apps configuration option defines the scope of user data the application will request (read-only) access to. Currently, the only supported values are calendar and tasks.

During the first run, user will be asked to grant the plugin access rights to selected apps. If the user approves the request, this decision is remembered by creating a credential storage file. The path to the storage can be customized by configuring the storage option.

If you want to store the client_id and client_secret not as plain text within your config file, use client_id_file and client_secret_file to point to files with the corresponding files.

class did.plugins.google.Event(dict)

Google Calendar Event

attended_by(email)

Check if user attended the event

created_by(email)

Check if user created the event

organized_by(email)

Check if user created the event

class did.plugins.google.GoogleCalendar(http)

Google Calendar functions

events(**kwargs)

Fetch events meeting specified criteria

class did.plugins.google.GoogleEventsAttended(option, name=None, parent=None)

Events attended

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.google.GoogleEventsOrganized(option, name=None, parent=None)

Events organized

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.google.GoogleStatsBase(option, name=None, parent=None)

Base class containing common code

property events

All events in calendar within specified time range

property tasks

All completed tasks within specified time range

class did.plugins.google.GoogleStatsGroup(option, name=None, parent=None, user=None)

Google stats group

order = 50
class did.plugins.google.GoogleTasks(http)

Google Tasks functions

tasks(**kwargs)

Fetch tasks specified criteria

class did.plugins.google.GoogleTasksCompleted(option, name=None, parent=None)

Tasks completed

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.google.Task(dict)

Google Tasks task

did.plugins.google.authorized_http(client_id, client_secret, apps, file=None)

Start an authorized HTTP session.

Try fetching valid user credentials from storage. If nothing has been stored, or if the stored credentials are invalid, complete the OAuth2 flow to obtain new credentials.

header

Customizable header

Config example:

[header]
type = header
highlights = Highlights
joy = Joy of the week ;-)

New line characters can be used in the definition as well. In this way it's possible to define subitems:

[header]
type = header
nested = Main topic\n  * Item one\n  * Item two\n  * Item three

Will produce the following output:

* Main topic
  * Item one
  * Item two
  * Item three
class did.plugins.header.Header(option, name=None, parent=None, user=None)
order = 0

items

Custom section with multiple items

Config example:

[projects]
type = items
header = Work on projects
item1 = Project One
item2 = Project Two
item3 = Project Three
class did.plugins.items.CustomStats(option, name=None, parent=None, user=None)

Custom stats

order = 800
class did.plugins.items.ItemStats(option, name=None, parent=None)

Custom section with given items

fetch()

Fetch the stats (to be implemented by respective class).

header()

Simple header for custom stats (no item count)

jira

Jira stats such as created, updated or resolved issues

Configuration example (token):

[issues]
type = jira
url = https://issues.redhat.com/
auth_type = token
token_file = ~/.did/jira-token
token_expiration = 7
token_name = did-token

Either token or token_file has to be defined.

token

Token string directly included in the config. Has a higher priority over token_file.

token_file

Path to the file where the token is stored.

token_expiration

Print warning if token with provided token_name expires within specified number of days.

token_name

Name of the token to check for expiration in token_expiration days. This has to match the name as seen in your Jira profile.

Configuration example (GSS authentication):

[issues]
type = jira
url = https://issues.redhat.org/
ssl_verify = true

Configuration example (basic authentication):

[issues]
type = jira
url = https://issues.redhat.org/
auth_url = https://issues.redhat.org/rest/auth/latest/session
auth_type = basic
auth_username = username
auth_password = password
auth_password_file = ~/.did/jira_password

Keys auth_username, auth_password and auth_password_file are only valid for basic authentication. Either auth_password or auth_password_file must be provided, auth_password has a higher priority.

Configuration example limiting report only to a single project, using an alternative username and a custom identifier prefix:

[issues]
type = jira
project = ORG
prefix = JIRA
login = alt_username
url = https://issues.redhat.org/
ssl_verify = true

Notes:

  • If your JIRA does not have scriptrunner installed you must set use_scriptrunner to false.

  • You must provide login variable that matches username if it doesn't match email/JIRA account.

  • Optional parameter ssl_verify can be used to enable/disable SSL verification (default: true).

  • The auth_url parameter is optional. If not provided, url + "/step-auth-gss" will be used for authentication. Its value is ignored for token auth_type.

  • The auth_type parameter is optional, default value is gss. Other values are basic and token.

class did.plugins.jira.Issue(issue=None, parent=None)

Jira issue investigator

static search(query, stats)

Perform issue search for given stats instance

updated(user, options)

True if the issue was commented by given user

class did.plugins.jira.JiraCreated(option, name=None, parent=None, user=None, options=None)

Created issues

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.jira.JiraResolved(option, name=None, parent=None, user=None, options=None)

Resolved issues

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.jira.JiraStats(option, name=None, parent=None, user=None)

Jira stats

order = 600
property session

Initialize the session

class did.plugins.jira.JiraUpdated(option, name=None, parent=None, user=None, options=None)

Updated issues

fetch()

Fetch the stats (to be implemented by respective class).

koji

Finished Koji builds

Config example:

[koji]
type = koji
url = https://koji.example.org/kojihub
login = testuser
name = Example koji server
class did.plugins.koji.KojiBuilds(option, name=None, parent=None, user=None, options=None, server=None, userinfo=None)

Finished koji builds

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.koji.KojiStats(option, name=None, parent=None, user=None)

Koji work

order = 420

nitrate

Nitrate stats such as created test plans, runs, cases

Config example:

[nitrate]
type = nitrate
class did.plugins.nitrate.AutomatedCases(option, name=None, parent=None, user=None, options=None)

Automated cases created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.nitrate.AutoproposedCases(option, name=None, parent=None, user=None, options=None)

Cases proposed for automation

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.nitrate.CopiedCases(option, name=None, parent=None, user=None, options=None)

Test cases copied

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.nitrate.ManualCases(option, name=None, parent=None, user=None, options=None)

Manual cases created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.nitrate.NitrateStats(option, name=None, parent=None, user=None)

Nitrate stats

property cases

All test cases created by the user

property copies

All test case copies created by the user

order = 100
class did.plugins.nitrate.TestPlans(option, name=None, parent=None, user=None, options=None)

Test plans created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.nitrate.TestRuns(option, name=None, parent=None, user=None, options=None)

Test runs finished

fetch()

Fetch the stats (to be implemented by respective class).

pagure

Pagure stats such as created/closed issues and pull requests.

Config example:

[pagure]
type = pagure
url = https://pagure.io/api/0/
login = <username>
token = <authentication-token>

Use login to override the default email address for searching. See the Config documentation for details on using aliases. The authentication token is optional and can be stored in a file pointed to by token_file instead of token.

class did.plugins.pagure.Issue(data, options)

Pagure Issue or Pull Request

class did.plugins.pagure.IssuesClosed(option, name=None, parent=None, user=None, options=None)

Issues closed

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.pagure.IssuesCreated(option, name=None, parent=None, user=None, options=None)

Issues created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.pagure.Pagure(url, token)

Pagure Investigator

search(query, pagination, result_field)

Perform Pagure query

class did.plugins.pagure.PagureStats(option, name=None, parent=None, user=None)

Pagure work

order = 390
class did.plugins.pagure.PullRequestsCreated(option, name=None, parent=None, user=None, options=None)

Pull requests created

fetch()

Fetch the stats (to be implemented by respective class).

phabricator

Phabricator stats for authored and reviewed differentials, aka reviews.

Config example:

[phabricator]
type = phabricator
url = https://reviews.llvm.org/api/
token = <authentication-token>
token_file = <file-with-authentication-token>
login = <username1>,<username2>

The authentication token is not optional. Go to https://reviews.llvm.org/settings/user/<username>/page/apitokens/ and get yourself a "Conduit API token". The token and the actual users for which we query stats are decoupled, allowing you to specify more than one username.

We use these endpoints for the most part:

class did.plugins.phabricator.Differential(data)

Phabricator Differential

Here's an example:

{
 "id": 134852,
 "type": "DREV",
 "phid": "PHID-DREV-6vyvvzlqatx6a4oveqkw",
 "fields":
 {
  "title": "[clang-format][NFC] Clean up class HeaderIncludes",
  "uri": "https://reviews.llvm.org/D134852",
  "authorPHID": "PHID-USER-vou2cb5rty2zlopptj5z",
  "status":
  {
    "value": "published",
    "name": "Closed",
    "closed": true,
    "color.ansi": "cyan"
  },
  "repositoryPHID": "PHID-REPO-f4scjekhnkmh7qilxlcy",
  "diffPHID": "PHID-DIFF-6qic23rkxpwvkp6g4wdg",
  "summary": "",
  "testPlan": "",
  "isDraft": false,
  "holdAsDraft": false,
  "dateCreated": 1664433452,
  "dateModified": 1665032091,
  "policy":
  {
   "view": "public",
   "edit": "users"
  }
 },
 "attachments": {}
}
property id: str

Returns the Phabricator ID for the differential as a string

property phid: str

Returns the Phabricator ID for the differential as a string

property title: str

Returns the Phabricator title for the differential as a string

property uri: str

Returns the Phabricator URI for the differential as a string

class did.plugins.phabricator.DifferentialsAccepted(**kwargs)

Differentials accepted

fetch()

To be implemented by subclasses

class did.plugins.phabricator.DifferentialsBaseStats(**kwargs)

Represent the base class of phabricator statistics

diffs_accepted = {}
diffs_closed = {}
diffs_commented = {}
diffs_created = {}
diffs_requested_changes = {}
fetch()

To be implemented by subclasses

fetch_all_relevant_diffs()

Fetches all differentials that we possibly need for all phabricator stats.

got_diffs = False
verbose = False
class did.plugins.phabricator.DifferentialsClosed(**kwargs)

Differentials closed

fetch()

To be implemented by subclasses

class did.plugins.phabricator.DifferentialsCommented(**kwargs)

Differentials commented

fetch()

To be implemented by subclasses

class did.plugins.phabricator.DifferentialsCreated(**kwargs)

Differentials created

fetch()

To be implemented by subclasses

class did.plugins.phabricator.DifferentialsRequestedChanges(**kwargs)

Differentials where changes were requested

fetch()

To be implemented by subclasses

class did.plugins.phabricator.EventType(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

EventType defines what type of transaction events we support.

ACCEPT = 'accept'
CLOSE = 'close'
COMMENT = 'comment'
CREATE = 'create'
INLINE = 'inline'
PROJECTS = 'projects'
REQUEST_CHANGES = 'request-changes'
REQUEST_REVIEW = 'request-review'
REVIEWERS = 'reviewers'
STATUS = 'status'
SUBSCRIBERS = 'subscribers'
SUMMARY = 'summary'
TITLE = 'title'
UNDEFINED = ''
UPDATE = 'update'
class did.plugins.phabricator.Phabricator(url, token, logins)

Phabricator Investigator

MAX_PAGE_SIZE = 100
property login_phids: List[str]

Returns the PHIDs for the login usernames.

Returns:

List[str]: The phabricator PHIDs for the login names

TODO(kwk): The return type could be just list[str] but the

Copr epel builder currently only has python 3.6 where this is not possible.

search_diffs(since: date = None, until: date = None, author_phids: List[str] = None, subscriber_phids: List[str] = None, responsible_phids: List[str] = None, reviewer_phids: List[str] = None) Set[Differential]

Find Phabricator Differentials

search_transactions(diff: Differential, author_phids: List[str] = None) Set[TransactionEvent]

Returns all the transaction events for a given differential object. If given you can search for events by certain authors.

class did.plugins.phabricator.PhabricatorStats(option, name=None, parent=None, user=None)

Phabricator work

order = 360
class did.plugins.phabricator.TransactionEvent(data)

Phabricator Transaction event.

See https://reviews.llvm.org/conduit/method/transaction.search/.

Here're examples:

{
    "id": 4077171,
    "phid": "PHID-XACT-DREV-7grkcftntvxf24c",
    "type": "create",
    "authorPHID": "PHID-USER-m46saogacat2jslbykue",
    "objectPHID": "PHID-DREV-ypgxje4hhhdefuy4d6sz",
    "dateCreated": 1674573526,
    "dateModified": 1674573526,
    "groupID": "dr3e2g6tx6ztr6zivk343kytk7uk7yng",
    "comments": [],
    "fields": {}
}

{
    "id": 4077175,
    "phid": "PHID-XACT-DREV-zconyio2dw2y7ne",
    "type": "reviewers",
    "authorPHID": "PHID-USER-m46saogacat2jslbykue",
    "objectPHID": "PHID-DREV-ypgxje4hhhdefuy4d6sz",
    "dateCreated": 1674573526,
    "dateModified": 1674573526,
    "groupID": "dr3e2g6tx6ztr6zivk343kytk7uk7yng",
    "comments": [],
    "fields": {
        "operations": [
        {
            "operation": "add",
            "phid": "PHID-USER-aigeqxvzdke5r36hodix",
            "oldStatus": null,
            "newStatus": "added",
            "isBlocking": false
        },
        {
            "operation": "add",
            "phid": "PHID-USER-7rdtwvftotyrjl5bf7gy",
            "oldStatus": null,
            "newStatus": "added",
            "isBlocking": false
        },
        {
            "operation": "add",
            "phid": "PHID-USER-icssaf6rtj6ahq4lchay",
            "oldStatus": null,
            "newStatus": "added",
            "isBlocking": false
        }
        ]
    }
},
property author_phid: str

Returns the author's PHID

property event_type: EventType

Returns the type of event

is_in_date_range(since: date = None, until: date = None) bool

Returns true if the event happend in the given timestamp range, including the boundaries.

is_type(event_type: EventType) bool

Returns true if the transaction refers to an event of the given type.

redmine

Redmine stats

Config example:

[redmine]
type = redmine
url = https://redmine.example.com/
login = <user_db_id>
activity_days = 30

Use login to set the database user id in Redmine (number not login name). See the Config docs for details on using aliases. Use activity_days to override the default 30 days of activity paging, this has to match to the server side setting, otherwise the plugin will miss entries.

class did.plugins.redmine.Activity(data)

Redmine Activity

class did.plugins.redmine.RedmineActivity(option, name=None, parent=None, user=None, options=None)

Redmine Activity Stats

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.redmine.RedmineStats(option, name=None, parent=None, user=None)

Redmine Stats

order = 550

rt

Request Tracker stats such as reported and resolved tickets

Config example:

[rt]
type = rt
prefix = RT
url = https://tracker.org/rt/Search/Results.tsv
class did.plugins.rt.ReportedTickets(option, name=None, parent=None, user=None, options=None)

Tickets reported

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.rt.RequestTracker(parent)

Request Tracker Investigator

get(path)

Perform a GET request with GSSAPI authentication

search(query)

Perform request tracker search

class did.plugins.rt.RequestTrackerStats(option, name=None, parent=None, user=None)

Request Tracker

order = 500
class did.plugins.rt.ResolvedTickets(option, name=None, parent=None, user=None, options=None)

Tickets resolved

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.rt.Ticket(record, parent)

Request tracker ticket

sentry

Sentry stats such as commented and resolved issues.

Configuration example:

[sentry]
type = sentry
url = https://sentry.io/api/0/
organization = team
token = ...

You need to generate authentication token at the server. The only scope you need to enable is org:read. If you prefer to store the token in a file, use token_file to point to the file that has your token.

class did.plugins.sentry.Activity(activity)

Sentry Activity

class did.plugins.sentry.CommentedIssues(option, name=None, parent=None, user=None, options=None)

Issues commented

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.sentry.Issue(issue)

Sentry Issue

class did.plugins.sentry.ResolvedIssues(option, name=None, parent=None, user=None, options=None)

Issues resolved

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.sentry.Sentry(config, stats)

Sentry API

activities()

Return all activites (fetch only once)

issues(kind, email)

Filter unique issues for given activity type and email

class did.plugins.sentry.SentryStats(option, name=None, parent=None, user=None)

Sentry stats

order = 650

trac

Trac stats such as created, accepted, updated and closed tickets

Config example:

[trac]
type = trac
prefix = TT
url = https://some.trac.com/trac/project/rpc
class did.plugins.trac.Trac(ticket=None, changelog=None, parent=None, options=None)

Trac investigator

accepted(user)

True if ticket was accepted in given time frame

closed()

True if ticket was closed in given time frame

history(user=None)

Return relevant who-did-what logs from the ticket history

static search(query, parent, options)

Perform Trac search

updated(user)

True if the user commented the ticket in given time frame

class did.plugins.trac.TracAccepted(option, name=None, parent=None)

Accepted tickets

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trac.TracClosed(option, name=None, parent=None)

Closed tickets

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trac.TracCommon(option, name=None, parent=None)

Common Trac Stats object for saving prefix & proxy

class did.plugins.trac.TracCreated(option, name=None, parent=None)

Created tickets

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trac.TracStats(option, name=None, parent=None, user=None)

Trac stats group

order = 400
class did.plugins.trac.TracUpdated(option, name=None, parent=None)

Updated tickets

fetch()

Fetch the stats (to be implemented by respective class).

trello

Trello actions such as created, moved or closed cards

Config example (public):

[tools]
type = trello
user = member

Config example (private):

[tools]
type = trello
apikey = ...
token = ...

Optional arguments:

board_links = g9mdhdzg
filters = createCard, updateCard,
    updateCard:idList, updateCard:closed,
    updateCheckItemStateOnCard

apikey
    https://trello.com/app-key

token
    http://stackoverflow.com/questions/17178907

token_file
    the token stored in a file

boards
    default: all

filters
    default: all
class did.plugins.trello.TrelloAPI(stats, config)

Trello API

Convert board links to ids

get_actions(filters, since=None, before=None, limit=1000)

Example of data structure: https://api.trello.com/1/members/ben/actions?limit=2

class did.plugins.trello.TrelloCardsClosed(trello, filt, option, name=None, parent=None)

Trello cards closed

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trello.TrelloCardsCommented(trello, filt, option, name=None, parent=None)

Trello cards commented

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trello.TrelloCardsCreated(trello, filt, option, name=None, parent=None)

Trello cards created

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trello.TrelloCardsMoved(trello, filt, option, name=None, parent=None)

Trello cards moved

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trello.TrelloCardsUpdated(trello, filt, option, name=None, parent=None)

Trello cards updated

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trello.TrelloCheckItem(trello, filt, option, name=None, parent=None)

Trello checklist items completed

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.trello.TrelloStats(trello, filt, option, name=None, parent=None)

Trello stats

class did.plugins.trello.TrelloStatsGroup(option, name=None, parent=None, user=None)

Trello stats group

order = 450
property session

Initialize the session

wiki

MoinMoin wiki stats about updated pages

Config example:

[wiki]
type = wiki
wiki test = http://moinmo.in/

The optional key 'api' can be used to change the default xmlrpc api endpoint:

[wiki]
type = wiki
api = ?action=xmlrpc2
wiki test = http://moinmo.in/
class did.plugins.wiki.WikiChanges(option, name=None, parent=None, url=None, api=None)

Wiki changes

fetch()

Fetch the stats (to be implemented by respective class).

header()

Show summary header.

merge(other)

Merge another stats.

class did.plugins.wiki.WikiStats(option, name=None, parent=None, user=None)

Wiki stats

order = 700

zammad

Zammad stats such as updated tickets

Config example:

[zammad]
type = zammad
url = https://zammad.example.com/api/v1/
token = <authentication-token>

Optionally use token_file to store the token in a file instead of plain in the config file.

class did.plugins.zammad.Ticket(data)

Zammad Ticket

class did.plugins.zammad.TicketsUpdated(option, name=None, parent=None, user=None, options=None)

Tickets updated

fetch()

Fetch the stats (to be implemented by respective class).

class did.plugins.zammad.Zammad(url, token)

Zammad Investigator

search(query)

Perform Zammad query

class did.plugins.zammad.ZammadStats(option, name=None, parent=None, user=None)

Zammad work

order = 680