Files
supabase/apps/studio/state/sql-editor.ts
Joshen Lim d025e0f739 chore/fix studio jest tests (#25872)
* Fix tests in tests/unit, tests/components and files under tests, looking into tests/pages

* Fix tests under pages/projects root

* Fix

* Comment out broken tests that im stuck with

* Fix api-report.test

* Fix storage-report-test

* chore: fix some tests

* chore: remove logging

* Fix LogsPreviewer.test.js

* Fix most of logs-query-test

* Skip broken tests instead of false positiving them

---------

Co-authored-by: TzeYiing <ty@tzeyiing.com>
2024-05-11 12:05:25 +02:00

240 lines
7.2 KiB
TypeScript

import { debounce, memoize } from 'lodash'
import { useMemo } from 'react'
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'
import { devtools, proxySet } from 'valtio/utils'
import { upsertContent, UpsertContentPayload } from 'data/content/content-upsert-mutation'
import type { SqlSnippet } from 'data/content/sql-snippets-query'
import type { SqlSnippets } from 'types'
export type StateSnippet = {
snippet: SqlSnippet
splitSizes: number[]
projectRef: string
}
export const sqlEditorState = proxy({
snippets: {} as {
[key: string]: StateSnippet
},
results: {} as {
[key: string]: {
rows: any[]
error?: any
}[]
},
// Project ref as the key, ids of each snippet as the order
orders: {} as {
[key: string]: string[]
},
loaded: {} as {
[key: string]: boolean
},
needsSaving: proxySet<string>([]),
savingStates: {} as {
[key: string]: 'IDLE' | 'UPDATING' | 'UPDATING_FAILED'
},
orderSnippets: (snippets: SqlSnippet[]) => {
return (
snippets
.filter((s) => Boolean(s.id))
// first alphabetical
.sort((a, b) => a.name?.localeCompare(b.name))
)
},
reorderSnippets: (projectRef: string) => {
sqlEditorState.orders[projectRef] = sqlEditorState
.orderSnippets(
sqlEditorState.orders[projectRef].map((id) => sqlEditorState.snippets[id].snippet)
)
.map((s) => s.id!)
},
setRemoteSnippets: (snippets: SqlSnippet[], projectRef: string) => {
if (!sqlEditorState.orders[projectRef]) {
const orderedSnippets = sqlEditorState.orderSnippets(snippets)
sqlEditorState.orders[projectRef] = orderedSnippets.map((s) => s.id!)
}
snippets.forEach((snippet) => {
sqlEditorState.addSnippet(snippet, projectRef)
})
},
addSnippet: (snippet: SqlSnippet, projectRef: string) => {
if (snippet.id && !sqlEditorState.snippets[snippet.id]) {
sqlEditorState.snippets[snippet.id] = {
snippet,
splitSizes: [50, 50],
projectRef,
}
sqlEditorState.results[snippet.id] = []
sqlEditorState.savingStates[snippet.id] = 'IDLE'
if (
sqlEditorState.orders[projectRef] !== undefined &&
!sqlEditorState.orders[projectRef].includes(snippet.id)
) {
sqlEditorState.orders[projectRef].unshift(snippet.id)
sqlEditorState.reorderSnippets(projectRef)
}
}
sqlEditorState.loaded[projectRef] = true
},
removeSnippet: (id: string) => {
const { [id]: snippet, ...otherSnippets } = sqlEditorState.snippets
sqlEditorState.snippets = otherSnippets
const { [id]: result, ...otherResults } = sqlEditorState.results
sqlEditorState.results = otherResults
sqlEditorState.orders[snippet.projectRef] = sqlEditorState.orders[snippet.projectRef].filter(
(s) => s !== id
)
sqlEditorState.needsSaving.delete(id)
},
updateSnippet: (id: string, snippet: SqlSnippet) => {
if (sqlEditorState.snippets[id]) {
sqlEditorState.snippets[id].snippet = snippet
sqlEditorState.needsSaving.add(id)
}
},
setSplitSizes: (id: string, splitSizes: number[]) => {
if (sqlEditorState.snippets[id]) {
sqlEditorState.snippets[id].splitSizes = splitSizes
}
},
collapseUtilityPanel: (id: string) => {
if (sqlEditorState.snippets[id]) {
sqlEditorState.snippets[id].splitSizes = [100, 0]
}
},
restoreUtilityPanel: (id: string) => {
if (sqlEditorState.snippets[id]) {
sqlEditorState.snippets[id].splitSizes = [50, 50]
}
},
setSql: (id: string, sql: string) => {
if (sqlEditorState.snippets[id]) {
sqlEditorState.snippets[id].snippet.content.sql = sql
sqlEditorState.needsSaving.add(id)
}
},
renameSnippet: (id: string, name: string, description?: string) => {
if (sqlEditorState.snippets[id]) {
const { snippet, projectRef } = sqlEditorState.snippets[id]
snippet.name = name
snippet.description = description
sqlEditorState.reorderSnippets(projectRef)
sqlEditorState.needsSaving.add(id)
}
},
shareSnippet: (id: string, visibility: 'user' | 'project' | 'org' | 'public') => {
if (sqlEditorState.snippets[id]) {
const { snippet, projectRef } = sqlEditorState.snippets[id]
snippet.visibility = visibility
sqlEditorState.reorderSnippets(projectRef)
sqlEditorState.needsSaving.add(id)
}
},
addNeedsSaving: (id: string) => {
sqlEditorState.needsSaving.add(id)
},
resetResult: (id: string) => {
if (sqlEditorState.results[id]) {
sqlEditorState.results[id] = []
}
},
addResult: (id: string, results: any[]) => {
if (sqlEditorState.results[id]) {
sqlEditorState.results[id].unshift({ rows: results })
}
},
addResultError: (id: string, error: any) => {
if (sqlEditorState.results[id]) {
sqlEditorState.results[id].unshift({ rows: [], error })
}
},
addFavorite: (id: string) => {
if (sqlEditorState.snippets[id]) {
sqlEditorState.snippets[id].snippet.content.favorite = true
sqlEditorState.needsSaving.add(id)
}
},
removeFavorite: (id: string) => {
if (sqlEditorState.snippets[id]) {
sqlEditorState.snippets[id].snippet.content.favorite = false
sqlEditorState.needsSaving.add(id)
}
},
})
export const getSqlEditorStateSnapshot = () => snapshot(sqlEditorState)
export const useSqlEditorStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
useSnapshot(sqlEditorState, options)
export const useSnippets = (projectRef: string | undefined) => {
const snapshot = useSqlEditorStateSnapshot()
return useMemo(() => {
return projectRef
? snapshot.orders[projectRef]?.map((id) => snapshot.snippets[id].snippet) ?? []
: []
}, [projectRef, snapshot.orders, snapshot.snippets])
}
async function upsert(id: string, projectRef: string, payload: UpsertContentPayload) {
try {
sqlEditorState.savingStates[id] = 'UPDATING'
await upsertContent({
projectRef,
payload,
})
sqlEditorState.savingStates[id] = 'IDLE'
} catch (error) {
sqlEditorState.savingStates[id] = 'UPDATING_FAILED'
}
}
const memoizedUpdate = memoize((_id: string) => debounce(upsert, 1000))
const debouncedUpdate = (id: string, projectRef: string, payload: UpsertContentPayload) =>
memoizedUpdate(id)(id, projectRef, payload)
if (typeof window !== 'undefined') {
devtools(sqlEditorState, {
name: 'sqlEditorState',
// [Joshen] So that jest unit tests can ignore this
enabled: process.env.NEXT_PUBLIC_ENVIRONMENT !== undefined,
})
subscribe(sqlEditorState.needsSaving, () => {
const state = getSqlEditorStateSnapshot()
Array.from(state.needsSaving).forEach((id) => {
const snippet = state.snippets[id]
if (snippet) {
debouncedUpdate(id, snippet.projectRef, {
...snippet.snippet,
name: snippet.snippet.name ?? 'Untitled',
description: snippet.snippet.description ?? '',
visibility: snippet.snippet.visibility ?? 'user',
project_id: snippet.snippet.project_id ?? 0,
content: { ...snippet.snippet.content, content_id: id } as SqlSnippets.Content,
type: 'sql',
id,
})
sqlEditorState.needsSaving.delete(id)
}
})
})
}