| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- import { useParams } from 'common'
- import dayjs from 'dayjs'
- import { RefreshCw, StopCircle } from 'lucide-react'
- import { useEffect, useState } from 'react'
- import { toast } from 'sonner'
- import {
- Button,
- cn,
- Sheet,
- SheetContent,
- SheetDescription,
- SheetHeader,
- SheetSection,
- SheetTitle,
- Tooltip,
- TooltipContent,
- TooltipTrigger,
- } from 'ui'
- import { CodeBlock } from 'ui-patterns/CodeBlock'
- import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
- import AlertError from '@/components/ui/AlertError'
- import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
- import { useQueryAbortMutation } from '@/data/sql/abort-query-mutation'
- import { useOngoingQueriesQuery } from '@/data/sql/ongoing-queries-query'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- import { useUrlState } from '@/hooks/ui/useUrlState'
- import { IS_PLATFORM } from '@/lib/constants'
- import { useAppStateSnapshot } from '@/state/app-state'
- import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
- import type { ResponseError } from '@/types'
- export const OngoingQueriesPanel = () => {
- const [_, setParams] = useUrlState({ replace: true })
- const { viewOngoingQueries } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const state = useDatabaseSelectorStateSnapshot()
- const appState = useAppStateSnapshot()
- const [selectedId, setSelectedId] = useState<number>()
- const { data: databases } = useReadReplicasQuery({ projectRef: project?.ref })
- const database = (databases ?? []).find((db) => db.identifier === state.selectedDatabaseId)
- const {
- data,
- error,
- isError,
- isPending: isLoadingOngoingQueries,
- isFetching: isFetchingOngoingQueries,
- refetch,
- } = useOngoingQueriesQuery(
- {
- projectRef: project?.ref,
- connectionString: database?.connectionString,
- },
- {
- enabled: !IS_PLATFORM || (IS_PLATFORM && database?.connectionString !== undefined),
- staleTime: 5000,
- }
- )
- const queries = data ?? []
- useEffect(() => {
- if (viewOngoingQueries) {
- appState.setOnGoingQueriesPanelOpen(true)
- setParams({ viewOngoingQueries: undefined })
- }
- }, [viewOngoingQueries])
- const { mutate: abortQuery, isPending } = useQueryAbortMutation({
- onSuccess: () => {
- toast.success(`Successfully aborted query (ID: ${selectedId})`)
- setSelectedId(undefined)
- },
- })
- const closePanel = () => {
- setParams({ viewOngoingQueries: undefined })
- appState.setOnGoingQueriesPanelOpen(false)
- }
- return (
- <>
- <Sheet open={appState.ongoingQueriesPanelOpen} onOpenChange={() => closePanel()}>
- <SheetContent size="lg">
- <SheetHeader>
- <SheetTitle className="flex items-center gap-x-2">
- Running queries on{' '}
- {database?.identifier === project?.ref ? 'primary database' : 'read replica'}
- <Button
- type="default"
- className="px-1.5"
- loading={isLoadingOngoingQueries || isFetchingOngoingQueries}
- icon={<RefreshCw />}
- onClick={() => refetch()}
- />
- </SheetTitle>
- <SheetDescription>
- There {queries.length === 1 ? 'is' : 'are'}{' '}
- <span className="text-foreground-light">{queries.length}</span> quer
- {queries.length === 1 ? 'y' : 'ies'} currently running{' '}
- {database?.identifier !== project?.ref ? `on replica ${database?.identifier}` : ''}
- </SheetDescription>
- </SheetHeader>
- <div className="max-h-full h-full divide-y overflow-y-auto">
- {isError && (
- <div className="flex items-center justify-center h-full px-16">
- <AlertError
- subject="Failed to retrieve ongoing queries"
- error={error as ResponseError}
- />
- </div>
- )}
- {queries.length === 0 && (
- <div className="flex flex-col gap-y-2 items-center justify-center h-full text-foreground-light text-sm">
- <span>
- No queries are currently running on the{' '}
- {database?.identifier !== project?.ref
- ? `read replica ${database?.identifier}`
- : (databases ?? []).length > 1
- ? 'primary database'
- : 'database'}
- </span>
- <Button
- type="default"
- loading={isLoadingOngoingQueries || isFetchingOngoingQueries}
- icon={<RefreshCw />}
- onClick={() => refetch()}
- >
- Refresh
- </Button>
- </div>
- )}
- {queries.map((query) => (
- <SheetSection key={query.pid} className="flex justify-between gap-x-4">
- <div className="flex flex-col gap-y-2 w-full">
- <CodeBlock
- hideLineNumbers
- value={query.query}
- language="sql"
- className={cn(
- 'max-w-none max-h-52 w-full',
- 'bg-transparent! py-3! px-3.5! prose dark:prose-dark',
- '[&>code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap'
- )}
- />
- <div className="flex items-center gap-x-2">
- <p className="text-foreground-light text-xs">PID: {query.pid}</p>
- <p className="text-foreground-light text-xs">•</p>
- <p className="text-foreground-light text-xs">
- Started since: {dayjs(query.query_start).format('DD MMM YYYY HH:mm (ZZ)')}
- </p>
- </div>
- </div>
- <Tooltip>
- <TooltipTrigger asChild>
- <Button
- type="warning"
- className="px-1.5"
- icon={<StopCircle />}
- onClick={() => setSelectedId(query.pid)}
- />
- </TooltipTrigger>
- <TooltipContent side="bottom">Abort query</TooltipContent>
- </Tooltip>
- </SheetSection>
- ))}
- </div>
- </SheetContent>
- </Sheet>
- <ConfirmationModal
- loading={isPending}
- variant="warning"
- title={`Confirm to abort this query? (ID: ${selectedId})`}
- visible={selectedId !== undefined}
- onCancel={() => setSelectedId(undefined)}
- onConfirm={() => {
- if (selectedId !== undefined)
- abortQuery({
- pid: selectedId,
- projectRef: project?.ref,
- connectionString: database?.connectionString,
- })
- }}
- >
- <p className="text-sm">This will force the query to stop running.</p>
- </ConfirmationModal>
- </>
- )
- }
|