mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
chore: log pages for functions now show the correct logs for a specific function
This commit is contained in:
1 parent
9f6d82fd12
commit
0834a2cdda
14 files changed
+381
-71
No files matched your search
-4
@@ -3,9 +3,6 @@ import { jsonSyntaxHighlight, ResponseCodeFormatter, SeverityFormatter } from '.
|
||||
|
||||
const FunctionLogsSelectionRender = ({ log }: any) => {
|
||||
const request = log?.request
|
||||
const response = log?.response
|
||||
const requestUrl = request?.url
|
||||
const host = request?.host
|
||||
|
||||
const DetailedRow = ({
|
||||
label,
|
||||
@@ -30,7 +27,6 @@ const FunctionLogsSelectionRender = ({ log }: any) => {
|
||||
<>
|
||||
<div className="px-5">
|
||||
<span className="text-scale-900 text-sm col-span-4">Event message</span>
|
||||
|
||||
<div
|
||||
className="text-xs text-wrap font-mono text-scale-1200 mt-2"
|
||||
dangerouslySetInnerHTML={{
|
||||
|
||||
@@ -177,7 +177,11 @@ export const LOG_TYPE_LABEL_MAPPING: { [k: string]: string } = {
|
||||
database: 'Database',
|
||||
}
|
||||
|
||||
export const genDefaultQuery = (table: LogsTableName, where: string | undefined) => {
|
||||
export const genDefaultQuery = (
|
||||
table: LogsTableName,
|
||||
where: string | undefined,
|
||||
idFilter?: string
|
||||
) => {
|
||||
switch (table) {
|
||||
case 'edge_logs':
|
||||
return `SELECT id, timestamp, event_message, metadata, request, response, request.method, request.path, response.status_code
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
import { LogsTableName, SQL_FILTER_TEMPLATES } from './Logs.constants'
|
||||
import { FilterObject } from './Logs.types'
|
||||
import { FilterObject, Override } from './Logs.types'
|
||||
|
||||
export function filterSqlWhereBuilder(
|
||||
filters: FilterObject | any,
|
||||
table: LogsTableName,
|
||||
override: Override
|
||||
) {
|
||||
let count = 0
|
||||
|
||||
// remove any filter arrays that are empty
|
||||
const filtersSanitized: any = Object.values(filters).filter((x: any) => x && x.length > 0)
|
||||
|
||||
const keys = Object.keys(filters)
|
||||
console.log('override first', override)
|
||||
|
||||
const hasOverride = override && override.value && override.key
|
||||
// build array of where statements
|
||||
// first item is `where` if there are any filters
|
||||
let whereArray: string[] = filtersSanitized.length > 0 ? ['where'] : []
|
||||
let whereArray: string[] = filtersSanitized.length > 0 || hasOverride ? ['where'] : []
|
||||
|
||||
if (filtersSanitized.length === 0 && !hasOverride) return whereArray
|
||||
|
||||
if (override.key && override) {
|
||||
console.log('override', override)
|
||||
count = +1
|
||||
const sql = `${override.key}='${override.value}'`
|
||||
whereArray.push(sql)
|
||||
}
|
||||
|
||||
if (filtersSanitized.length === 0) return whereArray
|
||||
|
||||
@@ -20,7 +34,6 @@ export function filterSqlWhereBuilder(
|
||||
// do not parse empty key
|
||||
if (!x) return
|
||||
|
||||
let count = 0
|
||||
const filterKeyArray: string[] = []
|
||||
|
||||
filters[x].map((value: string, i: number) => {
|
||||
|
||||
@@ -21,8 +21,8 @@ export interface LogData {
|
||||
id: string
|
||||
timestamp: number
|
||||
event_message: string
|
||||
metadata: Metadata,
|
||||
[other: string]: unknown;
|
||||
metadata: Metadata
|
||||
[other: string]: unknown
|
||||
}
|
||||
|
||||
export interface LogTemplate {
|
||||
@@ -64,3 +64,8 @@ export interface FilterObject {
|
||||
// `te` for timestamp start value.
|
||||
te?: string
|
||||
}
|
||||
|
||||
export type Override = {
|
||||
key: string
|
||||
value: string | string[] | undefined
|
||||
}
|
||||
@@ -140,6 +140,17 @@ export const SeverityFormatter = ({ value }: { value: string }) => {
|
||||
)
|
||||
break
|
||||
|
||||
case 'warning':
|
||||
return (
|
||||
<div className="flex items-center h-full gap-1">
|
||||
<div className=" p-0.5 rounded !text-amber-900">
|
||||
<IconAlertCircle size={14} strokeWidth={2} />
|
||||
</div>
|
||||
<span className="!text-amber-900 !block titlecase">{value}</span>
|
||||
</div>
|
||||
)
|
||||
break
|
||||
|
||||
// All other responses
|
||||
default:
|
||||
return (
|
||||
|
||||
@@ -25,6 +25,7 @@ import useLogsPreview from 'hooks/analytics/useLogsPreview'
|
||||
import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilterPanel'
|
||||
|
||||
import { LOGS_TABLES } from './Logs.constants'
|
||||
import { Override } from './Logs.types'
|
||||
|
||||
/**
|
||||
* Acts as a container component for the entire log display
|
||||
@@ -40,16 +41,15 @@ import { LOGS_TABLES } from './Logs.constants'
|
||||
interface Props {
|
||||
projectRef: string
|
||||
queryType: QueryType
|
||||
override: Override
|
||||
}
|
||||
export const LogsPreviewer: React.FC<Props> = ({ projectRef, queryType }) => {
|
||||
export const LogsPreviewer: React.FC<Props> = ({ projectRef, queryType, override }) => {
|
||||
const router = useRouter()
|
||||
const { s, te, ts } = router.query
|
||||
const [showChart, setShowChart] = useState(true)
|
||||
|
||||
const [whereFilters, dispatchWhereFilters] = useReducer(filterReducer, {})
|
||||
|
||||
// const table = queryType === 'api' ? LogsTableName.EDGE : LogsTableName.POSTGRES
|
||||
|
||||
console.log('queryType', queryType)
|
||||
const table = LOGS_TABLES[queryType]
|
||||
|
||||
@@ -63,7 +63,7 @@ export const LogsPreviewer: React.FC<Props> = ({ projectRef, queryType }) => {
|
||||
? `where REGEXP_CONTAINS(event_message, '${filterObj.search_query}')`
|
||||
: ''
|
||||
}
|
||||
${filterSqlWhereBuilder(whereFilters, table).join('\n')}`,
|
||||
${filterSqlWhereBuilder(whereFilters, table, override).join('\n')}`,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -37,13 +37,17 @@ import FunctionLayout from '../interfaces/FunctionLayout'
|
||||
*/
|
||||
export const LogPage: NextPage = () => {
|
||||
const router = useRouter()
|
||||
const { ref, type } = router.query
|
||||
const { ref, type, id } = router.query
|
||||
|
||||
const title = `Logs - ${LOG_TYPE_LABEL_MAPPING[type as keyof typeof LOG_TYPE_LABEL_MAPPING]}`
|
||||
|
||||
return (
|
||||
<FunctionLayout title={title}>
|
||||
<LogsPreviewer projectRef={ref as string} queryType={'fn_edge'} />
|
||||
<LogsPreviewer
|
||||
projectRef={ref as string}
|
||||
queryType={'fn_edge'}
|
||||
override={{ key: 'metadata.function_id', value: id }}
|
||||
/>
|
||||
</FunctionLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import React from 'react'
|
||||
import { NextPage } from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { withAuth } from 'hooks'
|
||||
import { useStore, withAuth } from 'hooks'
|
||||
import { SettingsLayout } from 'components/layouts/'
|
||||
import { LOG_TYPE_LABEL_MAPPING, QueryType } from 'components/interfaces/Settings/Logs'
|
||||
import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer'
|
||||
@@ -37,13 +37,21 @@ import FunctionLayout from '../interfaces/FunctionLayout'
|
||||
*/
|
||||
export const LogPage: NextPage = () => {
|
||||
const router = useRouter()
|
||||
const { ref, type } = router.query
|
||||
const { ref, type, id } = router.query
|
||||
|
||||
const { functions } = useStore()
|
||||
|
||||
console.log('id', id)
|
||||
|
||||
const title = `Logs - ${LOG_TYPE_LABEL_MAPPING[type as keyof typeof LOG_TYPE_LABEL_MAPPING]}`
|
||||
|
||||
return (
|
||||
<FunctionLayout title={title}>
|
||||
<LogsPreviewer projectRef={ref as string} queryType={'functions'} />
|
||||
<LogsPreviewer
|
||||
projectRef={ref as string}
|
||||
queryType={'functions'}
|
||||
override={{ key: 'metadata.function_id', value: id }}
|
||||
/>
|
||||
</FunctionLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { withAuth } from 'hooks'
|
||||
import { useStore, withAuth } from 'hooks'
|
||||
|
||||
import FunctionsLayout from './interfaces/FunctionsLayout'
|
||||
import {
|
||||
@@ -9,11 +9,18 @@ import {
|
||||
IconGlobe,
|
||||
IconSearch,
|
||||
Input,
|
||||
Loading,
|
||||
} from '@supabase/ui'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { memoryUsage } from 'process'
|
||||
import FunctionsListItem from './interfaces/FunctionsListItem'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL, PROJECT_STATUS } from 'lib/constants'
|
||||
import { useProjectFunctionsStore } from 'stores/projectFunctionsStore'
|
||||
import { toJS } from 'mobx'
|
||||
|
||||
const EmptyFunctions = () => {
|
||||
return (
|
||||
@@ -103,54 +110,67 @@ const EmptyFunctions = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const FunctionsList = () => {
|
||||
const PageState = {
|
||||
functions: [
|
||||
{
|
||||
name: 'hello-world',
|
||||
id: 1,
|
||||
runtime: 'Node14',
|
||||
memory: 256,
|
||||
created_at: 1646524256,
|
||||
},
|
||||
{
|
||||
name: 'charge',
|
||||
id: 2,
|
||||
runtime: 'Node14',
|
||||
memory: 256,
|
||||
created_at: 1646524256,
|
||||
},
|
||||
{
|
||||
name: 'projects',
|
||||
id: 3,
|
||||
runtime: 'Node14',
|
||||
memory: 512,
|
||||
created_at: 1646524256,
|
||||
},
|
||||
],
|
||||
}
|
||||
interface Function {
|
||||
id: string
|
||||
slug: string
|
||||
name: string
|
||||
version: 9
|
||||
status: 'ACTIVE' | 'INACTIVE' | 'THROTTLE'
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
const FunctionsList = ({ functions }: { functions: Function[] }) => {
|
||||
// const PageState = {
|
||||
// functions: [
|
||||
// {
|
||||
// name: 'hello-world',
|
||||
// id: 1,
|
||||
// runtime: 'Node14',
|
||||
// memory: 256,
|
||||
// created_at: 1646524256,
|
||||
// },
|
||||
// {
|
||||
// name: 'charge',
|
||||
// id: 2,
|
||||
// runtime: 'Node14',
|
||||
// memory: 256,
|
||||
// created_at: 1646524256,
|
||||
// },
|
||||
// {
|
||||
// name: 'projects',
|
||||
// id: 3,
|
||||
// runtime: 'Node14',
|
||||
// memory: 512,
|
||||
// created_at: 1646524256,
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-scale-900">2 functions deployed</span>
|
||||
<span className="text-sm text-scale-900">{`${functions.length} function${
|
||||
functions.length > 1 ? 's' : ''
|
||||
} deployed`}</span>
|
||||
<Input icon={<IconSearch size={14} />} size="tiny" />
|
||||
</div>
|
||||
<div>
|
||||
<Table
|
||||
head={
|
||||
<>
|
||||
<Table.th>Email</Table.th>
|
||||
<Table.th>Name</Table.th>
|
||||
<Table.th>Trigger</Table.th>
|
||||
<Table.th>Status</Table.th>
|
||||
<Table.th className="hidden 2xl:table-cell">Created</Table.th>
|
||||
<Table.th className="hidden xl:table-cell">Runtime</Table.th>
|
||||
<Table.th className="hidden lg:table-cell">Memory</Table.th>
|
||||
<Table.th className="hidden 2xl:table-cell">Last updated</Table.th>
|
||||
<Table.th className="hidden 2xl:table-cell">Version</Table.th>
|
||||
</>
|
||||
}
|
||||
body={
|
||||
<>
|
||||
{PageState.functions.length > 0 &&
|
||||
PageState.functions.map((x: any) => <FunctionsListItem key={x.id} _function={x} />)}
|
||||
{functions.length > 0 &&
|
||||
functions.map((item: any) => <FunctionsListItem key={item.id} function={item} />)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@@ -160,8 +180,23 @@ const FunctionsList = () => {
|
||||
}
|
||||
|
||||
const PageLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ui, functions } = useStore()
|
||||
|
||||
const [pageState, setPageState] = useState('list')
|
||||
|
||||
const project = ui.selectedProject
|
||||
|
||||
useEffect(() => {
|
||||
if (project && project.status === PROJECT_STATUS.INACTIVE) {
|
||||
post(`${API_URL}/projects/${ui.selectedProject}/restore`, {})
|
||||
}
|
||||
}, [project])
|
||||
|
||||
useEffect(() => {
|
||||
functions.load()
|
||||
}, [ui.selectedProject])
|
||||
|
||||
const Selection = () => (
|
||||
<div>
|
||||
<Button onClick={() => setPageState('empty')}>empty list</Button>
|
||||
@@ -169,6 +204,13 @@ const PageLayout = () => {
|
||||
</div>
|
||||
)
|
||||
|
||||
if (functions.isLoading)
|
||||
return (
|
||||
<FunctionsLayout>
|
||||
<Loading active={true}>loading</Loading>
|
||||
</FunctionsLayout>
|
||||
)
|
||||
|
||||
if (pageState === 'empty') {
|
||||
return (
|
||||
<FunctionsLayout>
|
||||
@@ -179,7 +221,7 @@ const PageLayout = () => {
|
||||
} else {
|
||||
return (
|
||||
<FunctionsLayout>
|
||||
<FunctionsList />
|
||||
<FunctionsList functions={functions.list()} />
|
||||
<Selection />
|
||||
</FunctionsLayout>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
import BaseLayout from 'components/layouts'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useStore, withAuth } from 'hooks'
|
||||
import { IconCode, IconSlash, Tabs } from '@supabase/ui'
|
||||
import { IconCode, IconSlash, Loading, Tabs } from '@supabase/ui'
|
||||
import { Tab } from '@headlessui/react'
|
||||
import FunctionsNav from './FunctionsNav'
|
||||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const PageLayout = ({ children }: { children?: React.ReactNode }) => {
|
||||
const { functions, ui } = useStore()
|
||||
|
||||
const router = useRouter()
|
||||
const { id } = router.query
|
||||
|
||||
useEffect(() => {
|
||||
functions.load()
|
||||
}, [ui.selectedProject])
|
||||
|
||||
if (!functions.isInitialized) {
|
||||
return (
|
||||
<BaseLayout>
|
||||
<Loading active={true}>loading</Loading>
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
const item = functions.byId(id)
|
||||
|
||||
const name = item?.name || ''
|
||||
|
||||
console.log('item', item)
|
||||
|
||||
return (
|
||||
<BaseLayout>
|
||||
<div className="h-full flex flex-col flex-grow py-10">
|
||||
@@ -51,9 +75,9 @@ const PageLayout = ({ children }: { children?: React.ReactNode }) => {
|
||||
<path d="M16 3.549L7.12 20.600"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<h5 className="text-2xl text-scale-1200">hello-world</h5>
|
||||
<h5 className="text-2xl text-scale-1200">{name}</h5>
|
||||
</div>
|
||||
<FunctionsNav />
|
||||
<FunctionsNav item={item} />
|
||||
</div>
|
||||
<div
|
||||
// weird repetative styling
|
||||
|
||||
@@ -13,28 +13,32 @@ import Table from 'components/to-be-cleaned/Table'
|
||||
|
||||
import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
|
||||
import router, { useRouter } from 'next/router'
|
||||
import { useStore } from 'hooks'
|
||||
// import _functionDropdown from './_functionDropdown'
|
||||
|
||||
interface Props {
|
||||
_function: any
|
||||
function: any
|
||||
}
|
||||
|
||||
const FunctionsListItem: FC<Props> = ({ _function }) => {
|
||||
const is_functionConfirmed = _function.email_confirmed_at || _function.phone_confirmed_at
|
||||
const FunctionsListItem: FC<Props> = ({ function: item }) => {
|
||||
const { ui } = useStore()
|
||||
|
||||
console.log('_function', item)
|
||||
const is_functionConfirmed = item.email_confirmed_at || item.phone_confirmed_at
|
||||
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
const ref = ui?.selectedProject?.ref
|
||||
|
||||
return (
|
||||
<Table.tr
|
||||
key={_function.id}
|
||||
key={item.id}
|
||||
onClick={() => {
|
||||
router.push(`/project/${ref}/functions/hello-world`)
|
||||
router.push(`/project/${ref}/functions/${item.id}`)
|
||||
}}
|
||||
>
|
||||
<Table.td className="whitespace-nowrap">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-base text-scale-1200">{_function.name}</span>
|
||||
<span className="text-base text-scale-1200">{item.name}</span>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td className="whitespace-nowrap flex flex-col gap-0">
|
||||
@@ -46,20 +50,25 @@ const FunctionsListItem: FC<Props> = ({ _function }) => {
|
||||
className="text-xs text-scale-900 flex gap-2 items-center
|
||||
font-mono"
|
||||
>
|
||||
https://adlnssnsfsfsfdf.supabase.com/functions/{_function.name}
|
||||
{`https://${ref}.supabase.com/functions/${item.name}`}
|
||||
<IconClipboard size={12} />
|
||||
</span>
|
||||
</Table.td>
|
||||
<Table.td className="hidden 2xl:table-cell">
|
||||
<span className="text-scale-1100">{item.status}</span>
|
||||
</Table.td>
|
||||
<Table.td className="hidden 2xl:table-cell">
|
||||
<span className="text-scale-1100">
|
||||
{dayjs(_function.created_at).format('DD MMM, YYYY HH:mm')}
|
||||
{dayjs(item.created_at).format('DD MMM, YYYY HH:mm')}
|
||||
</span>
|
||||
</Table.td>
|
||||
<Table.td className="hidden 2xl:table-cell">
|
||||
<span className="text-scale-1100">{_function.runtime}</span>
|
||||
<span className="text-scale-1100">
|
||||
{dayjs(item.updated_at).format('DD MMM, YYYY HH:mm')}
|
||||
</span>
|
||||
</Table.td>
|
||||
<Table.td className="hidden 2xl:table-cell">
|
||||
<span className="text-scale-1100">{_function.memory} MB</span>
|
||||
<span className="text-scale-1100">Version {item.version}</span>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Tabs } from '@supabase/ui'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const FunctionsNav = () => {
|
||||
const FunctionsNav = ({ item }) => {
|
||||
const router = useRouter()
|
||||
const activeRoute = router.pathname.split('/')[5]
|
||||
const { ref } = router.query
|
||||
@@ -14,7 +14,7 @@ const FunctionsNav = () => {
|
||||
size="medium"
|
||||
activeId={!activeRoute ? 'metrics' : activeRoute}
|
||||
onChange={(e: string) =>
|
||||
router.push(`/project/${ref}/functions/hello-world/${e === 'metrics' ? '' : e}`)
|
||||
router.push(`/project/${ref}/functions/${item.id}/${e === 'metrics' ? '' : e}`)
|
||||
}
|
||||
>
|
||||
<Tabs.Panel id="metrics" label="Metrics" />
|
||||
|
||||
@@ -3,6 +3,7 @@ import AppStore, { IAppStore } from './app/AppStore'
|
||||
import MetaStore, { IMetaStore } from './pgmeta/MetaStore'
|
||||
import UiStore, { IUiStore } from './UiStore'
|
||||
import ProjectContentStore, { IProjectContentStore } from './content/ProjectContentStore'
|
||||
import ProjectFunctionsStore, { IProjectFunctionsStore } from './functions/ProjectFunctionsStore'
|
||||
|
||||
// Temporary disable mobx warnings
|
||||
// TODO: need to remove this after refactoring old stores.
|
||||
@@ -13,6 +14,7 @@ configure({
|
||||
export interface IRootStore {
|
||||
ui: IUiStore
|
||||
content: IProjectContentStore
|
||||
functions: IProjectFunctionsStore
|
||||
meta: IMetaStore
|
||||
app: IAppStore
|
||||
setProjectRef: (value?: string) => void
|
||||
@@ -21,12 +23,14 @@ export interface IRootStore {
|
||||
export class RootStore implements IRootStore {
|
||||
ui: IUiStore
|
||||
content: IProjectContentStore
|
||||
functions: IProjectFunctionsStore
|
||||
meta: IMetaStore
|
||||
app: IAppStore
|
||||
|
||||
constructor() {
|
||||
this.ui = new UiStore(this)
|
||||
this.content = new ProjectContentStore(this, { projectRef: '' })
|
||||
this.functions = new ProjectFunctionsStore(this, { projectRef: '' })
|
||||
this.meta = new MetaStore(this, {
|
||||
projectRef: '',
|
||||
connectionString: '',
|
||||
@@ -46,12 +50,14 @@ export class RootStore implements IRootStore {
|
||||
(selectedProject) => {
|
||||
if (selectedProject) {
|
||||
this.content = new ProjectContentStore(this, { projectRef: selectedProject.ref })
|
||||
this.functions = new ProjectFunctionsStore(this, { projectRef: selectedProject.ref })
|
||||
this.meta = new MetaStore(this, {
|
||||
projectRef: selectedProject.ref,
|
||||
connectionString: selectedProject.connectionString ?? '',
|
||||
})
|
||||
} else {
|
||||
this.content = new ProjectContentStore(this, { projectRef: '' })
|
||||
this.functions = new ProjectFunctionsStore(this, { projectRef: '' })
|
||||
this.meta = new MetaStore(this, {
|
||||
projectRef: '',
|
||||
connectionString: '',
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import { makeAutoObservable, toJS } from 'mobx'
|
||||
import { keyBy } from 'lodash'
|
||||
|
||||
import { get, post, patch, delete_ } from 'lib/common/fetch'
|
||||
import { UserContent, UserContentMap } from 'types'
|
||||
import { IRootStore } from '../RootStore'
|
||||
|
||||
// [Joshen] This will be the new ProjectContentStore
|
||||
// but use the one under the stores folder projectContentStore first while we transition
|
||||
|
||||
export interface IProjectFunctionsStore {
|
||||
isLoading: boolean
|
||||
isInitialized: boolean
|
||||
error: any
|
||||
|
||||
load: () => void
|
||||
list: (filter?: any) => any[]
|
||||
byId: (id?: any) => []
|
||||
reports: (filter?: any) => any[]
|
||||
sqlSnippets: (filter?: any) => any[]
|
||||
}
|
||||
|
||||
export default class ProjectFunctionsStore implements IProjectFunctionsStore {
|
||||
rootStore: IRootStore
|
||||
|
||||
STATES = {
|
||||
INITIAL: 'initial',
|
||||
LOADING: 'loading',
|
||||
ERROR: 'error',
|
||||
LOADED: 'loaded',
|
||||
}
|
||||
|
||||
baseUrl: string
|
||||
data: UserContentMap = []
|
||||
|
||||
state = this.STATES.INITIAL
|
||||
error = null
|
||||
|
||||
constructor(rootStore: IRootStore, options: { projectRef: string }) {
|
||||
const { projectRef } = options
|
||||
this.rootStore = rootStore
|
||||
this.baseUrl = `${process.env.NEXT_PUBLIC_API_ADMIN_URL}/projects/${projectRef}/functions`
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
get isLoading() {
|
||||
return this.state === this.STATES.INITIAL || this.state === this.STATES.LOADING
|
||||
}
|
||||
|
||||
get isInitialized() {
|
||||
return this.state !== this.STATES.INITIAL
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
const response = await get(this.baseUrl, { headers })
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
this.data = keyBy(response, 'id')
|
||||
return response
|
||||
}
|
||||
|
||||
async load() {
|
||||
let { LOADING, ERROR, LOADED } = this.STATES
|
||||
try {
|
||||
this.error = null
|
||||
this.state = LOADING
|
||||
await this.fetchData()
|
||||
this.state = LOADED
|
||||
return this.data
|
||||
} catch (e: any) {
|
||||
console.error('Failed to load project content', e.message)
|
||||
this.error = e
|
||||
this.state = ERROR
|
||||
}
|
||||
}
|
||||
|
||||
async loadPersistentData() {}
|
||||
|
||||
async loadRemotePersistentData(userId: any) {
|
||||
const sqlSnippets = this.sqlSnippets((x: any) => x.owner_id === userId)
|
||||
}
|
||||
|
||||
list(filter?: any) {
|
||||
const arr = Object.values(this.data)
|
||||
if (!!filter) {
|
||||
return arr.filter(filter).sort((a, b) => a.name.localeCompare(b.name))
|
||||
} else {
|
||||
return arr.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
}
|
||||
|
||||
byId(id: any) {
|
||||
// console.log('this.data', toJS(this.data))
|
||||
// return toJS(this.data).find((x: any) => x.id === id)
|
||||
return toJS(this.data)[id]
|
||||
}
|
||||
|
||||
reports(filter?: any) {
|
||||
let arr = Object.values(this.data)
|
||||
if (!!filter) {
|
||||
return arr
|
||||
.filter((x) => x.type == 'report')
|
||||
.filter(filter)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
} else {
|
||||
return arr.filter((x) => x.type == 'report').sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
}
|
||||
|
||||
sqlSnippets(filter?: any) {
|
||||
let arr = Object.values(this.data)
|
||||
if (!!filter) {
|
||||
const arr_filtered = arr
|
||||
.filter((x) => x.type == 'sql')
|
||||
.filter(filter)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
return arr_filtered
|
||||
} else {
|
||||
const arr_filtered = arr
|
||||
.filter((x) => x.type == 'sql')
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
return arr_filtered
|
||||
}
|
||||
}
|
||||
|
||||
async create(payload: any) {
|
||||
try {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
const created = await post(this.baseUrl, payload, { headers })
|
||||
if (created.error) throw created.error
|
||||
this.data[created['id']] = created
|
||||
return { data: created, error: null }
|
||||
} catch (error) {
|
||||
return { data: null, error }
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: any, updates: any, type: UserContent['type']) {
|
||||
try {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
let payload = {
|
||||
...updates,
|
||||
type,
|
||||
id,
|
||||
}
|
||||
const url = `${this.baseUrl}?id=${id}`
|
||||
const updated = await patch(url, payload, { headers })
|
||||
if (updated.error) throw updated.error
|
||||
this.data[id] = updated
|
||||
return { data: updated, error: null }
|
||||
} catch (error) {
|
||||
return { data: null, error }
|
||||
}
|
||||
}
|
||||
|
||||
async updateSql(id: any, updates: any) {
|
||||
return this.update(id, updates, 'sql')
|
||||
}
|
||||
|
||||
async updateReport(id: any, updates: any) {
|
||||
return this.update(id, updates, 'report')
|
||||
}
|
||||
|
||||
async del(id: any) {
|
||||
try {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
const url = `${this.baseUrl}?id=${id}`
|
||||
const deleted = await delete_(url, {}, { headers })
|
||||
if (deleted.error) throw deleted.error
|
||||
delete this.data[id]
|
||||
return { data: true, error: null }
|
||||
} catch (error) {
|
||||
return { data: false, error }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user