feat: implement lazy imports for token refresh in Confluence and Jira connectors

- Refactored token refresh logic in ConfluenceHistoryConnector and JiraHistoryConnector to use lazy imports, avoiding circular dependencies.
- Enhanced the ComposerAction component to manage tool availability based on connected types, adding support for Jira and Confluence tools.
- Updated tool icon management to include Jira and Confluence, improving the user interface for tool interactions.
This commit is contained in:
Anish Sarkar 2026-03-21 12:41:06 +05:30
parent e71eae26fc
commit 79bc123439
5 changed files with 77 additions and 6 deletions

View file

@ -14,7 +14,6 @@ from sqlalchemy.future import select
from app.config import config
from app.connectors.confluence_connector import ConfluenceConnector
from app.db import SearchSourceConnector
from app.routes.confluence_add_connector_route import refresh_confluence_token
from app.schemas.atlassian_auth_credentials import AtlassianAuthCredentialsBase
from app.utils.oauth_security import TokenEncryption
@ -190,7 +189,9 @@ class ConfluenceHistoryConnector:
f"Connector {self._connector_id} not found; cannot refresh token."
)
# Refresh token
# Lazy import to avoid circular dependency
from app.routes.confluence_add_connector_route import refresh_confluence_token
connector = await refresh_confluence_token(self._session, connector)
# Reload credentials after refresh

View file

@ -14,7 +14,6 @@ from sqlalchemy.future import select
from app.config import config
from app.connectors.jira_connector import JiraConnector
from app.db import SearchSourceConnector
from app.routes.jira_add_connector_route import refresh_jira_token
from app.schemas.atlassian_auth_credentials import AtlassianAuthCredentialsBase
from app.utils.oauth_security import TokenEncryption
@ -184,7 +183,9 @@ class JiraHistoryConnector:
f"Connector {self._connector_id} not found; cannot refresh token."
)
# Refresh token
# Lazy import to avoid circular dependency
from app.routes.jira_add_connector_route import refresh_jira_token
connector = await refresh_jira_token(self._session, connector)
# Reload credentials after refresh

View file

@ -880,6 +880,12 @@ async def _stream_agent_events(
"create_calendar_event",
"update_calendar_event",
"delete_calendar_event",
"create_jira_issue",
"update_jira_issue",
"delete_jira_issue",
"create_confluence_page",
"update_confluence_page",
"delete_confluence_page",
):
yield streaming_service.format_tool_output_available(
tool_call_id,

View file

@ -90,7 +90,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { Switch } from "@/components/ui/switch";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { CONNECTOR_TOOL_ICON_PATHS, getToolIcon } from "@/contracts/enums/toolIcons";
import { CONNECTOR_ICON_TO_TYPES, CONNECTOR_TOOL_ICON_PATHS, getToolIcon } from "@/contracts/enums/toolIcons";
import type { Document } from "@/contracts/types/document.types";
import { useBatchCommentsPreload } from "@/hooks/use-comments";
import { useCommentsElectric } from "@/hooks/use-comments-electric";
@ -603,6 +603,12 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
const setDisabledTools = useSetAtom(disabledToolsAtom);
const hydrateDisabled = useSetAtom(hydrateDisabledToolsAtom);
const { data: connectors } = useAtomValue(connectorsAtom);
const connectedTypes = useMemo(
() => new Set<string>((connectors ?? []).map((c) => c.connector_type)),
[connectors]
);
const toggleToolGroup = useCallback(
(toolNames: string[]) => {
const allDisabled = toolNames.every((name) => disabledTools.includes(name));
@ -628,6 +634,15 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
const placed = new Set<string>();
for (const group of TOOL_GROUPS) {
if (group.connectorIcon) {
const requiredTypes = CONNECTOR_ICON_TO_TYPES[group.connectorIcon];
const isConnected = requiredTypes?.some((t) => connectedTypes.has(t));
if (!isConnected) {
for (const name of group.tools) placed.add(name);
continue;
}
}
const matched = group.tools.flatMap((name) => {
const tool = toolsByName.get(name);
if (!tool) return [];
@ -645,7 +660,7 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
}
return result;
}, [filteredTools]);
}, [filteredTools, connectedTypes]);
const { visibleTotal, visibleEnabled } = useMemo(() => {
let total = 0;
@ -669,6 +684,30 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
hydrateDisabled();
}, [hydrateDisabled]);
useEffect(() => {
const unavailable: string[] = [];
for (const group of TOOL_GROUPS) {
if (!group.connectorIcon) continue;
const requiredTypes = CONNECTOR_ICON_TO_TYPES[group.connectorIcon];
const isConnected = requiredTypes?.some((t) => connectedTypes.has(t));
if (!isConnected) {
unavailable.push(...group.tools);
}
}
if (unavailable.length === 0) return;
setDisabledTools((prev) => {
const next = new Set(prev);
let changed = false;
for (const name of unavailable) {
if (!next.has(name)) {
next.add(name);
changed = true;
}
}
return changed ? [...next] : prev;
});
}, [connectedTypes, setDisabledTools]);
const hasModelConfigured = useMemo(() => {
if (!preferences) return false;
const agentLlmId = preferences.agent_llm_id;
@ -1092,6 +1131,18 @@ const TOOL_GROUPS: ToolGroup[] = [
connectorIcon: "linear",
tooltip: "Create, update, and delete issues in Linear.",
},
{
label: "Jira",
tools: ["create_jira_issue", "update_jira_issue", "delete_jira_issue"],
connectorIcon: "jira",
tooltip: "Create, update, and delete issues in Jira.",
},
{
label: "Confluence",
tools: ["create_confluence_page", "update_confluence_page", "delete_confluence_page"],
connectorIcon: "confluence",
tooltip: "Create, update, and delete pages in Confluence.",
},
];
const MessageError: FC = () => {

View file

@ -37,4 +37,16 @@ export const CONNECTOR_TOOL_ICON_PATHS: Record<string, { src: string; alt: strin
google_drive: { src: "/connectors/google-drive.svg", alt: "Google Drive" },
notion: { src: "/connectors/notion.svg", alt: "Notion" },
linear: { src: "/connectors/linear.svg", alt: "Linear" },
jira: { src: "/connectors/jira.svg", alt: "Jira" },
confluence: { src: "/connectors/confluence.svg", alt: "Confluence" },
};
export const CONNECTOR_ICON_TO_TYPES: Record<string, string[]> = {
gmail: ["GOOGLE_GMAIL_CONNECTOR", "COMPOSIO_GMAIL_CONNECTOR"],
google_calendar: ["GOOGLE_CALENDAR_CONNECTOR", "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR"],
google_drive: ["GOOGLE_DRIVE_CONNECTOR", "COMPOSIO_GOOGLE_DRIVE_CONNECTOR"],
notion: ["NOTION_CONNECTOR"],
linear: ["LINEAR_CONNECTOR"],
jira: ["JIRA_CONNECTOR"],
confluence: ["CONFLUENCE_CONNECTOR"],
};