mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Merge pull request #6627 from supabase/feat/logs-recent-queries
feat/logs: recent queries in log explorer are cached to local storage
This commit is contained in:
6 files changed
+159
-5
No files matched your search
@@ -13,10 +13,12 @@ const LogsNavigation = () => {
|
||||
|
||||
const indexRoute = 'query'
|
||||
const logsSavedQueries = useFlag('logsSavedQueries')
|
||||
const logsRecentQueries = useFlag('logsRecentQueries')
|
||||
|
||||
const tabs = [
|
||||
{ id: 'query', label: 'Query' },
|
||||
logsSavedQueries ? { id: 'saved', label: 'Saved' } : null,
|
||||
logsRecentQueries ? { id: 'recent', label: 'Recent' } : null,
|
||||
{ id: 'templates', label: 'Templates' },
|
||||
].filter(Boolean) as TabItem[]
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Button, IconPlay } from '@supabase/ui'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { useRouter } from 'next/router'
|
||||
import { LogSqlSnippets } from 'types'
|
||||
|
||||
interface Props {
|
||||
item: LogSqlSnippets.Content
|
||||
}
|
||||
|
||||
const RecentQueriesItem: React.FC<Props> = ({ item }) => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
return (
|
||||
<Table.tr key={item.sql}>
|
||||
<Table.td>
|
||||
<div
|
||||
className={ 'whitespace-nowrap bg-scale-100 border-l border-r !pt-0 !pb-0 text-scale-1200 transition-all expanded-row-content '}
|
||||
>
|
||||
<pre className="text-sm break-words py-4 px-3 ">{item.sql}</pre>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td className=" text-right">
|
||||
<Button
|
||||
type="alternative"
|
||||
iconRight={<IconPlay size={10} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/project/${ref}/logs-explorer?sql=${encodeURIComponent(item.sql)}`
|
||||
)
|
||||
}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecentQueriesItem
|
||||
@@ -86,6 +86,9 @@ export const LogsExplorerPage: NextPage = () => {
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, q: query },
|
||||
})
|
||||
content.addRecentLogSqlSnippet({
|
||||
sql: query
|
||||
})
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { NextPage } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Button, IconClock, IconSave, Loading } from '@supabase/ui'
|
||||
import { useStore, withAuth } from 'hooks'
|
||||
import RecentQueriesItem from 'components/interfaces/Settings/Logs/RecentQueriesItem'
|
||||
import LogsExplorerLayout from 'components/layouts/LogsExplorerLayout/LogsExplorerLayout'
|
||||
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { useRouter } from 'next/router'
|
||||
import { LogSqlSnippets } from 'types'
|
||||
|
||||
export const LogsSavedPage: NextPage = () => {
|
||||
const { content, ui } = useStore()
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
useEffect(()=>{
|
||||
content.loadPersistentData()
|
||||
}, [ui.selectedProjectRef])
|
||||
const recent = content.recentLogSqlSnippets.slice().reverse()
|
||||
|
||||
return (
|
||||
<LogsExplorerLayout>
|
||||
<div className="flex flex-col gap-3">
|
||||
{recent.length > 0 && (
|
||||
<>
|
||||
<div className="flex flex-row justify-end">
|
||||
<Button size="tiny" type="default" onClick={()=>{
|
||||
content.clearRecentLogSqlSnippets()
|
||||
}}>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
head={
|
||||
<>
|
||||
<Table.th>Snippets</Table.th>
|
||||
<Table.th className="w-24"></Table.th>
|
||||
</>
|
||||
}
|
||||
body={
|
||||
<>
|
||||
{recent.map((item: LogSqlSnippets.Content) => <RecentQueriesItem key={item.sql} item={item} />)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{recent.length === 0 && (
|
||||
<>
|
||||
<div className="items-center flex flex-col gap-1 my-auto justify-center h-full flex-grow">
|
||||
<IconClock className="animate-bounce" />
|
||||
<h3 className="text-lg text-scale-1200">No Recent Queries Yet</h3>
|
||||
<p className="text-sm text-scale-900">
|
||||
Your recent queries run from the{' '}
|
||||
<Link href={`/project/${ref}/logs-explorer`}>
|
||||
<span className="font-bold underline text-white cursor-pointer">Query</span>
|
||||
</Link>{' '}
|
||||
tab will show here.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</LogsExplorerLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(observer(LogsSavedPage))
|
||||
@@ -1,8 +1,8 @@
|
||||
import { makeAutoObservable, action } from 'mobx'
|
||||
import { makeAutoObservable } from 'mobx'
|
||||
import { keyBy } from 'lodash'
|
||||
|
||||
import { get, post, patch, delete_ } from 'lib/common/fetch'
|
||||
import { UserContent, UserContentMap } from 'types'
|
||||
import { LogSqlSnippets, UserContent, UserContentMap } from 'types'
|
||||
import { IRootStore } from '../RootStore'
|
||||
import { API_URL } from 'lib/constants'
|
||||
|
||||
@@ -15,16 +15,20 @@ export interface IProjectContentStore {
|
||||
isInitialized: boolean
|
||||
isLoaded: boolean
|
||||
error: any
|
||||
recentLogSqlSnippets: LogSqlSnippets.Content[]
|
||||
|
||||
baseUrl: string
|
||||
projectRef?: string
|
||||
|
||||
load: () => void
|
||||
loadPersistentData: () => Promise<void>
|
||||
create: (x: UserContent) => { data: UserContent; error: { error: { message: string } } }
|
||||
list: (filter?: any) => any[]
|
||||
reports: (filter?: any) => any[]
|
||||
sqlSnippets: (filter?: any) => any[]
|
||||
logSqlSnippets: (filter?: CustomFilter) => UserContent[]
|
||||
addRecentLogSqlSnippet: (snippet: Partial<LogSqlSnippets.Content>) => void
|
||||
clearRecentLogSqlSnippets: () => void
|
||||
setProjectRef: (ref?: string) => void
|
||||
}
|
||||
|
||||
@@ -39,9 +43,12 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
}
|
||||
|
||||
baseUrl: string
|
||||
localStorageKey: string
|
||||
recentLogSqlKey: string
|
||||
projectRef: string
|
||||
|
||||
data: UserContentMap = {}
|
||||
recentLogSqlSnippets: LogSqlSnippets.Content[] = []
|
||||
|
||||
state = this.STATES.INITIAL
|
||||
error = null
|
||||
@@ -50,6 +57,9 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
const { projectRef } = options
|
||||
this.projectRef = projectRef
|
||||
this.rootStore = rootStore
|
||||
this.localStorageKey = `project-content-${projectRef}`
|
||||
this.recentLogSqlKey = `${this.localStorageKey}-recent-log-sql`
|
||||
this.loadPersistentData()
|
||||
this.baseUrl = ``
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
@@ -93,12 +103,21 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
}
|
||||
}
|
||||
|
||||
async loadPersistentData() {}
|
||||
async loadPersistentData() {
|
||||
this.loadRecentLogSqlSnippets()
|
||||
}
|
||||
|
||||
async loadRemotePersistentData(userId: any) {
|
||||
const sqlSnippets = this.sqlSnippets((x: any) => x.owner_id === userId)
|
||||
}
|
||||
|
||||
loadRecentLogSqlSnippets() {
|
||||
if (typeof window === 'undefined') return
|
||||
this.recentLogSqlSnippets = JSON.parse(
|
||||
(window as any).localStorage.getItem(this.recentLogSqlKey) || '[]'
|
||||
)
|
||||
}
|
||||
|
||||
list(filter?: any) {
|
||||
const arr = Object.values(this.data)
|
||||
if (!!filter) {
|
||||
@@ -141,6 +160,25 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
return arr_filtered
|
||||
}
|
||||
}
|
||||
public addRecentLogSqlSnippet(snippet: Partial<LogSqlSnippets.Content>) {
|
||||
if (typeof window === 'undefined') return
|
||||
const defaults: LogSqlSnippets.Content = {
|
||||
schema_version: '1',
|
||||
favorite: false,
|
||||
sql: '',
|
||||
content_id: '',
|
||||
}
|
||||
this.recentLogSqlSnippets.push({ ...defaults, ...snippet })
|
||||
;(window as any).localStorage.setItem(
|
||||
this.recentLogSqlKey,
|
||||
JSON.stringify(this.recentLogSqlSnippets)
|
||||
)
|
||||
}
|
||||
public clearRecentLogSqlSnippets() {
|
||||
if (typeof window === 'undefined') return
|
||||
this.recentLogSqlSnippets = []
|
||||
;(window as any).localStorage.setItem(this.recentLogSqlKey, JSON.stringify([]))
|
||||
}
|
||||
|
||||
logSqlSnippets(filter?: CustomFilter) {
|
||||
let arr = ((Object.values(this.data)[0] as any) || []) as UserContent[]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export interface UserContent {
|
||||
export interface UserContent<T = Dashboards.Content | SqlSnippets.Content | LogSqlSnippets.Content> {
|
||||
id?: string
|
||||
name: string
|
||||
description?: string
|
||||
type: 'sql' | 'report' | 'log_sql'
|
||||
visibility: 'user' | 'project' | 'org' | 'public'
|
||||
content: Dashboards.Content | SqlSnippets.Content | LogSqlSnippets.Content
|
||||
content: T
|
||||
owner_id?: number // user id
|
||||
last_updated_by?: number // user id
|
||||
inserted_at?: string // '2021-08-26T08:24:52.040695+00:00'
|
||||
|
||||
Reference in new issue
Block a user