mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Revert "feat(docs): route docs page feedback comments to Postgres" (#47027)
Reverts supabase/supabase#46941 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Feedback is now automatically routed to the appropriate documentation team based on the section being viewed. * **Improvements** * Streamlined feedback submission process—votes and comments are now collected more efficiently in a single submission. * Enhanced feedback data handling and organization for better team collaboration. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
8b05c769fb
commit
c9baed829f
6 files changed
+114
-100
No files matched your search
@@ -2,8 +2,9 @@
|
||||
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { IS_PLATFORM } from '~/lib/constants'
|
||||
import { useSendFeedbackMutation } from '~/lib/fetch/feedback'
|
||||
import { useSendTelemetryEvent } from '~/lib/telemetry'
|
||||
import { gotrueClient, useConstant, useIsLoggedIn, type Database } from 'common'
|
||||
import { useConstant, useIsLoggedIn, type Database } from 'common'
|
||||
import { Check, MessageSquareQuote, X } from 'lucide-react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import {
|
||||
@@ -16,7 +17,7 @@ import {
|
||||
} from 'react'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
import { getSanitizedTabParams, updateDocsFeedbackComment } from './Feedback.utils'
|
||||
import { getLinearTeam, getSanitizedTabParams } from './Feedback.utils'
|
||||
import { FeedbackModal, type FeedbackFields } from './FeedbackModal'
|
||||
|
||||
const FeedbackButton = forwardRef<
|
||||
@@ -77,20 +78,15 @@ function Feedback({ className }: { className?: string }) {
|
||||
|
||||
const pathname = usePathname() ?? ''
|
||||
const sendTelemetryEvent = useSendTelemetryEvent()
|
||||
const { mutate: sendFeedbackComment } = useSendFeedbackMutation()
|
||||
const supabase = useConstant(() =>
|
||||
IS_PLATFORM
|
||||
? createClient<Database>(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
{
|
||||
accessToken: async () =>
|
||||
(await gotrueClient.getSession()).data.session?.access_token ?? null,
|
||||
}
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
)
|
||||
: undefined
|
||||
)
|
||||
const isLoggedIn = useIsLoggedIn()
|
||||
const feedbackInsertRef = useRef<Promise<number | null> | null>(null)
|
||||
|
||||
const unanswered = state.type === 'unanswered'
|
||||
const isYes = 'response' in state && state.response === 'yes'
|
||||
@@ -98,26 +94,17 @@ function Feedback({ className }: { className?: string }) {
|
||||
const showYes = unanswered || isYes
|
||||
const showNo = unanswered || isNo
|
||||
|
||||
// Returns the new row's id for logged-in users (who can read their own row
|
||||
// back) so an optional comment can be attached afterwards. Anonymous votes are
|
||||
// insert-only and resolve to null.
|
||||
async function sendFeedbackVote(response: Response): Promise<number | null> {
|
||||
if (!supabase) return null
|
||||
async function sendFeedbackVote(response: Response) {
|
||||
if (!supabase) return
|
||||
|
||||
const row = { vote: response, page: pathname, metadata: { query: getSanitizedTabParams() } }
|
||||
|
||||
if (isLoggedIn) {
|
||||
const { data, error } = await supabase.from('feedback').insert(row).select('id').single()
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return null
|
||||
}
|
||||
return data.id
|
||||
} else {
|
||||
const { error } = await supabase.from('feedback').insert(row)
|
||||
if (error) console.error(error)
|
||||
return null
|
||||
}
|
||||
const { error } = await supabase.from('feedback').insert({
|
||||
vote: response,
|
||||
page: pathname,
|
||||
metadata: {
|
||||
query: getSanitizedTabParams(),
|
||||
},
|
||||
})
|
||||
if (error) console.error(error)
|
||||
}
|
||||
|
||||
function handleVote(response: Response) {
|
||||
@@ -125,7 +112,7 @@ function Feedback({ className }: { className?: string }) {
|
||||
action: 'docs_feedback_clicked',
|
||||
properties: { response },
|
||||
})
|
||||
feedbackInsertRef.current = sendFeedbackVote(response)
|
||||
sendFeedbackVote(response)
|
||||
dispatch({ event: 'VOTED', response })
|
||||
// Focus so screen reader users are aware of the new element
|
||||
setTimeout(() => {
|
||||
@@ -141,14 +128,15 @@ function Feedback({ className }: { className?: string }) {
|
||||
}, 100)
|
||||
}
|
||||
|
||||
async function handleSubmit({ comment, title }: FeedbackFields) {
|
||||
// Wait for the vote insert so the comment update can't run before the row
|
||||
// (and its id) exists.
|
||||
const id = await feedbackInsertRef.current
|
||||
if (supabase && id !== null) {
|
||||
const { error } = await updateDocsFeedbackComment(supabase, { id, title, comment })
|
||||
if (error) console.error(error)
|
||||
}
|
||||
async function handleSubmit({ page, comment, title }: FeedbackFields) {
|
||||
sendFeedbackComment({
|
||||
message: comment,
|
||||
pathname: page,
|
||||
title,
|
||||
// @ts-expect-error -- can't click this button without having a state.response
|
||||
isHelpful: state.response === 'yes',
|
||||
team: getLinearTeam(pathname),
|
||||
})
|
||||
setModalOpen(false)
|
||||
refocusButton()
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { updateDocsFeedbackComment } from './Feedback.utils'
|
||||
|
||||
describe('updateDocsFeedbackComment', () => {
|
||||
it('updates the feedback row by id with the comment', async () => {
|
||||
const eq = vi.fn().mockResolvedValue({ error: null })
|
||||
const update = vi.fn(() => ({ eq }))
|
||||
const from = vi.fn(() => ({ update }))
|
||||
const supabase = { from } as any
|
||||
|
||||
await updateDocsFeedbackComment(supabase, {
|
||||
id: 42,
|
||||
title: 'Great explanation',
|
||||
comment: 'This page was really helpful.',
|
||||
})
|
||||
|
||||
expect(from).toHaveBeenCalledWith('feedback')
|
||||
expect(update).toHaveBeenCalledWith({
|
||||
title: 'Great explanation',
|
||||
comment: 'This page was really helpful.',
|
||||
})
|
||||
expect(eq).toHaveBeenCalledWith('id', 42)
|
||||
})
|
||||
})
|
||||
@@ -1,17 +1,44 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
import type { Database } from 'common'
|
||||
import { pick } from 'lodash-es'
|
||||
|
||||
type FeedbackComment = {
|
||||
id: number
|
||||
title: string
|
||||
comment: string
|
||||
}
|
||||
/**
|
||||
* Gets the Notion team to send feedback to based on the pathname.
|
||||
*/
|
||||
const getLinearTeam = (pathname: string) => {
|
||||
const DEFAULT_TEAM = 'Docs'
|
||||
|
||||
const updateDocsFeedbackComment = (
|
||||
supabase: SupabaseClient<Database>,
|
||||
{ id, title, comment }: FeedbackComment
|
||||
) => supabase.from('feedback').update({ title, comment }).eq('id', id)
|
||||
// Pathname has format `/guides/(team)/**`
|
||||
const pathParts = pathname.split('/')
|
||||
|
||||
if (pathParts[1] !== 'guides' || !pathParts[2]) return DEFAULT_TEAM
|
||||
|
||||
switch (pathParts[2]) {
|
||||
case 'database':
|
||||
return 'Postgres'
|
||||
case 'auth':
|
||||
return 'Auth'
|
||||
case 'storage':
|
||||
return 'Storage'
|
||||
case 'functions':
|
||||
return 'Functions'
|
||||
case 'realtime':
|
||||
return 'Realtime'
|
||||
case 'ai':
|
||||
return 'AI'
|
||||
case 'local-development':
|
||||
case 'self-hosting':
|
||||
case 'deployment':
|
||||
return 'Dev Workflows'
|
||||
case 'integrations':
|
||||
return 'API'
|
||||
case 'security':
|
||||
return 'Security'
|
||||
case 'platform':
|
||||
case 'monitoring-troubleshooting':
|
||||
return 'Infra'
|
||||
default:
|
||||
return DEFAULT_TEAM
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tab selection state from the URL search params.
|
||||
@@ -26,4 +53,4 @@ const getSanitizedTabParams = () => {
|
||||
return pick(Object.fromEntries(searchParams.entries()), queryGroups)
|
||||
}
|
||||
|
||||
export { updateDocsFeedbackComment, getSanitizedTabParams }
|
||||
export { getLinearTeam, getSanitizedTabParams }
|
||||
@@ -0,0 +1,50 @@
|
||||
import { type UseMutationOptions, useMutation } from '@tanstack/react-query'
|
||||
|
||||
import { type ResponseError } from '~/types/fetch'
|
||||
import { post } from './fetchWrappers'
|
||||
|
||||
type SendFeedbackVariables = {
|
||||
title: string
|
||||
message: string
|
||||
isHelpful: boolean
|
||||
pathname?: string
|
||||
team?: string
|
||||
}
|
||||
|
||||
export async function sendFeedback({
|
||||
message,
|
||||
pathname,
|
||||
title,
|
||||
isHelpful,
|
||||
team,
|
||||
}: SendFeedbackVariables) {
|
||||
const { data, error } = await post('/platform/feedback/docs', {
|
||||
body: {
|
||||
page: pathname ?? '',
|
||||
isHelpful,
|
||||
title,
|
||||
feedback: message,
|
||||
team,
|
||||
},
|
||||
})
|
||||
if (error) throw Error(`Couldn't send feedback`, { cause: error })
|
||||
return data
|
||||
}
|
||||
|
||||
type SendFeedbackData = Awaited<ReturnType<typeof sendFeedback>>
|
||||
|
||||
export const useSendFeedbackMutation = (
|
||||
options: Omit<
|
||||
UseMutationOptions<SendFeedbackData, ResponseError, SendFeedbackVariables>,
|
||||
'mutationFn'
|
||||
> = {}
|
||||
) => {
|
||||
return useMutation<SendFeedbackData, ResponseError, SendFeedbackVariables>({
|
||||
...options,
|
||||
mutationFn: (vars) => sendFeedback(vars),
|
||||
onError: (error, vars, ctx) => {
|
||||
console.error(error)
|
||||
options.onError?.(error, vars, ctx)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -129,33 +129,24 @@ export type Database = {
|
||||
Tables: {
|
||||
feedback: {
|
||||
Row: {
|
||||
comment: string | null
|
||||
date_created: string
|
||||
id: number
|
||||
metadata: Json | null
|
||||
page: string
|
||||
title: string | null
|
||||
user_id: string | null
|
||||
vote: Database['public']['Enums']['feedback_vote']
|
||||
}
|
||||
Insert: {
|
||||
comment?: string | null
|
||||
date_created?: string
|
||||
id?: never
|
||||
metadata?: Json | null
|
||||
page: string
|
||||
title?: string | null
|
||||
user_id?: string | null
|
||||
vote: Database['public']['Enums']['feedback_vote']
|
||||
}
|
||||
Update: {
|
||||
comment?: string | null
|
||||
date_created?: string
|
||||
id?: never
|
||||
metadata?: Json | null
|
||||
page?: string
|
||||
title?: string | null
|
||||
user_id?: string | null
|
||||
vote?: Database['public']['Enums']['feedback_vote']
|
||||
}
|
||||
Relationships: []
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
alter table feedback
|
||||
add column user_id uuid default auth.uid(),
|
||||
add column title text,
|
||||
add column comment text;
|
||||
|
||||
create policy "Users can read their own feedback"
|
||||
on feedback
|
||||
as permissive for select
|
||||
to authenticated
|
||||
using ((select auth.uid()) = user_id);
|
||||
|
||||
create policy "Users can update their own feedback"
|
||||
on feedback
|
||||
as permissive for update
|
||||
to authenticated
|
||||
using ((select auth.uid()) = user_id)
|
||||
with check ((select auth.uid()) = user_id);
|
||||
Reference in new issue
Block a user