diff --git a/backend/onyx/connectors/jira/connector.py b/backend/onyx/connectors/jira/connector.py index 4c3638ee21..84688a053f 100644 --- a/backend/onyx/connectors/jira/connector.py +++ b/backend/onyx/connectors/jira/connector.py @@ -2,7 +2,7 @@ import copy import json import os from collections.abc import Callable, Generator, Iterable, Iterator -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta from typing import Any import requests @@ -714,19 +714,13 @@ class JiraConnector( def _get_jql_query( self, start: SecondsSinceUnixEpoch, end: SecondsSinceUnixEpoch ) -> str: - """Get the JQL query based on configuration and time range + """JQL for the configured project/query plus the poll window. - If a custom JQL query is provided, it will be used and combined with time constraints. - Otherwise, the query will be constructed based on project key (if provided). + Unquoted epoch-ms so Jira does not reinterpret naive datetimes in the + API user's profile timezone. + https://support.atlassian.com/jira-software-cloud/docs/jql-fields/#Updated """ - start_date_str = datetime.fromtimestamp(start, tz=timezone.utc).strftime( - "%Y-%m-%d %H:%M" - ) - end_date_str = datetime.fromtimestamp(end, tz=timezone.utc).strftime( - "%Y-%m-%d %H:%M" - ) - - time_jql = f"updated >= '{start_date_str}' AND updated <= '{end_date_str}'" + time_jql = f"updated >= {int(start * 1000)} AND updated <= {int(end * 1000)}" # If custom JQL query is provided, use it and combine with time constraints if self.jql_query: diff --git a/backend/tests/unit/onyx/connectors/jira/test_jira_checkpointing.py b/backend/tests/unit/onyx/connectors/jira/test_jira_checkpointing.py index 9964d896fd..d5a98d907b 100644 --- a/backend/tests/unit/onyx/connectors/jira/test_jira_checkpointing.py +++ b/backend/tests/unit/onyx/connectors/jira/test_jira_checkpointing.py @@ -141,22 +141,21 @@ def test_load_credentials(jira_connector: JiraConnector) -> None: def test_get_jql_query_with_project(jira_connector: JiraConnector) -> None: - """Test JQL query generation with project specified""" + """Poll windows are unquoted epoch-ms, not naive datetimes.""" start = datetime(2023, 1, 1, tzinfo=timezone.utc).timestamp() end = datetime(2023, 1, 2, tzinfo=timezone.utc).timestamp() query = jira_connector._get_jql_query(start, end) - # Check that the project part and time part are both in the query assert f'project = "{jira_connector.jira_project}"' in query - assert "updated >= '2023-01-01 00:00'" in query - assert "updated <= '2023-01-02 00:00'" in query + assert f"updated >= {int(start * 1000)}" in query + assert f"updated <= {int(end * 1000)}" in query + assert "2023-01-01 00:00" not in query assert " AND " in query def test_get_jql_query_without_project(jira_base_url: str) -> None: - """Test JQL query generation without project specified""" - # Create connector without project key + """Poll windows stay epoch-ms when no project key is set.""" connector = JiraConnector(jira_base_url=jira_base_url) start = datetime(2023, 1, 1, tzinfo=timezone.utc).timestamp() @@ -164,10 +163,10 @@ def test_get_jql_query_without_project(jira_base_url: str) -> None: query = connector._get_jql_query(start, end) - # Check that only time part is in the query assert "project =" not in query - assert "updated >= '2023-01-01 00:00'" in query - assert "updated <= '2023-01-02 00:00'" in query + assert f"updated >= {int(start * 1000)}" in query + assert f"updated <= {int(end * 1000)}" in query + assert "2023-01-01 00:00" not in query def test_load_from_checkpoint_happy_path(