TableIndexAdvisorContext.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // @ts-nocheck
  2. import { useQueryClient } from '@tanstack/react-query'
  3. import { createContext, PropsWithChildren, useCallback, useContext, useState } from 'react'
  4. import { Sheet, SheetContent, SheetHeader, SheetTitle } from 'ui'
  5. import { useIndexAdvisorStatus } from '@/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus'
  6. import { QueryIndexes } from '@/components/interfaces/QueryPerformance/QueryIndexes'
  7. import { databaseKeys } from '@/data/database/keys'
  8. import {
  9. cleanIndexColumnName,
  10. IndexAdvisorSuggestion,
  11. TableIndexAdvisorData,
  12. useTableIndexAdvisorQuery,
  13. } from '@/data/database/table-index-advisor-query'
  14. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  15. interface TableIndexAdvisorContextValue {
  16. isLoading: boolean
  17. isAvailable: boolean
  18. isEnabled: boolean
  19. columnsWithSuggestions: string[]
  20. suggestions: TableIndexAdvisorData['suggestions']
  21. openSheet: (columnName: string) => void
  22. getSuggestionsForColumn: (columnName: string) => IndexAdvisorSuggestion[]
  23. invalidate: () => Promise<void>
  24. }
  25. const TableIndexAdvisorContext = createContext<TableIndexAdvisorContextValue>({
  26. isLoading: false,
  27. isAvailable: false,
  28. isEnabled: false,
  29. columnsWithSuggestions: [],
  30. suggestions: [],
  31. openSheet: () => {},
  32. getSuggestionsForColumn: () => [],
  33. invalidate: async () => {},
  34. })
  35. interface TableIndexAdvisorProviderProps {
  36. schema: string
  37. table: string
  38. }
  39. export function TableIndexAdvisorProvider({
  40. children,
  41. schema,
  42. table,
  43. }: PropsWithChildren<TableIndexAdvisorProviderProps>) {
  44. const { data: project } = useSelectedProjectQuery()
  45. const { isIndexAdvisorAvailable, isIndexAdvisorEnabled } = useIndexAdvisorStatus()
  46. const queryClient = useQueryClient()
  47. const [isSheetOpen, setIsSheetOpen] = useState(false)
  48. const [selectedColumn, setSelectedColumn] = useState<string | undefined>(undefined)
  49. const { data, isLoading } = useTableIndexAdvisorQuery(
  50. {
  51. projectRef: project?.ref,
  52. connectionString: project?.connectionString,
  53. schema,
  54. table,
  55. },
  56. {
  57. enabled: isIndexAdvisorEnabled && !!schema && !!table,
  58. }
  59. )
  60. const openSheet = useCallback((columnName: string) => {
  61. setSelectedColumn(columnName)
  62. setIsSheetOpen(true)
  63. }, [])
  64. const closeSheet = useCallback(() => {
  65. setIsSheetOpen(false)
  66. setSelectedColumn(undefined)
  67. }, [])
  68. const getSuggestionsForColumn = useCallback(
  69. (columnName: string): IndexAdvisorSuggestion[] => {
  70. if (!data?.suggestions) return []
  71. // Filter suggestions that include this column in their index statements
  72. return data.suggestions.filter((suggestion) =>
  73. suggestion.index_statements.some((stmt) => {
  74. const match = stmt.match(/USING\s+\w+\s*\(([^)]+)\)/i)
  75. if (match) {
  76. const columns = match[1].split(',').map((c) => cleanIndexColumnName(c))
  77. return columns.includes(columnName)
  78. }
  79. return false
  80. })
  81. )
  82. },
  83. [data?.suggestions]
  84. )
  85. const invalidate = useCallback(async () => {
  86. if (project?.ref && schema && table) {
  87. await queryClient.invalidateQueries({
  88. queryKey: databaseKeys.tableIndexAdvisor(project.ref, schema, table),
  89. })
  90. }
  91. }, [queryClient, project?.ref, schema, table])
  92. // Get the first suggestion for the selected column to pass to QueryIndexes
  93. const selectedSuggestion = selectedColumn ? getSuggestionsForColumn(selectedColumn)[0] : null
  94. const prefetchedIndexAdvisorResult = selectedSuggestion
  95. ? {
  96. errors: [],
  97. index_statements: selectedSuggestion.index_statements,
  98. startup_cost_before: selectedSuggestion.startup_cost_before,
  99. startup_cost_after: selectedSuggestion.startup_cost_after,
  100. total_cost_before: selectedSuggestion.total_cost_before,
  101. total_cost_after: selectedSuggestion.total_cost_after,
  102. }
  103. : null
  104. const value: TableIndexAdvisorContextValue = {
  105. isLoading,
  106. isAvailable: isIndexAdvisorAvailable,
  107. isEnabled: isIndexAdvisorEnabled,
  108. columnsWithSuggestions: data?.columnsWithSuggestions ?? [],
  109. suggestions: data?.suggestions ?? [],
  110. openSheet,
  111. getSuggestionsForColumn,
  112. invalidate,
  113. }
  114. return (
  115. <TableIndexAdvisorContext.Provider value={value}>
  116. {children}
  117. <Sheet open={isSheetOpen} onOpenChange={(open) => !open && closeSheet()}>
  118. <SheetContent className="flex flex-col gap-0 p-0 lg:w-[calc(100vw-802px)]! max-w-[700px]">
  119. <SheetHeader className="border-b px-5 py-3">
  120. <SheetTitle>Index Recommendation</SheetTitle>
  121. </SheetHeader>
  122. {selectedSuggestion && (
  123. <QueryIndexes
  124. selectedRow={{ query: selectedSuggestion.query }}
  125. columnName={selectedColumn}
  126. suggestedSelectQuery={selectedSuggestion.query}
  127. prefetchedIndexAdvisorResult={prefetchedIndexAdvisorResult}
  128. onClose={closeSheet}
  129. />
  130. )}
  131. </SheetContent>
  132. </Sheet>
  133. </TableIndexAdvisorContext.Provider>
  134. )
  135. }
  136. export function useTableIndexAdvisor() {
  137. return useContext(TableIndexAdvisorContext)
  138. }
  139. export function useColumnHasIndexSuggestion(columnName: string): boolean {
  140. const { columnsWithSuggestions } = useTableIndexAdvisor()
  141. return columnsWithSuggestions.includes(columnName)
  142. }