Skip to content

Commit f6d9617

Browse files
alaisterjoshenlim
andauthored
chore: paginate user content (supabase#30675)
* chore: paginate user content * progress * loading states * add load more buttons to private snippets * working pagination * fix some types * always show snippet counts * support new api parameters * favorite snippets * progress * searching * paginate root folder * fix renaming snippets * fix ts * removed unused prop * Shift sharing/unsharing query logic outside of valtio to leverage on RQ only * Fix invalidation on an unsaved snippet * Clean up * Fix * Clean up * Update API type * Update API * fix duplicate snippets error after moving a snippet * add currently selected snippet * Fix unsharing a snippet that has yet to be opened * i'm dumb * fix sharing a snippet * fix sharing and unsharing * show favorite or shared snippet in list even if it's in another page * Fix wrong import for debounce * Fix false positive toast error when creating custom report * Update API type * Change create new snippet CTA to link back to /new with skip flag * Fix saving logs explorer query * Bump page number --------- Co-authored-by: Joshen Lim <[email protected]>
1 parent f25ef1e commit f6d9617

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1274
-1006
lines changed

apps/studio/components/interfaces/Integrations/CronJobs/CronJobScheduleSection.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { toString as CronToString } from 'cronstrue'
21
import { motion } from 'framer-motion'
32
import { useEffect, useState } from 'react'
43
import { UseFormReturn } from 'react-hook-form'
@@ -26,7 +25,7 @@ import {
2625
} from 'ui'
2726
import { Input } from 'ui-patterns/DataInputs/Input'
2827
import { CreateCronJobForm } from './CreateCronJobSheet'
29-
import { formatScheduleString, getScheduleMessage, secondsPattern } from './CronJobs.utils'
28+
import { formatScheduleString, getScheduleMessage } from './CronJobs.utils'
3029
import CronSyntaxChart from './CronSyntaxChart'
3130

3231
interface CronJobScheduleSectionProps {

apps/studio/components/interfaces/Reports/Reports.CreateReportModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const CreateReportModal = ({ visible, onCancel, afterSubmit }: CreateRepo
2121
const { mutate: insertReport, isLoading: isCreating } = useContentInsertMutation({
2222
onSuccess: (data) => {
2323
toast.success('Successfully created new report')
24-
const newReportId = data[0].id
24+
const newReportId = data.id
2525
router.push(`/project/${ref}/reports/${newReportId}`)
2626
afterSubmit()
2727
},

apps/studio/components/interfaces/Reports/Reports.UpdateModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const UpdateCustomReportModal = ({
3939
projectRef: ref,
4040
payload: {
4141
...selectedReport,
42+
owner_id: selectedReport.owner_id!,
4243
project_id: selectedReport.project_id,
4344
id: selectedReport.id,
4445
name: newVals.name,

apps/studio/components/interfaces/Reports/Reports.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ const Reports = () => {
3434
const [endDate, setEndDate] = useState<any>(null)
3535
const [hasEdits, setHasEdits] = useState<any>(false)
3636

37-
const { data: userContents, isLoading } = useContentQuery(ref)
37+
const { data: userContents, isLoading } = useContentQuery({
38+
projectRef: ref,
39+
type: 'report',
40+
})
3841
const { mutate: saveReport, isLoading: isSaving } = useContentUpdateMutation({
3942
onSuccess: () => {
4043
setHasEdits(false)

apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ const MonacoEditor = ({
190190
onMount={handleEditorOnMount}
191191
onChange={handleEditorChange}
192192
defaultLanguage="pgsql"
193-
defaultValue={snippet?.snippet.content.sql}
193+
defaultValue={snippet?.snippet.content?.sql}
194194
path={id}
195195
options={{
196196
tabSize: 2,

apps/studio/components/interfaces/SQLEditor/MoveQueryModal.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ import { useParams } from 'common'
99
import { getContentById } from 'data/content/content-id-query'
1010
import { useContentUpsertV2Mutation } from 'data/content/content-upsert-v2-mutation'
1111
import { useSQLSnippetFolderCreateMutation } from 'data/content/sql-folder-create-mutation'
12-
import { Snippet, SnippetDetail } from 'data/content/sql-folders-query'
13-
import { useSnippetFolders, useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
12+
import { Snippet } from 'data/content/sql-folders-query'
13+
import {
14+
SnippetWithContent,
15+
useSnippetFolders,
16+
useSqlEditorV2StateSnapshot,
17+
} from 'state/sql-editor-v2'
1418
import {
1519
Button,
1620
CommandEmpty_Shadcn_,
@@ -125,16 +129,18 @@ export const MoveQueryModal = ({ visible, snippets = [], onClose }: MoveQueryMod
125129

126130
await Promise.all(
127131
snippets.map(async (snippet) => {
128-
let snippetContent = (snippet as SnippetDetail)?.content
132+
let snippetContent = (snippet as SnippetWithContent)?.content
129133
if (snippetContent === undefined) {
130134
const { content } = await getContentById({ projectRef: ref, id: snippet.id })
131-
snippetContent = content
135+
if ('sql' in content) {
136+
snippetContent = content
137+
}
132138
}
133139

134140
if (snippetContent === undefined) {
135141
return toast.error('Failed to save snippet: Unable to retrieve snippet contents')
136142
} else {
137-
moveSnippetAsync({
143+
await moveSnippetAsync({
138144
projectRef: ref,
139145
payload: {
140146
id: snippet.id,
@@ -144,7 +150,7 @@ export const MoveQueryModal = ({ visible, snippets = [], onClose }: MoveQueryMod
144150
visibility: snippet.visibility,
145151
project_id: snippet.project_id,
146152
owner_id: snippet.owner_id,
147-
folder_id: selectedId === 'root' ? (null as any) : folderId,
153+
folder_id: selectedId === 'root' ? null : folderId,
148154
content: snippetContent as any,
149155
},
150156
})
@@ -158,7 +164,7 @@ export const MoveQueryModal = ({ visible, snippets = [], onClose }: MoveQueryMod
158164
snippets.forEach((snippet) => {
159165
snapV2.updateSnippet({
160166
id: snippet.id,
161-
snippet: { ...snippet, folder_id: selectedId === 'root' ? (null as any) : selectedId },
167+
snippet: { ...snippet, folder_id: selectedId === 'root' ? null : selectedId },
162168
skipSave: true,
163169
})
164170
})

apps/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { toast } from 'sonner'
44
import { useParams } from 'common'
55
import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation'
66
import { getContentById } from 'data/content/content-id-query'
7+
import { useContentUpdateMutation } from 'data/content/content-update-mutation'
78
import { Snippet } from 'data/content/sql-folders-query'
89
import type { SqlSnippet } from 'data/content/sql-snippets-query'
910
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
@@ -57,7 +58,9 @@ const RenameQueryModal = ({
5758
} else {
5859
try {
5960
const { content } = await getContentById({ projectRef: ref, id: snippet.id })
60-
titleSql({ sql: content.sql })
61+
if ('sql' in content) {
62+
titleSql({ sql: content.sql })
63+
}
6164
} catch (error) {
6265
toast.error('Unable to generate title based on query contents')
6366
}
@@ -70,24 +73,44 @@ const RenameQueryModal = ({
7073
return errors
7174
}
7275

76+
const { mutateAsync: updateContent } = useContentUpdateMutation()
77+
7378
const onSubmit = async (values: any, { setSubmitting }: any) => {
7479
if (!ref) return console.error('Project ref is required')
7580
if (!id) return console.error('Snippet ID is required')
7681

7782
setSubmitting(true)
7883
try {
84+
let localSnippet = snippet
85+
7986
// [Joshen] For SQL V2 - content is loaded on demand so we need to fetch the data if its not already loaded in the valtio state
80-
if (!('content' in snippet)) {
81-
// [Joshen] I feel like there's definitely some optimization we can do here but will involve changes to API
82-
const snippet = await getContentById({ projectRef: ref, id })
83-
snapV2.addSnippet({ projectRef: ref, snippet })
87+
if (!('content' in localSnippet)) {
88+
localSnippet = await getContentById({ projectRef: ref, id })
89+
90+
snapV2.addSnippet({ projectRef: ref, snippet: localSnippet })
8491
}
85-
snapV2.renameSnippet({ id, name: nameInput, description: descriptionInput })
8692

93+
const updatedSnippet = await updateContent({
94+
projectRef: ref,
95+
id,
96+
type: localSnippet.type,
97+
content: (localSnippet as any).content,
98+
name: nameInput,
99+
description: descriptionInput,
100+
})
101+
102+
snapV2.renameSnippet({
103+
id,
104+
name: updatedSnippet.name,
105+
description: updatedSnippet.description,
106+
})
107+
108+
toast.success('Successfully renamed snippet!')
87109
if (onComplete) onComplete()
88110
} catch (error: any) {
111+
setSubmitting(false)
89112
// [Joshen] We probably need some rollback cause all the saving is async
90-
toast.error(`Failed to rename query: ${error.message}`)
113+
toast.error(`Failed to rename snippet: ${error.message}`)
91114
}
92115
}
93116

apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export const SQLEditor = () => {
227227
const selection = editor.getSelection()
228228
const selectedValue = selection ? editor.getModel()?.getValueInRange(selection) : undefined
229229
const sql = snippet
230-
? (selectedValue || editorRef.current?.getValue()) ?? snippet.snippet.content.sql
230+
? (selectedValue || editorRef.current?.getValue()) ?? snippet.snippet.content?.sql
231231
: selectedValue || editorRef.current?.getValue()
232232
formatQuery(
233233
{
@@ -267,7 +267,7 @@ export const SQLEditor = () => {
267267
const selectedValue = selection ? editor.getModel()?.getValueInRange(selection) : undefined
268268

269269
const sql = snippet
270-
? (selectedValue || editorRef.current?.getValue()) ?? snippet.snippet.content.sql
270+
? (selectedValue || editorRef.current?.getValue()) ?? snippet.snippet.content?.sql
271271
: selectedValue || editorRef.current?.getValue()
272272

273273
let queryHasIssues = false
@@ -373,7 +373,9 @@ export const SQLEditor = () => {
373373
const result = snapV2.results[id]?.[0]
374374
appSnap.setAiAssistantPanel({
375375
open: true,
376-
sqlSnippets: [snippet.snippet.content.sql.replace(sqlAiDisclaimerComment, '').trim()],
376+
sqlSnippets: [
377+
(snippet.snippet.content?.sql ?? '').replace(sqlAiDisclaimerComment, '').trim(),
378+
],
377379
initialInput: `Help me to debug the attached sql snippet which gives the following error: \n\n${result.error.message}`,
378380
})
379381
} catch (error: unknown) {

apps/studio/components/interfaces/SQLEditor/SQLEditor.utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { SnippetDetail } from 'data/content/sql-folders-query'
21
import { removeCommentsFromSql } from 'lib/helpers'
2+
import type { SnippetWithContent } from 'state/sql-editor-v2'
33
import type { SqlSnippets, UserContent } from 'types'
44
import {
55
NEW_SQL_SNIPPET_SKELETON,
@@ -52,7 +52,7 @@ export const createSqlSnippetSkeletonV2 = ({
5252
owner_id: number
5353
project_id: number
5454
folder_id?: string
55-
}): SnippetDetail => {
55+
}): SnippetWithContent => {
5656
return {
5757
...NEW_SQL_SNIPPET_SKELETON,
5858
id,

apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ import { toast } from 'sonner'
1414
import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector'
1515
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
1616
import DatabaseSelector from 'components/ui/DatabaseSelector'
17-
import { Content, ContentData } from 'data/content/content-query'
18-
import { contentKeys } from 'data/content/keys'
1917
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
2018
import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants'
2119
import { detectOS } from 'lib/helpers'
@@ -74,50 +72,10 @@ const UtilityActions = ({
7472

7573
const addFavorite = async () => {
7674
snapV2.addFavorite(id)
77-
78-
client.setQueryData<ContentData>(
79-
contentKeys.list(project?.ref),
80-
(oldData: ContentData | undefined) => {
81-
if (!oldData) return
82-
83-
return {
84-
...oldData,
85-
content: oldData.content.map((content: Content) => {
86-
if (content.type === 'sql' && content.id === id) {
87-
return {
88-
...content,
89-
content: { ...content.content, favorite: true },
90-
}
91-
}
92-
return content
93-
}),
94-
}
95-
}
96-
)
9775
}
9876

9977
const removeFavorite = async () => {
10078
snapV2.removeFavorite(id)
101-
102-
client.setQueryData<ContentData>(
103-
contentKeys.list(project?.ref),
104-
(oldData: ContentData | undefined) => {
105-
if (!oldData) return
106-
107-
return {
108-
...oldData,
109-
content: oldData.content.map((content: Content) => {
110-
if (content.type === 'sql' && content.id === id) {
111-
return {
112-
...content,
113-
content: { ...content.content, favorite: false },
114-
}
115-
}
116-
return content
117-
}),
118-
}
119-
}
120-
)
12179
}
12280

12381
return (

0 commit comments

Comments
 (0)