Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/docs/content/guides/platform/compute-and-disk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ In paid organizations, Nano Compute are billed at the same price as Micro Comput
[^2]: Database size for each compute instance is the default recommendation but the actual performance of your database has many contributing factors, including resources available to it and the size of the data contained within it. See the [shared responsibility model](/docs/guides/platform/shared-responsibility-model) for more information.
[^3]: Compute resources on the Free plan are subject to change.

Compute sizes can be changed by first selecting your project in the dashboard [here](/dashboard/project/_/settings/compute-and-disk) and the upgrade process will [incur downtime](/docs/guides/platform/compute-and-disk#upgrade-downtime).
Compute sizes can be changed by first selecting your project in the dashboard [here](/dashboard/project/_/settings/compute-and-disk) and the upgrade process will [incur downtime](/docs/guides/platform/compute-and-disk#upgrades).

<Image
alt="Compute Size Selection"
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/components/interfaces/Auth/Users/SortDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ export const SortDropdown = ({
className: 'w-80 text-center',
text: (
<>
Sorting cannot be changed which searching on a specific column. If you'd like to
sort on other columns, change the search to{' '}
Sorting cannot be changed when searching on a specific column. If you'd like to sort
on other columns, change the search to{' '}
<span className="text-warning">all columns</span> from the header.
</>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface UsersSearchProps {
searchInvalid: boolean
specificFilterColumn: 'id' | 'email' | 'phone' | 'freeform'
setSearch: (value: SetStateAction<string>) => void
setFilterKeywords: (value: SetStateAction<string>) => void
setFilterKeywords: (value: string) => void
setSpecificFilterColumn: (value: 'id' | 'email' | 'phone' | 'freeform') => void
}

Expand Down
82 changes: 78 additions & 4 deletions apps/studio/components/interfaces/Auth/Users/UsersV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { User, useUsersInfiniteQuery } from 'data/auth/users-infinite-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { cleanPointerEventsNoneOnBody, isAtBottom } from 'lib/helpers'
import {
Button,
Expand Down Expand Up @@ -50,14 +51,17 @@ import {
import { formatUserColumns, formatUsersData } from './Users.utils'
import { UsersFooter } from './UsersFooter'
import { UsersSearch } from './UsersSearch'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'

export const UsersV2 = () => {
const queryClient = useQueryClient()
const { ref: projectRef } = useParams()
const { data: project } = useSelectedProjectQuery()
const { data: selectedOrg } = useSelectedOrganizationQuery()
const gridRef = useRef<DataGridHandle>(null)
const xScroll = useRef<number>(0)
const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled()
const { mutate: sendEvent } = useSendEventMutation()

const {
authenticationShowProviderFilter: showProviderFilter,
Expand Down Expand Up @@ -151,6 +155,19 @@ export const UsersV2 = () => {
// [Joshen] Only relevant for when selecting one user only
const selectedUserFromCheckbox = users.find((u) => u.id === [...selectedUsers][0])

const telemetryProps = {
sort_column: sortColumn,
sort_order: sortOrder,
providers: selectedProviders,
user_type: filter === 'all' ? undefined : filter,
keywords: filterKeywords,
filter_column: specificFilterColumn === 'freeform' ? undefined : specificFilterColumn,
}
const telemetryGroups = {
project: projectRef ?? 'Unknown',
organization: selectedOrg?.slug ?? 'Unknown',
}

const handleScroll = (event: UIEvent<HTMLDivElement>) => {
const isScrollingHorizontally = xScroll.current !== event.currentTarget.scrollLeft
xScroll.current = event.currentTarget.scrollLeft
Expand Down Expand Up @@ -293,6 +310,15 @@ export const UsersV2 = () => {
setFilterKeywords={(s) => {
setFilterKeywords(s)
setSelectedUser(undefined)
sendEvent({
action: 'auth_users_search_submitted',
properties: {
trigger: 'search_input',
...telemetryProps,
keywords: s,
},
groups: telemetryGroups,
})
}}
setSpecificFilterColumn={(value) => {
if (value === 'freeform') {
Expand All @@ -304,7 +330,21 @@ export const UsersV2 = () => {
/>

{showUserTypeFilter && specificFilterColumn === 'freeform' && (
<Select_Shadcn_ value={filter} onValueChange={(val) => setFilter(val as Filter)}>
<Select_Shadcn_
value={filter}
onValueChange={(val) => {
setFilter(val as Filter)
sendEvent({
action: 'auth_users_search_submitted',
properties: {
trigger: 'user_type_filter',
...telemetryProps,
user_type: val,
},
groups: telemetryGroups,
})
}}
>
<SelectContent_Shadcn_>
<SelectTrigger_Shadcn_
size="tiny"
Expand Down Expand Up @@ -344,7 +384,18 @@ export const UsersV2 = () => {
labelClass="text-xs"
maxHeightClass="h-[190px]"
className="w-52"
onSaveFilters={setSelectedProviders}
onSaveFilters={(providers) => {
setSelectedProviders(providers)
sendEvent({
action: 'auth_users_search_submitted',
properties: {
trigger: 'provider_filter',
...telemetryProps,
providers,
},
groups: telemetryGroups,
})
}}
/>
)}

Expand Down Expand Up @@ -402,7 +453,20 @@ export const UsersV2 = () => {
sortColumn={sortColumn}
sortOrder={sortOrder}
sortByValue={sortByValue}
setSortByValue={setSortByValue}
setSortByValue={(value) => {
const [sortColumn, sortOrder] = value.split(':')
setSortByValue(value)
sendEvent({
action: 'auth_users_search_submitted',
properties: {
trigger: 'sort_change',
...telemetryProps,
sort_column: sortColumn,
sort_order: sortOrder,
},
groups: telemetryGroups,
})
}}
showSortByEmail={showSortByEmail}
showSortByPhone={showSortByPhone}
/>
Expand All @@ -418,7 +482,17 @@ export const UsersV2 = () => {
type="default"
className="w-7"
loading={isRefetching && !isFetchingNextPage}
onClick={() => refetch()}
onClick={() => {
refetch()
sendEvent({
action: 'auth_users_search_submitted',
properties: {
trigger: 'refresh_button',
...telemetryProps,
},
groups: telemetryGroups,
})
}}
tooltip={{ content: { side: 'bottom', text: 'Refresh' } }}
/>
<AddUserDropdown />
Expand Down
73 changes: 48 additions & 25 deletions apps/studio/components/interfaces/Support/AttachmentUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
import { compact } from 'lodash'
import { Plus, X } from 'lucide-react'
import { type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
type ChangeEvent,
type RefObject,
} from 'react'
import { toast } from 'sonner'
// End of third-party imports

import { useGenerateAttachmentURLsMutation } from 'data/support/generate-attachment-urls-mutation'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { cn } from 'ui'
import { createSupportStorageClient } from './support-storage-client'

const MAX_ATTACHMENTS = 5

const uploadAttachments = async (ref: string, files: File[]) => {
const uploadAttachments = async ({ userId, files }: { userId: string; files: File[] }) => {
if (files.length === 0) return []

const supportSupabaseClient = createSupportStorageClient()

const filesToUpload = Array.from(files)
const uploadedFiles = await Promise.all(
filesToUpload.map(async (file) => {
const suffix = file.type.split('/')[1]
const prefix = `${ref}/${uuidv4()}.${suffix}`
const prefix = `${userId}/${uuidv4()}.${suffix}`
const options = { cacheControl: '3600' }

const { data, error } = await supportSupabaseClient.storage
Expand All @@ -29,24 +41,17 @@ const uploadAttachments = async (ref: string, files: File[]) => {
})
)
const keys = compact(uploadedFiles).map((file) => file.path)

if (keys.length === 0) return []

const { data, error } = await supportSupabaseClient.storage
.from('support-attachments')
.createSignedUrls(keys, 10 * 365 * 24 * 60 * 60)
if (error) {
console.error('Failed to retrieve URLs for attachments', error)
}
return data ? data.map((file) => file.signedUrl) : []
return keys
}

export function useAttachmentUpload() {
const { profile } = useProfile()
const uploadButtonRef = useRef<HTMLInputElement>(null)

const [uploadedFiles, setUploadedFiles] = useState<File[]>([])
const [uploadedDataUrls, setUploadedDataUrls] = useState<string[]>([])

const { mutateAsync: generateAttachmentURLs } = useGenerateAttachmentURLsMutation()

const isFull = uploadedFiles.length >= MAX_ATTACHMENTS

const addFile = useCallback(() => {
Expand Down Expand Up @@ -92,14 +97,18 @@ export function useAttachmentUpload() {
}
}, [uploadedFiles])

const createAttachments = useCallback(
async (projectRef: string) => {
const attachments =
uploadedFiles.length > 0 ? await uploadAttachments(projectRef, uploadedFiles) : []
return attachments
},
[uploadedFiles]
)
const createAttachments = useCallback(async () => {
if (!profile?.id) {
console.error('[Support Form > uploadAttachments] Unable to upload files, missing user ID')
toast.error('Unable to upload attachments')
return []
}

const filenames = await uploadAttachments({ userId: profile.gotrue_id, files: uploadedFiles })
const urls = await generateAttachmentURLs({ filenames })
return urls
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [uploadedFiles])

return useMemo(
() => ({
Expand All @@ -116,22 +125,36 @@ export function useAttachmentUpload() {
}

interface AttachmentUploadDisplayProps {
uploadButtonRef: React.RefObject<HTMLInputElement>
uploadButtonRef: RefObject<HTMLInputElement>
isFull: boolean
uploadedDataUrls: string[]
addFile: () => void
handleFileUpload: (event: ChangeEvent<HTMLInputElement>) => Promise<void>
removeFileUpload: (idx: number) => void
uploadedDataUrls: Array<string>
}

export function AttachmentUploadDisplay({
uploadButtonRef,
isFull,
uploadedDataUrls,
addFile,
handleFileUpload,
removeFileUpload,
uploadedDataUrls,
}: AttachmentUploadDisplayProps) {
const { profile } = useProfile()

if (!profile) {
return (
<div>
<h3 className="text-sm text-foreground">Attachments</h3>
<p className="text-sm text-foreground-lighter mt-2">
Uploads are only supported when logged in. Please reply to the acknowledgement email you
will receive with any screenshots you'd like to upload.
</p>
</div>
)
}

return (
<div className="flex flex-col gap-y-4">
<div className="flex flex-col gap-y-1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const SUPPORT_ACCESS_CATEGORIES: ExtendedSupportCategories[] = [
SupportCategories.DATABASE_UNRESPONSIVE,
SupportCategories.PERFORMANCE_ISSUES,
SupportCategories.PROBLEM,
SupportCategories.DASHBOARD_BUG,
]

interface SupportAccessToggleProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ import {
createLoader,
createParser,
createSerializer,
type inferParserType,
parseAsString,
parseAsStringLiteral,
type inferParserType,
type UseQueryStatesKeysMap,
} from 'nuqs'
// End of third-party imports

import {
DocsSearchResultType as PageType,
type DocsSearchResult as Page,
type DocsSearchResultSection as PageSection,
DocsSearchResultType as PageType,
} from 'common'
import { getProjectDetail } from 'data/projects/project-detail-query'
import { DOCS_URL } from 'lib/constants'
Expand Down
15 changes: 4 additions & 11 deletions apps/studio/components/interfaces/Support/SupportFormV2.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Dispatch, MouseEventHandler } from 'react'
import { type Dispatch, type MouseEventHandler } from 'react'
import type { SubmitHandler, UseFormReturn } from 'react-hook-form'
// End of third-party imports

Expand All @@ -14,7 +14,7 @@ import {
AffectedServicesSelector,
CATEGORIES_WITHOUT_AFFECTED_SERVICES,
} from './AffectedServicesSelector'
import { useAttachmentUpload } from './AttachmentUpload'
import { AttachmentUploadDisplay, useAttachmentUpload } from './AttachmentUpload'
import { CategoryAndSeverityInfo } from './CategoryAndSeverityInfo'
import { ClientLibraryInfo } from './ClientLibraryInfo'
import { MessageField } from './MessageField'
Expand Down Expand Up @@ -72,7 +72,7 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo

const onSubmit: SubmitHandler<SupportFormValues> = async (values) => {
dispatch({ type: 'SUBMIT' })
const attachments = await attachmentUpload.createAttachments(projectRef)
const attachments = await attachmentUpload.createAttachments()

const selectedLibrary = values.library
? CLIENT_LIBRARIES.find((library) => library.language === values.library)
Expand Down Expand Up @@ -153,14 +153,7 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
<ClientLibraryInfo form={form} library={library} category={category} />
<AffectedServicesSelector form={form} category={category} />
<MessageField form={form} originalError={initialError} />
{/* <AttachmentUploadDisplay {...attachmentUpload} /> */}
<div>
<h3 className="text-sm text-foreground">Attachments</h3>
<p className="text-sm text-foreground-lighter mt-2">
Uploads have been temporarily disabled. Please reply to the acknowledgement email you
will receive with any screenshots you'd like to upload
</p>
</div>
<AttachmentUploadDisplay {...attachmentUpload} />
</div>

<DialogSectionSeparator />
Expand Down
Loading
Loading