Python library for calculating various metrics related to the software development process. Provides developer and team velocity calculations based on data from Jira and Azure DevOps. Metrics calculation classes use interfaces, so the library can be easily extended with other data providers (e.g., Trello, Asana) from application code.
This library separates metric calculation from data sourcing. Calculators operate on abstract provider interfaces so you can plug in Jira, Azure DevOps, or your own sources. Below is a structured overview by package with links to the key classes you will use.
- Module:
sd_metrics_lib.calculators.metrics
MetricCalculator
(abstract): Base interface for all metric calculators (calculate()
).
- Module:
sd_metrics_lib.calculators.velocity
AbstractMetricCalculator
(abstract): Adds lazy extraction and sharedcalculate()
workflow.UserVelocityCalculator
: Per-user velocity (story points per time unit). RequiresTaskProvider
,StoryPointExtractor
,WorklogExtractor
.GeneralizedTeamVelocityCalculator
: Team velocity (total story points per time unit). RequiresTaskProvider
,StoryPointExtractor
,TaskTotalSpentTimeExtractor
.
- Module:
sd_metrics_lib.sources.tasks
TaskProvider
(abstract): Fetches a list of tasks/work items.ProxyTaskProvider
: Wraps a pre-fetched list of tasks (useful for tests/custom sources).CachingTaskProvider
: Caches results of anyTaskProvider
. Cache key is built fromprovider.query
andprovider.additional_fields
; works with any dict-like cache (e.g.,cachetools.TTLCache
).
- Module:
sd_metrics_lib.sources.story_points
StoryPointExtractor
(abstract)ConstantStoryPointExtractor
: Returns a constant story point value (defaults to 1).FunctionStoryPointExtractor
: Wraps a callable to compute story points from a task.AttributePathStoryPointExtractor
: Reads story points via a dotted attribute path and converts to float with default fallback.- Vendor implementations below:
AzureStoryPointExtractor
,JiraCustomFieldStoryPointExtractor
,JiraTShirtStoryPointExtractor
.
- Module:
sd_metrics_lib.sources.worklog
WorklogExtractor
(abstract): Returns mappinguser -> Duration
for a task.TaskTotalSpentTimeExtractor
(abstract): Returns totalDuration
spent on a task.ChainedWorklogExtractor
: Tries extractors in order and returns the first non-empty result.FunctionWorklogExtractor
: Wraps a callable to produce per-user time dict; values must beDuration
instances; invalid values are ignored.FunctionTotalSpentTimeExtractor
: Wraps a callable returning aDuration
; invalid values fall back toDuration.zero()
.AttributePathWorklogExtractor
: Reads a mapping at a dotted attribute path; values must beDuration
instances; invalid values are ignored.AttributePathTotalSpentTimeExtractor
: Reads a value at a dotted attribute path; returns it if it's aDuration
, otherwise returns a defaultDuration
(configurable).
- Module:
sd_metrics_lib.sources.abstract_worklog
AbstractStatusChangeWorklogExtractor
(abstract): Derives work time from assignment/status change history; attributes time to assignee and respects optional user filters andWorkTimeExtractor
.
- Module:
sd_metrics_lib.sources.jira.tasks
JiraTaskProvider
: Fetch tasks byJQL
viaatlassian-python-api
; supports paging and optionalThreadPoolExecutor
.
- Module:
sd_metrics_lib.sources.jira.query
JiraSearchQueryBuilder
: Builder forJQL
(project, status, date range, type, team, custom raw filters, order by)
- Module:
sd_metrics_lib.sources.jira.story_points
JiraCustomFieldStoryPointExtractor
: Reads a numeric custom field; supports default value.JiraTShirtStoryPointExtractor
: Maps T-shirt sizes (e.g.,S
/M
/L
) to numbers from a custom field.
- Module:
sd_metrics_lib.sources.jira.worklog
JiraWorklogExtractor
: Aggregates time from native Jira worklogs (optionally includes subtasks); optional user filter.JiraStatusChangeWorklogExtractor
: Derives time from changelog (status/assignee changes); supports username vsaccountId
and status names vs codes; uses aWorkTimeExtractor
.JiraResolutionTimeTaskTotalSpentTimeExtractor
: Total time fromcreated
toresolutiondate
.
- Module:
sd_metrics_lib.sources.azure.tasks
AzureTaskProvider
: ExecutesWIQL
; fetches work items in pages (sync orThreadPoolExecutor
); can expand updates for status-change-based calculations.
- Module:
sd_metrics_lib.sources.azure.query
AzureSearchQueryBuilder
: Builder for WIQL (project, status, date range, type, area path/team, custom raw filters, order by)
- Module:
sd_metrics_lib.sources.azure.story_points
AzureStoryPointExtractor
: Reads story points from a field (defaultMicrosoft.VSTS.Scheduling.StoryPoints
); robust parsing with default.
- Module:
sd_metrics_lib.sources.azure.worklog
AzureStatusChangeWorklogExtractor
: Derives per-user time from work item updates (assignment/state changes); supports status filters; usesWorkTimeExtractor
.AzureTaskTotalSpentTimeExtractor
: Total time fromSystem.CreatedDate
toMicrosoft.VSTS.Common.ClosedDate
.
- Module:
sd_metrics_lib.utils.enums
HealthStatus
(Enum): valuesGREEN
,YELLOW
,ORANGE
,RED
,GRAY
SeniorityLevel
(Enum): valuesJUNIOR
,MIDDLE
,SENIOR
- Module:
sd_metrics_lib.utils.storypoints
TShirtMapping
: Helper to convert between T-shirt sizes (XS
/S
/M
/L
/XL
) and story points using default mappingxs=1
,s=5
,m=8
,l=13
,xl=21
.
- Module:
sd_metrics_lib.utils.time
- Constants:
SECONDS_IN_HOUR
,WORKING_HOURS_PER_DAY
,WORKING_DAYS_PER_WEEK
,WORKING_WEEKS_IN_MONTH
,WEEKDAY_FRIDAY
- Classes:
TimeUnit
,TimePolicy
(with presetsTimePolicy.ALL_HOURS
,TimePolicy.BUSINESS_HOURS
),Duration
- Key methods: zero(), of(), datetime_difference(), to_seconds(), convert(), is_zero(), add()/sub() and operators +/-, sum(iterable), scalar * and /.
- Prefer
TimePolicy.convert()
orDuration.convert()
over manual seconds math.
- Constants:
- Module:
sd_metrics_lib.utils.worktime
WorkTimeExtractor
(abstract)SimpleWorkTimeExtractor
: Computes working Duration between two datetimes with business-day heuristics.BoundarySimpleWorkTimeExtractor
: LikeSimpleWorkTimeExtractor
but clamps to [start, end] boundaries.
- Module:
sd_metrics_lib.utils.cache
CacheProtocol
(Protocol),DictProtocol
(Protocol)DictToCacheProtocolAdapter
: Adapts a dict-like toCacheProtocol
.CacheKeyBuilder
: Helpers to build cache keys for data/meta entries.SupersetResolver
: Finds a superset fieldset for cached data reuse.
- Module:
sd_metrics_lib.utils.generators
TimeRangeGenerator
: Iterator producing date ranges for the requestedTimeUnit
(supports HOUR, DAY, WEEK, MONTH)
Use the physical modules directly (no export shims):
- Calculators:
from sd_metrics_lib.calculators.velocity import UserVelocityCalculator, GeneralizedTeamVelocityCalculator
- Common utilities:
from sd_metrics_lib.utils.enums import HealthStatus, SeniorityLevel
from sd_metrics_lib.utils.storypoints import TShirtMapping
from sd_metrics_lib.utils.time import SECONDS_IN_HOUR, WORKING_HOURS_PER_DAY, WORKING_DAYS_PER_WEEK, WORKING_WEEKS_IN_MONTH, WEEKDAY_FRIDAY, TimeUnit, TimePolicy, Duration
from sd_metrics_lib.utils.worktime import WorkTimeExtractor, SimpleWorkTimeExtractor, BoundarySimpleWorkTimeExtractor
from sd_metrics_lib.utils.generators import TimeRangeGenerator
from sd_metrics_lib.utils.cache import CacheKeyBuilder, CacheProtocol, DictToCacheProtocolAdapter, SupersetResolver, DictProtocol
- Sources (providers):
from sd_metrics_lib.sources.tasks import TaskProvider, ProxyTaskProvider, CachingTaskProvider
from sd_metrics_lib.sources.story_points import StoryPointExtractor, ConstantStoryPointExtractor, FunctionStoryPointExtractor, AttributePathStoryPointExtractor
from sd_metrics_lib.sources.worklog import WorklogExtractor, ChainedWorklogExtractor, TaskTotalSpentTimeExtractor, FunctionWorklogExtractor, FunctionTotalSpentTimeExtractor, AttributePathWorklogExtractor, AttributePathTotalSpentTimeExtractor
- Jira:
from sd_metrics_lib.sources.jira.query import JiraSearchQueryBuilder
from sd_metrics_lib.sources.jira.tasks import JiraTaskProvider
from sd_metrics_lib.sources.jira.story_points import JiraCustomFieldStoryPointExtractor, JiraTShirtStoryPointExtractor
from sd_metrics_lib.sources.jira.worklog import JiraWorklogExtractor, JiraStatusChangeWorklogExtractor, JiraResolutionTimeTaskTotalSpentTimeExtractor
- Azure:
from sd_metrics_lib.sources.azure.query import AzureSearchQueryBuilder
from sd_metrics_lib.sources.azure.tasks import AzureTaskProvider
from sd_metrics_lib.sources.azure.story_points import AzureStoryPointExtractor
from sd_metrics_lib.sources.azure.worklog import AzureStatusChangeWorklogExtractor, AzureTaskTotalSpentTimeExtractor
Install core library:
pip install sd-metrics-lib
Optional extras for providers:
pip install sd-metrics-lib[jira]
pip install sd-metrics-lib[azure]
- Most-used class:
UserVelocityCalculator
. - Minimal flow: TaskProvider + StoryPointExtractor + WorklogExtractor ->
calculate()
. - Time model:
Duration
+TimeUnit
+TimePolicy
(convert usingDuration.convert(...)
). - Typical defaults: business-hours policy and per-day velocities.
Smallest working sketch:
from sd_metrics_lib.calculators.velocity import UserVelocityCalculator
from sd_metrics_lib.sources.tasks import ProxyTaskProvider
from sd_metrics_lib.sources.story_points import ConstantStoryPointExtractor
from sd_metrics_lib.sources.worklog import FunctionWorklogExtractor
from sd_metrics_lib.utils.time import Duration, TimeUnit
# Pretend we have two tasks and attribute all work to one user
tasks = [{"id": 1}, {"id": 2}]
provider = ProxyTaskProvider(tasks)
sp = ConstantStoryPointExtractor(1)
wl = FunctionWorklogExtractor(lambda t: {"user1": Duration.of(1, TimeUnit.DAY)})
calc = UserVelocityCalculator(provider, sp, wl)
print(calc.calculate(TimeUnit.DAY)) # {"user1": ~2.0 / day}
- Task: A Jira issue or Azure DevOps work item fetched by a TaskProvider.
- Story points: Numeric size measure, extracted by a StoryPointExtractor from a field or via function.
- Worklog (derived): Time per user inferred either from native logs (Jira) or from status/assignee changes.
- Duration: A typed quantity with unit (SECOND/HOUR/DAY/WEEK/MONTH). Convert via
Duration.convert()
. - TimeUnit/TimePolicy: Choose units and business vs civil time assumptions.
- TaskProvider.get_tasks() -> list
- StoryPointExtractor.get_story_points(task) -> float | None
- WorklogExtractor.get_work_time_per_user(task) -> Dict[str, Duration]
- TaskTotalSpentTimeExtractor.get_total_spent_time(task) -> Duration
- Duration:
of()
,zero()
,convert()
,to_seconds()
, arithmetic add/sub/sum.
This code should work on any project and give at least some data for analysis.
from atlassian import Jira
from sd_metrics_lib.calculators.velocity import UserVelocityCalculator
from sd_metrics_lib.sources.jira.story_points import JiraCustomFieldStoryPointExtractor
from sd_metrics_lib.sources.jira.tasks import JiraTaskProvider
from sd_metrics_lib.sources.jira.worklog import JiraStatusChangeWorklogExtractor
from sd_metrics_lib.utils.time import TimeUnit
JIRA_SERVER = 'server_url'
JIRA_LOGIN = 'login'
JIRA_PASS = 'password'
jira_client = Jira(JIRA_SERVER, JIRA_LOGIN, JIRA_PASS, cloud=True)
jql = " project in ('TBC') AND resolutiondate >= 2022-08-01 "
task_provider = JiraTaskProvider(jira_client, jql, additional_fields=['changelog'])
story_point_extractor = JiraCustomFieldStoryPointExtractor('customfield_10010', default_story_points_value=1)
jira_worklog_extractor = JiraStatusChangeWorklogExtractor(['In Progress', 'In Development'])
velocity_calculator = UserVelocityCalculator(task_provider=task_provider,
story_point_extractor=story_point_extractor,
worklog_extractor=jira_worklog_extractor)
velocity = velocity_calculator.calculate(velocity_time_unit=TimeUnit.DAY)
print(velocity)
This example uses Azure DevOps WIQL to fetch closed items and derives time spent per user from status/assignment changes. It also demonstrates enabling concurrency with a thread pool and caching results with a TTL cache.
# from cachetools import TTLCache # optional, for caching examples
from concurrent.futures import ThreadPoolExecutor
from azure.devops.connection import Connection
from msrest.authentication import BasicAuthentication
from sd_metrics_lib.calculators.velocity import UserVelocityCalculator
from sd_metrics_lib.sources.azure.story_points import AzureStoryPointExtractor
from sd_metrics_lib.sources.azure.tasks import AzureTaskProvider
from sd_metrics_lib.sources.azure.worklog import AzureStatusChangeWorklogExtractor
from sd_metrics_lib.utils.time import TimeUnit
# Optional thread pool for faster fetching
jira_fetch_executor = ThreadPoolExecutor(max_workers=20, thread_name_prefix="azure-fetch")
ORGANIZATION_URL = 'https://dev.azure.com/your_org'
PERSONAL_ACCESS_TOKEN = 'your_pat'
credentials = BasicAuthentication('', PERSONAL_ACCESS_TOKEN)
connection = Connection(base_url=ORGANIZATION_URL, creds=credentials)
wit_client = connection.clients.get_work_item_tracking_client()
wiql = """
SELECT [System.Id]
FROM workitems
WHERE
[System.TeamProject] = 'YourProject'
AND [System.State] IN ('Closed', 'Done', 'Resolved')
AND [System.WorkItemType] IN ('User Story', 'Bug')
AND [Microsoft.VSTS.Common.ClosedDate] >= '2025-08-01'
ORDER BY [System.ChangedDate] DESC \
"""
# Use thread pool
task_provider = AzureTaskProvider(wit_client, query=wiql, thread_pool_executor=jira_fetch_executor)
story_point_extractor = AzureStoryPointExtractor(default_story_points_value=1)
worklog_extractor = AzureStatusChangeWorklogExtractor(transition_statuses=['In Progress'])
velocity_calculator = UserVelocityCalculator(task_provider=task_provider,
story_point_extractor=story_point_extractor,
worklog_extractor=worklog_extractor)
velocity = velocity_calculator.calculate(velocity_time_unit=TimeUnit.DAY)
print(velocity)
For each task with positive story points:
- Extract per-user working time via WorklogExtractor.
- Compute total task time across users; skip if zero.
- Split the task’s story points among users proportionally to each user’s share of time.
- Sum per-user story points and per-user time across tasks.
- Convert total time to the requested TimeUnit/TimePolicy.
- Return story_points / time_in_unit per user (omit zero velocities).
- Jira
- JiraTaskProvider: JQL, paging, optional ThreadPoolExecutor, can fetch all fields for subtasks.
- JiraWorklogExtractor: native worklogs; filter by users; can include subtasks.
- JiraStatusChangeWorklogExtractor: derives work time from changelog; supports names vs accountId; status names vs codes.
- JiraCustomFieldStoryPointExtractor: numeric custom field by name.
- JiraTShirtStoryPointExtractor: T-shirt sizes -> numeric mapping.
- JiraResolutionTimeTaskTotalSpentTimeExtractor: duration from created to resolutiondate.
- Azure DevOps
- AzureTaskProvider: WIQL, stable pagination of get_work_items; custom expand fields (updates, child tasks); optional ThreadPoolExecutor.
- AzureStatusChangeWorklogExtractor: derives time from updates; supports author/assignee resolution; user-filter; status filters.
- AzureTaskTotalSpentTimeExtractor: creation -> closed duration.
- AzureStoryPointExtractor: reads story points from a configurable field.
- Team velocity from resolution time (Jira):
from atlassian import Jira
from sd_metrics_lib.calculators.velocity import GeneralizedTeamVelocityCalculator
from sd_metrics_lib.sources.jira.story_points import JiraCustomFieldStoryPointExtractor
from sd_metrics_lib.sources.jira.tasks import JiraTaskProvider
from sd_metrics_lib.sources.jira.worklog import JiraResolutionTimeTaskTotalSpentTimeExtractor
from sd_metrics_lib.utils.time import TimeUnit
# Fetch resolved tasks only; no changelog needed
jira = Jira('https://your_jira', 'login', 'password', cloud=True)
jql = "project = 'TBC' AND resolutiondate >= 2025-01-01"
provider = JiraTaskProvider(jira, jql)
sp = JiraCustomFieldStoryPointExtractor('customfield_10010', default_story_points_value=1)
spent = JiraResolutionTimeTaskTotalSpentTimeExtractor()
team = GeneralizedTeamVelocityCalculator(provider, sp, spent)
print(team.calculate(TimeUnit.DAY))
- Custom story points from nested attribute path:
from sd_metrics_lib.sources.story_points import AttributePathStoryPointExtractor
sp = AttributePathStoryPointExtractor('my_model.points', default=1.0)
- Custom worklog by callable returning Durations:
from sd_metrics_lib.sources.worklog import FunctionWorklogExtractor
from sd_metrics_lib.utils.time import Duration, TimeUnit
def my_worklog(task):
return {'u1': Duration.of(3, TimeUnit.HOUR)}
wl = FunctionWorklogExtractor(my_worklog)
- I get zeros or empty results.
- Ensure your extractor returns Duration objects (not ints). Zero totals are skipped.
- Ensure tasks actually have story points > 0.
- For status-change extractors, include changelog/updates in additional/custom fields.
- Time conversion seems wrong.
- Pass TimePolicy explicitly if you need business vs civil time:
calculate(time_policy=TimePolicy.BUSINESS_HOURS)
. - Use
Duration.convert(TimeUnit.SECOND/DAY/...)
to check values step-by-step.
- Pass TimePolicy explicitly if you need business vs civil time:
- Jira user identifiers mismatch.
- Use JiraStatusChangeWorklogExtractor(use_user_name=True) to attribute by display name instead of accountId.
- Azure date parsing fails.
- Extractors handle formats with/without milliseconds. If a custom format is needed, pass time_format.
- Cache misses unexpectedly.
- CachingTaskProvider keys include query and additional_fields. Field order doesn’t matter; ensure consistent field sets.
- Add a TaskProvider: implement get_tasks() that returns a list of your task objects.
- Add a StoryPointExtractor: implement get_story_points(task) -> float | None.
- Add a WorklogExtractor: implement get_work_time_per_user(task) -> Dict[str, Duration]. Return Duration objects only.
- Add a TaskTotalSpentTimeExtractor: implement get_total_spent_time(task) -> Duration.
- Python: 3.9+
- Optional extras: [jira], [azure]
- Do not embed tokens in code; prefer environment variables or secret managers.
- (Breaking) JiraTaskProvider API migrated to the latest Jira API using enhanced_jql with new pagination.
- (Feature) Add support of custom TimePolicy in SimpleWorkTimeExtractor
- (Bug Fix) Fix wrong time assignment in Azure DevOps for status and user change in same worklog
- (Feature) Support internal caching of get worklog requests inside AzureTaskProvider
- (Feature) Support math and comparison operations of Duration with float/int.
- (Feature) Add assignee history support in constructors of Azure and Jira query builders.
- (Refactor) Replace default SimpleWorkTimeExtractor() with SIMPLE_WORKTIME_EXTRACTOR constant in worklog extractors.
- (Breaking) Removed VelocityTimeUnit. TimeUnit should be used instead.
- (Breaking) Adopt Duration/TimeUnit/TimePolicy across the public API. Calculators and worklog extractors now use Duration. Velocity calculators accept utils.time.TimeUnit (not utils.enums.VelocityTimeUnit).
- (Feature) Add assignee history filters to Azure and Jira query builders.
- (Bug Fix) Quote project keys/names in Jira project IN (...) filter.
- (Refactor) Migrate internals to Duration math with TimePolicy.
- (Docs) Refresh README examples and API overview for the new time model.
- (Feature) Add time conversion to seconds and customizable time units.
- (Feature) Add SECOND to VelocityTimeUnit.
- (Feature) Add GRAY (unknown/indeterminate) to HealthStatus.
- (Feature) Add assignee filter to Azure and Jira query builders.
- (Bug Fix) Azure: use IN (...) for team filter; support multiple teams.
- (Feature) Add ideal_working_hours_per_day option to utils.time.convert_time for non-standard working days.
- (Improvement) When use_user_name=True, prefer displayName, then uniqueName, then id in AzureStatusChangeWorklogExtractor.
- (Bug Fix) Fetch custom expand fields for child tasks in AzureTaskProvider.
- (Bug Fix) Prefer StateChangeDate/ChangedDate for change time in AzureStatusChangeWorklogExtractor.
- (Bug Fix) Use revised_date as change timestamp; accept datetime objects; handle times without milliseconds in AzureStatusChangeWorklogExtractor.
- (Feature) Infer assignee from status-change author when last assigned is unknown in status-change worklog extractors.
- (Bug Fix) Support resolving by user name or user id in AzureStatusChangeWorklogExtractor.
- (Bug Fix) Use revisedDate as change timestamp (not CreatedDate) in AzureStatusChangeWorklogExtractor.
- (Bug Fix) Handle a single entry that changes assignee and status at once in abstract status-change worklog.
- (Feature) Add child tasks via custom expand field 'CustomExpand.ChildTasks' in AzureTaskProvider.
- (Feature) Fetch all fields for subtasks when 'subtasks' is requested in JiraTaskProvider.
- (Bug Fix) Skip filters for empty iterables in JiraSearchQueryBuilder (avoid broken JQL).
- (Bug Fix) Use user id instead of uniqueName for proper log extraction in AzureStatusChangeWorklogExtractor.
- (Improvement) Better type support for FunctionExtractors
- (Bug Fix) Fix bad import in utils module
-
(Breaking) Restructure packages and rename files for better import Developer Experience.
-
(Feature) Add proxy-style classes for extractors
-
(Bug Fix) Fix task id adding in query builders
-
(Bug Fix) Fix custom expand field in AzureTaskProvider
-
(Breaking) Fix circular module import issue
-
(Feature) Add filtering by task ids in Azure and Jira query builders
-
(Breaking) Rename all Issue* terms to Task* across API (IssueProvider -> TaskProvider, IssueTotalSpentTimeExtractor -> TaskTotalSpentTimeExtractor, etc.). Removed backward-compatibility aliases.
-
(Breaking) Change package and method names in JiraSearchQueryBuilder
-
(Feature) Introduce AzureSearchQueryBuilder
-
(Feature) Make changelog history optional via additional fields in AzureTaskProvider
-
(Feature) Extend JiraSearchQueryBuilder with custom raw filters; filter by Team; open-ended resolution date
-
(Feature) Rewrite CachingTaskProvider to support Django caches
-
(Feature) Introduce AzureSearchQueryBuilder
-
(Bug Fix) Azure: fetch all tasks beyond 20k limit using stable pagination
-
(Bug Fix) Jira: do not fail on empty search results
- (Feature) Add integration with Azure DevOps
- (Breaking) Add a generic CachingIssueProvider to wrap any IssueProvider and remove CachingJiraIssueProvider
- (Improvement) Add possibility to adjust init time
- (Bug Fix) Fix bug with wrong cache data fetching
- (Bug Fix) Fix bug in week time period end date resolving
- (Feature) Added BoundarySimpleWorkTimeExtractor
- (Improvement) Filter unneeded changelog items for better performance
- (Improvement) Add T-Shirt to story points mapping util class
- (Improvement) Add helper enums
- (Bug Fix) Fix bug with story points returned instead of spent time
- (Bug Fix) Fix bug with missing time for active status
- (Bug Fix) Fix bug with passing class instances in extractor
- (Improvement) Add multithreading support for JiraIssueProvider.
- (Feature) Add CachingJiraIssueProvider.
- (Improvement) Add story points getter for GeneralizedTeamVelocityCalculator.
- (Improvement) Execute data fetching in Jira velocity calculators only once.
- (Improvement) Add story points getter for Jira velocity calculators.
- (Feature) Add team velocity calculator.
- (Improvement) Add JQL filter for last modified data.
- (Bug Fix) Fix wrong user resolving in JiraStatusChangeWorklogExtractor.
- (Bug Fix) Fix resolving more time than spent period of time.
- (Bug Fix) Fix Jira filter query joins without AND.
- (Improvement) Add JiraIssueSearchQueryBuilder util class.
- (Improvement) Add TimeRangeGenerator util class.
- (Bug Fix) Fix filtering by status when no status list passed.
- (Bug Fix) Fix package import exception after installing from pypi.
- (Feature) Add user velocity calculator.