fix: pre-landing review fixes — company_id filter and CW condition injection
- Apply company_id filter in CW search_tickets conditions (was silently ignored) - Sanitize query string to strip single quotes before CW condition interpolation - Add psaError state to TicketsPage for permissions error surfacing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,9 @@ class ConnectWiseProvider(PSAProvider):
|
|||||||
|
|
||||||
conditions: list[str] = []
|
conditions: list[str] = []
|
||||||
if query:
|
if query:
|
||||||
conditions.append(f"summary contains '{query}'")
|
# Sanitize: strip single quotes to prevent CW condition injection
|
||||||
|
safe_query = query.replace("'", "")
|
||||||
|
conditions.append(f"summary contains '{safe_query}'")
|
||||||
if filters.get("board_id"):
|
if filters.get("board_id"):
|
||||||
conditions.append(f"board/id = {filters['board_id']}")
|
conditions.append(f"board/id = {filters['board_id']}")
|
||||||
if filters.get("status_id"):
|
if filters.get("status_id"):
|
||||||
@@ -89,6 +91,8 @@ class ConnectWiseProvider(PSAProvider):
|
|||||||
if board_ids:
|
if board_ids:
|
||||||
board_list = ", ".join(str(bid) for bid in board_ids)
|
board_list = ", ".join(str(bid) for bid in board_ids)
|
||||||
conditions.append(f"board/id in ({board_list})")
|
conditions.append(f"board/id in ({board_list})")
|
||||||
|
if filters.get("company_id"):
|
||||||
|
conditions.append(f"company/id = {int(filters['company_id'])}")
|
||||||
|
|
||||||
condition_str = " and ".join(conditions) if conditions else ""
|
condition_str = " and ".join(conditions) if conditions else ""
|
||||||
if condition_str:
|
if condition_str:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useState, useCallback } from 'react'
|
import { useEffect, useState, useCallback } from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { Plus, Ticket } from 'lucide-react'
|
import { Plus, Ticket, AlertTriangle } from 'lucide-react'
|
||||||
|
import axios from 'axios'
|
||||||
import { TicketFilterBar } from '@/components/tickets/TicketFilterBar'
|
import { TicketFilterBar } from '@/components/tickets/TicketFilterBar'
|
||||||
import { TicketListRow } from '@/components/tickets/TicketListRow'
|
import { TicketListRow } from '@/components/tickets/TicketListRow'
|
||||||
import { TicketDetailPanel } from '@/components/tickets/TicketDetailPanel'
|
import { TicketDetailPanel } from '@/components/tickets/TicketDetailPanel'
|
||||||
@@ -37,6 +38,7 @@ export default function TicketsPage() {
|
|||||||
const [tickets, setTickets] = useState<PSATicketSearchResult[]>([])
|
const [tickets, setTickets] = useState<PSATicketSearchResult[]>([])
|
||||||
const [total, setTotal] = useState(0)
|
const [total, setTotal] = useState(0)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [psaError, setPsaError] = useState<string | null>(null)
|
||||||
const [boards, setBoards] = useState<PSABoard[]>([])
|
const [boards, setBoards] = useState<PSABoard[]>([])
|
||||||
const [statuses, setStatuses] = useState<PSATicketStatusItem[]>([])
|
const [statuses, setStatuses] = useState<PSATicketStatusItem[]>([])
|
||||||
const [priorities, setPriorities] = useState<PSAPriority[]>([])
|
const [priorities, setPriorities] = useState<PSAPriority[]>([])
|
||||||
@@ -66,6 +68,7 @@ export default function TicketsPage() {
|
|||||||
// Fetch tickets on filter/page change
|
// Fetch tickets on filter/page change
|
||||||
const fetchTickets = useCallback(async () => {
|
const fetchTickets = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
setPsaError(null)
|
||||||
try {
|
try {
|
||||||
const result = await ticketsApi.searchTickets({
|
const result = await ticketsApi.searchTickets({
|
||||||
query: filters.search || undefined,
|
query: filters.search || undefined,
|
||||||
@@ -90,9 +93,20 @@ export default function TicketsPage() {
|
|||||||
})
|
})
|
||||||
return seen.size > 0 ? Array.from(seen, ([id, name]) => ({ id, name })) : prev
|
return seen.size > 0 ? Array.from(seen, ([id, name]) => ({ id, name })) : prev
|
||||||
})
|
})
|
||||||
} catch {
|
} catch (err: unknown) {
|
||||||
setTickets([])
|
setTickets([])
|
||||||
setTotal(0)
|
setTotal(0)
|
||||||
|
if (axios.isAxiosError(err)) {
|
||||||
|
const status = err.response?.status
|
||||||
|
const detail = (err.response?.data as { detail?: string })?.detail ?? ''
|
||||||
|
if (status === 502 && detail.toLowerCase().includes('permission')) {
|
||||||
|
setPsaError('ConnectWise returned a permissions error. Check that the API member\'s security role has Service Tickets → Inquire → ALL and System → Table Setup → Inquire → ALL.')
|
||||||
|
} else if (status === 502) {
|
||||||
|
setPsaError('ConnectWise is unavailable or returned an error. Check your integration settings.')
|
||||||
|
} else {
|
||||||
|
setPsaError('Failed to load tickets.')
|
||||||
|
}
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -165,7 +179,13 @@ export default function TicketsPage() {
|
|||||||
Loading tickets…
|
Loading tickets…
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loading && tickets.length === 0 && (
|
{!loading && psaError && (
|
||||||
|
<div className="mx-6 mt-6 flex items-start gap-3 px-4 py-3 rounded-lg bg-danger-dim border border-danger/30 text-sm text-danger">
|
||||||
|
<AlertTriangle className="w-4 h-4 mt-0.5 shrink-0" />
|
||||||
|
<span>{psaError}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!loading && !psaError && tickets.length === 0 && (
|
||||||
<div className="flex flex-col items-center justify-center py-16 text-muted-foreground text-sm gap-2">
|
<div className="flex flex-col items-center justify-center py-16 text-muted-foreground text-sm gap-2">
|
||||||
<Ticket className="w-8 h-8 opacity-30" />
|
<Ticket className="w-8 h-8 opacity-30" />
|
||||||
No tickets match your filters
|
No tickets match your filters
|
||||||
|
|||||||
Reference in New Issue
Block a user