diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.constants.ts b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.constants.ts index 35d69f4e347f6..6dc2007951dfb 100644 --- a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.constants.ts +++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.constants.ts @@ -12,31 +12,31 @@ export const QUERY_PERFORMANCE_PRESET_MAP = { export const QUERY_PERFORMANCE_REPORTS = { [QUERY_PERFORMANCE_REPORT_TYPES.MOST_TIME_CONSUMING]: [ - { id: 'query', name: 'Query', description: undefined, minWidth: 600 }, - { id: 'rolname', name: 'Role', description: undefined, minWidth: undefined }, - { id: 'calls', name: 'Calls', description: undefined, minWidth: 60 }, + { id: 'query', name: 'Query', description: undefined, minWidth: 500 }, + { id: 'calls', name: 'Calls', description: undefined, minWidth: 100 }, { id: 'total_time', name: 'Total time', description: 'latency', minWidth: 150 }, - { id: 'prop_total_time', name: 'Time consumed', description: undefined, minWidth: 110 }, - { id: 'mean_time', name: 'Mean time', description: undefined, minWidth: 80 }, + { id: 'prop_total_time', name: 'Time consumed', description: undefined, minWidth: 150 }, + { id: 'mean_time', name: 'Mean time', description: undefined, minWidth: 150 }, + { id: 'rolname', name: 'Role', description: undefined, minWidth: 120 }, ], [QUERY_PERFORMANCE_REPORT_TYPES.MOST_FREQUENT]: [ - { id: 'query', name: 'Query', description: undefined, minWidth: 600 }, - { id: 'rolname', name: 'Role', description: undefined, minWidth: undefined }, - { id: 'avg_rows', name: 'Avg. Rows', description: undefined, minWidth: undefined }, - { id: 'calls', name: 'Calls', description: undefined, minWidth: undefined }, - { id: 'max_time', name: 'Max time', description: undefined, minWidth: undefined }, - { id: 'mean_time', name: 'Mean time', description: undefined, minWidth: undefined }, - { id: 'min_time', name: 'Min time', description: undefined, minWidth: undefined }, - { id: 'total_time', name: 'Total time', description: 'latency', minWidth: 180 }, + { id: 'query', name: 'Query', description: undefined, minWidth: 500 }, + { id: 'avg_rows', name: 'Avg. Rows', description: undefined, minWidth: 100 }, + { id: 'calls', name: 'Calls', description: undefined, minWidth: 100 }, + { id: 'total_time', name: 'Total time', description: 'latency', minWidth: 150 }, + { id: 'max_time', name: 'Max time', description: undefined, minWidth: 150 }, + { id: 'mean_time', name: 'Mean time', description: undefined, minWidth: 150 }, + { id: 'min_time', name: 'Min time', description: undefined, minWidth: 150 }, + { id: 'rolname', name: 'Role', description: undefined, minWidth: 120 }, ], [QUERY_PERFORMANCE_REPORT_TYPES.SLOWEST_EXECUTION]: [ - { id: 'query', name: 'Query', description: undefined, minWidth: 600 }, - { id: 'rolname', name: 'Role', description: undefined, minWidth: undefined }, - { id: 'avg_rows', name: 'Avg. Rows', description: undefined, minWidth: undefined }, - { id: 'calls', name: 'Calls', description: undefined, minWidth: undefined }, - { id: 'max_time', name: 'Max time', description: undefined, minWidth: undefined }, - { id: 'mean_time', name: 'Mean time', description: undefined, minWidth: undefined }, - { id: 'min_time', name: 'Min time', description: undefined, minWidth: undefined }, - { id: 'total_time', name: 'Total time', description: 'latency', minWidth: 180 }, + { id: 'query', name: 'Query', description: undefined, minWidth: 500 }, + { id: 'avg_rows', name: 'Avg. Rows', description: undefined, minWidth: 100 }, + { id: 'calls', name: 'Calls', description: undefined, minWidth: 100 }, + { id: 'total_time', name: 'Total time', description: 'latency', minWidth: 150 }, + { id: 'max_time', name: 'Max time', description: undefined, minWidth: 150 }, + { id: 'mean_time', name: 'Mean time', description: undefined, minWidth: 150 }, + { id: 'min_time', name: 'Min time', description: undefined, minWidth: 150 }, + { id: 'rolname', name: 'Role', description: undefined, minWidth: 120 }, ], } as const diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx index 1be556d2a3db9..0775f723264d5 100644 --- a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx +++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx @@ -10,8 +10,9 @@ import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query' import { DbQueryHook } from 'hooks/analytics/useDbQuery' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { Button } from 'ui' +import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' +import { useQueryPerformanceSort } from './hooks/useQueryPerformanceSort' export const QueryPerformanceFilterBar = ({ queryPerformanceQuery, @@ -22,6 +23,7 @@ export const QueryPerformanceFilterBar = ({ }) => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() + const { sort, clearSort } = useQueryPerformanceSort() const [showBottomSection] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.QUERY_PERF_SHOW_BOTTOM_SECTION, true @@ -63,7 +65,7 @@ export const QueryPerformanceFilterBar = ({ }, [searchValue]) return ( -
+
+ + {sort && ( +
+

+ Sort: {sort.column} {sort.order} +

+ + + + + Clear sort + +
+ )}
-
+
{!showBottomSection && onResetReportClick && (
) @@ -80,7 +114,7 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance const value = props.row?.[col.id] if (col.id === 'query') { return ( -
+
{hasIndexRecommendations(props.row.index_advisor_result, true) && ( )} -
) @@ -137,8 +148,8 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance if (col.id === 'prop_total_time') { return ( -
-

{value || 'n/a'}

+
+

{value ? `${value.toFixed(1)}%` : 'n/a'}

) } @@ -150,17 +161,20 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance ? `${value.toFixed(0)}ms` : value.toLocaleString() : '' + + if (col.id === 'total_time') { + return ( +
+ {isTime && typeof value === 'number' && !isNaN(value) && isFinite(value) && ( +

{(value / 1000).toFixed(2) + 's' || 'n/a'}

+ )} +
+ ) + } + return ( -
+

{formattedValue}

- {isTime && typeof value === 'number' && !isNaN(value) && isFinite(value) && ( -

{(value / 1000).toFixed(2)}s

- )}
) }, @@ -168,7 +182,28 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance return result }) - const reportData = useMemo(() => data ?? [], [data]) + const reportData = useMemo(() => { + const rawData = data ?? [] + + if (sort?.column === 'prop_total_time') { + const sortedData = [...rawData].sort((a, b) => { + const getNumericValue = (value: number | string) => { + if (!value || value === 'n/a') return 0 + if (typeof value === 'number') return value + return parseFloat(value.toString().replace('%', '')) || 0 + } + + const aValue = getNumericValue(a.prop_total_time) + const bValue = getNumericValue(b.prop_total_time) + + return sort.order === 'asc' ? aValue - bValue : bValue - aValue + }) + + return sortedData + } + + return rawData + }, [data, sort]) const selectedQuery = selectedRow !== undefined ? reportData[selectedRow]?.query : undefined const query = (selectedQuery ?? '').trim().toLowerCase() const showIndexSuggestions = @@ -177,32 +212,6 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance query.startsWith('with pgrst_payload')) && hasIndexRecommendations(reportData[selectedRow!]?.index_advisor_result, true) - const onSortChange = (column: string) => { - let updatedSort = undefined - - if (sort?.column === column) { - if (sort.order === 'desc') { - updatedSort = { column, order: 'asc' } - } else { - updatedSort = undefined - } - } else { - updatedSort = { column, order: 'desc' } - } - - setSort(updatedSort as QueryPerformanceSort) - - if (updatedSort === undefined) { - const { sort, order, ...otherParams } = router.query - router.push({ ...router, query: otherParams }) - } else { - router.push({ - ...router, - query: { ...router.query, sort: updatedSort.column, order: updatedSort.order }, - }) - } - } - useEffect(() => { setSelectedRow(undefined) }, [preset, search, roles, urlSort, order]) @@ -264,7 +273,7 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance return [ `${isSelected ? 'bg-surface-300 dark:bg-surface-300' : 'bg-200'} cursor-pointer`, `${isSelected ? '[&>div:first-child]:border-l-4 border-l-secondary [&>div]:border-l-foreground' : ''}`, - '[&>.rdg-cell]:border-box [&>.rdg-cell]:outline-none [&>.rdg-cell]:shadow-none', + '[&>.rdg-cell]:box-border [&>.rdg-cell]:outline-none [&>.rdg-cell]:shadow-none', '[&>.rdg-cell:first-child>div]:ml-4', ].join(' ') }} diff --git a/apps/studio/components/interfaces/QueryPerformance/hooks/useQueryPerformanceSort.ts b/apps/studio/components/interfaces/QueryPerformance/hooks/useQueryPerformanceSort.ts new file mode 100644 index 0000000000000..5cc9740a778e0 --- /dev/null +++ b/apps/studio/components/interfaces/QueryPerformance/hooks/useQueryPerformanceSort.ts @@ -0,0 +1,28 @@ +import { useQueryStates, parseAsString } from 'nuqs' +import { QueryPerformanceSort } from '../../Reports/Reports.queries' + +export const useQueryPerformanceSort = () => { + const [{ sort, order }, setQueryStates] = useQueryStates({ + sort: parseAsString, + order: parseAsString, + }) + + const setSortConfig = (column: string, order: 'asc' | 'desc') => { + setQueryStates({ sort: column, order }) + } + + const clearSort = () => { + setQueryStates({ sort: null, order: null }) + } + + const sortConfig: QueryPerformanceSort | null = + sort && order && ['asc', 'desc'].includes(order) + ? { column: sort as QueryPerformanceSort['column'], order: order as 'asc' | 'desc' } + : null + + return { + sort: sortConfig, + setSortConfig, + clearSort, + } +} diff --git a/apps/studio/components/interfaces/Reports/Reports.constants.ts b/apps/studio/components/interfaces/Reports/Reports.constants.ts index 96b3b326d3b2d..af8866eae5e4e 100644 --- a/apps/studio/components/interfaces/Reports/Reports.constants.ts +++ b/apps/studio/components/interfaces/Reports/Reports.constants.ts @@ -407,7 +407,7 @@ select statements.calls, statements.total_exec_time + statements.total_plan_time as total_time, statements.mean_exec_time + statements.mean_plan_time as mean_time, - to_char(((statements.total_exec_time + statements.total_plan_time)/sum(statements.total_exec_time + statements.total_plan_time) OVER()) * 100, 'FM90D0') || '%' AS prop_total_time${ + ((statements.total_exec_time + statements.total_plan_time)/sum(statements.total_exec_time + statements.total_plan_time) OVER()) * 100 as prop_total_time${ runIndexAdvisor ? `, case diff --git a/apps/studio/components/interfaces/Reports/Reports.queries.ts b/apps/studio/components/interfaces/Reports/Reports.queries.ts index 4a62bd2eb36ca..8de1c7bde575b 100644 --- a/apps/studio/components/interfaces/Reports/Reports.queries.ts +++ b/apps/studio/components/interfaces/Reports/Reports.queries.ts @@ -4,6 +4,8 @@ import { Presets } from './Reports.types' export type QueryPerformanceSort = { column: + | 'query' + | 'rolname' | 'total_time' | 'prop_total_time' | 'calls' diff --git a/apps/studio/next-env.d.ts b/apps/studio/next-env.d.ts index 52e831b434248..254b73c165d90 100644 --- a/apps/studio/next-env.d.ts +++ b/apps/studio/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/apps/studio/pages/project/[ref]/advisors/query-performance.tsx b/apps/studio/pages/project/[ref]/advisors/query-performance.tsx index 44c8c93667929..f35dc61ed3776 100644 --- a/apps/studio/pages/project/[ref]/advisors/query-performance.tsx +++ b/apps/studio/pages/project/[ref]/advisors/query-performance.tsx @@ -1,19 +1,16 @@ -import { useRouter } from 'next/router' -import { parseAsString, useQueryStates } from 'nuqs' +import { parseAsArrayOf, parseAsString, useQueryStates } from 'nuqs' import { useParams } from 'common' import { EnableIndexAdvisorButton } from 'components/interfaces/QueryPerformance/EnableIndexAdvisorButton' import { useIndexAdvisorStatus } from 'components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus' +import { useQueryPerformanceSort } from 'components/interfaces/QueryPerformance/hooks/useQueryPerformanceSort' import { QueryPerformance } from 'components/interfaces/QueryPerformance/QueryPerformance' import { QUERY_PERFORMANCE_PRESET_MAP, QUERY_PERFORMANCE_REPORT_TYPES, } from 'components/interfaces/QueryPerformance/QueryPerformance.constants' import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' -import { - QueryPerformanceSort, - useQueryPerformanceQuery, -} from 'components/interfaces/Reports/Reports.queries' +import { useQueryPerformanceQuery } from 'components/interfaces/Reports/Reports.queries' import { Presets } from 'components/interfaces/Reports/Reports.types' import { queriesFactory } from 'components/interfaces/Reports/Reports.utils' import AdvisorsLayout from 'components/layouts/AdvisorsLayout/AdvisorsLayout' @@ -24,15 +21,16 @@ import { FormHeader } from 'components/ui/Forms/FormHeader' import type { NextPageWithLayout } from 'types' const QueryPerformanceReport: NextPageWithLayout = () => { - const router = useRouter() const { ref } = useParams() const { isIndexAdvisorEnabled } = useIndexAdvisorStatus() + const { sort: sortConfig } = useQueryPerformanceSort() - const [{ preset: urlPreset, search: searchQuery, order, sort }] = useQueryStates({ + const [{ preset: urlPreset, search: searchQuery, roles }] = useQueryStates({ sort: parseAsString, - search: parseAsString.withDefault(''), order: parseAsString, + search: parseAsString.withDefault(''), preset: parseAsString.withDefault(QUERY_PERFORMANCE_REPORT_TYPES.MOST_TIME_CONSUMING), + roles: parseAsArrayOf(parseAsString).withDefault([]), }) const config = PRESET_CONFIG[Presets.QUERY_PERFORMANCE] @@ -40,14 +38,12 @@ const QueryPerformanceReport: NextPageWithLayout = () => { const queryHitRate = hooks.queryHitRate() const preset = QUERY_PERFORMANCE_PRESET_MAP[urlPreset as QUERY_PERFORMANCE_REPORT_TYPES] - const orderBy = !!sort ? ({ column: sort, order } as QueryPerformanceSort) : undefined - const roles = router?.query?.roles ?? [] const queryPerformanceQuery = useQueryPerformanceQuery({ searchQuery, - orderBy, + orderBy: sortConfig || undefined, preset, - roles: typeof roles === 'string' ? [roles] : roles, + roles, runIndexAdvisor: isIndexAdvisorEnabled, }) diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx index 40029b7d137a1..94d27bf058280 100644 --- a/apps/www/components/Hero/Hero.tsx +++ b/apps/www/components/Hero/Hero.tsx @@ -1,9 +1,8 @@ import Link from 'next/link' -import { Badge, Button } from 'ui' +import { Button } from 'ui' import SectionContainer from '~/components/Layouts/SectionContainer' import { useSendTelemetryEvent } from '~/lib/telemetry' -import AnnouncementBadge from '../Announcement/Badge' const Hero = () => { const sendTelemetryEvent = useSendTelemetryEvent() @@ -15,12 +14,6 @@ const Hero = () => {
- -

Build in a weekend diff --git a/packages/ui/src/components/CodeBlock/CodeBlock.tsx b/packages/ui/src/components/CodeBlock/CodeBlock.tsx index fd59454f9df1c..56322df9d6a5a 100644 --- a/packages/ui/src/components/CodeBlock/CodeBlock.tsx +++ b/packages/ui/src/components/CodeBlock/CodeBlock.tsx @@ -26,6 +26,7 @@ import { default as python, } from 'react-syntax-highlighter/dist/cjs/languages/hljs/python' import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql' +import pgsql from 'react-syntax-highlighter/dist/cjs/languages/hljs/pgsql' import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript' export type CodeBlockLang = @@ -44,6 +45,7 @@ export type CodeBlockLang = | 'php' | 'python' | 'go' + | 'pgsql' export interface CodeBlockProps { title?: ReactNode language?: CodeBlockLang @@ -146,6 +148,7 @@ export const CodeBlock = ({ SyntaxHighlighter.registerLanguage('php', php) SyntaxHighlighter.registerLanguage('python', python) SyntaxHighlighter.registerLanguage('go', go) + SyntaxHighlighter.registerLanguage('pgsql', pgsql) const large = false // don't show line numbers if bash == lang