Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions frontend/src/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ import {
ToolOutlined,
SaveOutlined,
} from '@ant-design/icons'
import { ConfigProvider, MenuProps } from 'antd'
import { ConfigProvider, MenuProps, notification } from 'antd'
import { Layout, Menu } from 'antd'
import { fetchJson } from './utils/api'
import QueryEditorPage from './pages/QueryEditor/QueryEditorPage'
import AIToolsPage from './pages/AITools/AIToolsPage'

Expand Down Expand Up @@ -66,9 +67,13 @@ export default function AppLayout(): JSX.Element {
const [hostname, setHostname] = useState('')

const fetchHostname = async () => {
const response = await fetch(`/api/analyze/hostname`)
const responseJson = await response.json()
setHostname(responseJson.hostname)
try {
const responseJson = await fetchJson(`/api/analyze/hostname`)
setHostname(responseJson.hostname)
} catch (err) {
// A transient HTML response (gateway/auth/error page) shouldn't blow up the whole app
notification.error({ message: 'Failed to load hostname' })
}
}

useEffect(() => {
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/pages/AITools/AIToolsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ import React, { useEffect, useState } from 'react'
import { Tabs } from 'antd'
import { useHistory } from 'react-router-dom'
import NaturalLanguageQueryEditor from './NaturalLanguageQueryEditor'
import { fetchJson } from '../../utils/api'

export default function AIToolsPage() {
const history = useHistory()
const [error, setError] = useState<string | null>(null)

const loadData = async () => {
const res = await fetch('/api/analyze/ai_tools_available')
const resJson = await res.json()
if ('error' in resJson) {
setError(resJson['error'])
try {
const resJson = await fetchJson('/api/analyze/ai_tools_available')
if ('error' in resJson) {
setError(resJson['error'])
}
} catch (err) {
setError('Failed to load AI tools')
}
}

Expand Down
50 changes: 29 additions & 21 deletions frontend/src/pages/AITools/NaturalLanguageQueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'
import { Select, Checkbox, Button, Table, ConfigProvider } from 'antd'
import { Select, Checkbox, Button, Table, ConfigProvider, notification } from 'antd'
import TextArea from 'antd/es/input/TextArea'
// @ts-ignore
import { highlight, languages } from 'prismjs/components/prism-core' // @ts-ignore
Expand All @@ -8,6 +8,7 @@ import 'prismjs/themes/prism.css'
import Editor from 'react-simple-code-editor'
// @ts-ignore
import { format } from 'sql-formatter-plus'
import { fetchJson } from '../../utils/api'

export interface TableData {
table: string
Expand All @@ -29,31 +30,38 @@ export default function NaturalLanguageQueryEditor() {
const runQuery = async () => {
setLoading(true)
setSql(null)
const res = await fetch('/api/analyze/natural_language_query', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: query,
tables_to_query: tablesToQuery,
readonly: readonly || false,
}),
})
const resJson = await res.json()
if (resJson.error) {
setError(resJson.error)
try {
const resJson = await fetchJson('/api/analyze/natural_language_query', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: query,
tables_to_query: tablesToQuery,
readonly: readonly || false,
}),
})
if (resJson.error) {
setError(resJson.error)
setData([])
} else {
setData(resJson.result)
}
setSql(resJson.sql)
} catch (err) {
setError(String(err))
setData([])
} else {
setData(resJson.result)
}
setSql(resJson.sql)
setLoading(false)
}
const loadTableData = async () => {
const res = await fetch('/api/analyze/tables')
const resJson = await res.json()
setTables(resJson)
try {
const resJson = await fetchJson('/api/analyze/tables')
setTables(resJson)
} catch (err) {
notification.error({ message: 'Failed to load tables' })
}
}

useEffect(() => {
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/pages/Backups/Backups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
import { usePollingEffect } from '../../utils/usePollingEffect'
import { ColumnType } from 'antd/es/table'
import { Table, Button, Form, Input, Checkbox, Modal, Tag, Col, Progress, Row, Tooltip, notification } from 'antd'
import { fetchJson } from '../../utils/api'
import useSWR, { mutate } from 'swr'

interface BackupRow {
Expand Down Expand Up @@ -45,7 +46,7 @@ export default function Backups() {
// Validate and get form values
const values = await form.validateFields()
setConfirmLoading(true)
const res = await fetch(`/api/backups`, {
const resJson = await fetchJson(`/api/backups`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -55,7 +56,7 @@ export default function Backups() {
setOpen(false)
setConfirmLoading(false)
mutate('/api/backups')
return await res.json()
return resJson
} catch (error) {
notification.error({
message: 'Creating backup failed',
Expand All @@ -73,8 +74,7 @@ export default function Backups() {

const loadData = async (url: string) => {
try {
const res = await fetch(url)
const resJson = await res.json()
const resJson = await fetchJson(url)
const backups = { backups: resJson }
return backups
} catch (err) {
Expand Down
15 changes: 7 additions & 8 deletions frontend/src/pages/Backups/ScheduledBackups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import DeleteOutlined from '@ant-design/icons/DeleteOutlined'
import EditOutlined from '@ant-design/icons/EditOutlined'
import { Clusters } from '../Clusters/Clusters'
import { fetchJson } from '../../utils/api'
import useSWR, { mutate } from 'swr'

interface ScheduleRow {
Expand Down Expand Up @@ -69,7 +70,7 @@ export default function ScheduledBackups() {
const url = editingRow ? `/api/scheduled_backups/${editingRow.id}` : '/api/scheduled_backups'
const values = await form.validateFields()
setConfirmLoading(true)
const res = await fetch(url, {
const resJson = await fetchJson(url, {
method: method,
headers: {
'Content-Type': 'application/json',
Expand All @@ -80,7 +81,7 @@ export default function ScheduledBackups() {
setConfirmLoading(false)
setEditingRow(null)
mutate('/api/scheduled_backups')
return await res.json()
return resJson
} catch (error) {
notification.error({
message: 'Creating backup failed',
Expand Down Expand Up @@ -111,8 +112,7 @@ export default function ScheduledBackups() {

const fetchBackups = async (url: string) => {
try {
const res = await fetch(url)
const resJson = await res.json()
const resJson = await fetchJson(url)
const backups = { backups: resJson.results }
return backups
} catch (err) {
Expand All @@ -121,8 +121,7 @@ export default function ScheduledBackups() {
}
const fetchClusters = async (url: string) => {
try {
const res = await fetch(url)
const resJson = await res.json()
const resJson = await fetchJson(url)
const clusters = { clusters: resJson }
return clusters
} catch (err) {
Expand All @@ -146,15 +145,15 @@ export default function ScheduledBackups() {
render: (_, sched) => {
const toggleEnabled = async () => {
try {
const res = await fetch(`/api/scheduled_backups/${sched.id}`, {
const resJson = await fetchJson(`/api/scheduled_backups/${sched.id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ enabled: !sched.enabled }),
})
mutate('/api/scheduled_backups')
return await res.json()
return resJson
} catch (error) {
notification.error({
message: 'Failed to toggle backup',
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/Clusters/Clusters.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'
import { ColumnType } from 'antd/es/table'
import { Table, Col, Row, Tooltip, notification } from 'antd'
import { fetchJson } from '../../utils/api'
import useSWR from 'swr'

interface ClusterNode {
Expand Down Expand Up @@ -31,8 +32,7 @@ export interface Clusters {
export default function Clusters() {
const loadData = async (url: string) => {
try {
const res = await fetch(url)
const resJson = await res.json()
const resJson = await fetchJson(url)
const clusters = { clusters: resJson }
return clusters
} catch (err) {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/DiskUsage/DiskUsage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'
import { Pie } from '@ant-design/plots'
import { Card, Spin, Row, Col, notification } from 'antd'
import { fetchJson } from '../../utils/api'

import useSWR from 'swr'

Expand All @@ -13,8 +14,7 @@ interface NodeData {
export function DiskUsage(): JSX.Element {
const loadData = async (url: string) => {
try {
const res = await fetch(url)
const resJson = await res.json()
const resJson = await fetchJson(url)
return resJson
} catch {
notification.error({ message: 'Failed to load data' })
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/Errors/Errors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
import { Table, notification } from 'antd'
import { ColumnsType } from 'antd/es/table'
import { isoTimestampToHumanReadable } from '../../utils/dateUtils'
import { fetchJson } from '../../utils/api'

import useSWR from 'swr'

Expand Down Expand Up @@ -32,8 +33,7 @@ export default function CollapsibleTable() {

const loadData = async (url: string) => {
try {
const res = await fetch(url)
const resJson = await res.json()
const resJson = await fetchJson(url)

const slowQueriesData = resJson.map((error: ErrorData, idx: number) => ({ key: idx, ...error }))
return slowQueriesData
Expand Down
57 changes: 32 additions & 25 deletions frontend/src/pages/Logs/Logs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Table, Typography, Input, Card, ConfigProvider, Empty } from 'antd'
import { Table, Typography, Input, Card, ConfigProvider, Empty, notification } from 'antd'
import React, { useEffect, useState } from 'react'
import { Column } from '@ant-design/charts'
import { fetchJson } from '../../utils/api'

const { Paragraph } = Typography

Expand Down Expand Up @@ -42,36 +43,42 @@ export default function Logs() {

const fetchLogs = async (messageIlike = '') => {
setLoadingLogs(true)
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({ message_ilike: messageIlike }),
headers: {
'Content-Type': 'application/json',
},
})
const resJson = await res.json()
if (resJson.error) {
setError(resJson.error)
} else {
setLogs(resJson)
try {
const resJson = await fetchJson(url, {
method: 'POST',
body: JSON.stringify({ message_ilike: messageIlike }),
headers: {
'Content-Type': 'application/json',
},
})
if (resJson.error) {
setError(resJson.error)
} else {
setLogs(resJson)
}
} catch (err) {
notification.error({ message: 'Failed to load logs' })
}
setLoadingLogs(false)
}

const fetchLogsFrequency = async (messageIlike = '') => {
setLoadingLogsFrequency(true)
const res = await fetch('/api/analyze/logs_frequency', {
method: 'POST',
body: JSON.stringify({ message_ilike: messageIlike }),
headers: {
'Content-Type': 'application/json',
},
})
const resJson = await res.json()
if (resJson.error) {
setError(resJson.error)
} else {
setLogsFrequency(resJson)
try {
const resJson = await fetchJson('/api/analyze/logs_frequency', {
method: 'POST',
body: JSON.stringify({ message_ilike: messageIlike }),
headers: {
'Content-Type': 'application/json',
},
})
if (resJson.error) {
setError(resJson.error)
} else {
setLogsFrequency(resJson)
}
} catch (err) {
notification.error({ message: 'Failed to load logs' })
}
setLoadingLogsFrequency(false)
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/Operations/Operations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Button, Input, Progress, Table, Tabs, notification } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import { ColumnType } from 'antd/es/table'
import { isoTimestampToHumanReadable } from '../../utils/dateUtils'
import { fetchJson } from '../../utils/api'

import useSWR from 'swr'

Expand Down Expand Up @@ -77,8 +78,7 @@ export function OperationControls({

export function OperationsList(): JSX.Element {
const fetchAndUpdateOperationsIfNeeded = async (url: string) => {
const response = await fetch(url)
const responseJson = await response.json()
const responseJson = await fetchJson(url)
const results = responseJson.results
if (JSON.stringify(results) !== JSON.stringify(operations)) {
return results
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/Overview/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Line } from '@ant-design/charts'
import { Card, Col, Row, Tooltip, notification } from 'antd'
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined'
import { clickhouseTips } from './tips'
import { fetchJson } from '../../utils/api'
import useSWR from 'swr'

interface MetricData {
Expand All @@ -20,8 +21,7 @@ interface QueryGraphsData {
export default function Overview() {
const loadData = async (url: string) => {
try {
const res = await fetch(url)
const resJson = await res.json()
const resJson = await fetchJson(url)
const execution_count = resJson.execution_count
const memory_usage = resJson.memory_usage
const read_bytes = resJson.read_bytes
Expand Down
Loading
Loading