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:
Jonathan Summers-Muir authored and GitHub committed 2022-05-04 09:36:37 +08:00
commit 44f9db13d1
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))
+41 -3
View File
@@ -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[]
+2 -2
View File
@@ -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'