Skip to content

Commit

Permalink
wip: better useFetcher
Browse files Browse the repository at this point in the history
  • Loading branch information
DimaAmega committed Dec 25, 2024
1 parent 367ce69 commit f06344f
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 96 deletions.
43 changes: 36 additions & 7 deletions ui/packages/evidently-ui-lib/src/router-utils/components/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
type TypographyProps
} from '@mui/material'
import { Link as ReactRouterLink } from 'react-router-dom'
import type { GetParams, MatchAny } from 'router-utils/types'
import { replaceParamsInLink } from 'router-utils/utils'

export type RouterLinkTemplateComponentProps =
| ({
Expand All @@ -20,7 +22,7 @@ export type RouterLinkTemplateComponentProps =
type: 'tab'
} & RLT)

export const RouterLinkTemplate = (props: RouterLinkTemplateComponentProps) => {
const RouterLinkTemplate = (props: RouterLinkTemplateComponentProps) => {
return props.type === 'button' ? (
<RLBComponent {...props} />
) : props.type === 'tab' ? (
Expand All @@ -30,26 +32,26 @@ export const RouterLinkTemplate = (props: RouterLinkTemplateComponentProps) => {
)
}

export type RLB = {
type RLB = {
to: string
title?: string
} & ButtonProps

export const RLBComponent = ({ to, title, ...buttonProps }: RLB) => {
const RLBComponent = ({ to, title, ...buttonProps }: RLB) => {
return (
<Button component={ReactRouterLink} to={to} {...buttonProps}>
{title}
</Button>
)
}

export type RLL = {
type RLL = {
children?: React.ReactNode
to: string
title?: string
} & TypographyProps

export const RLLComponent = ({ children, to, title, ...typographyProps }: RLL) => {
const RLLComponent = ({ children, to, title, ...typographyProps }: RLL) => {
return (
<Link component={ReactRouterLink} to={to}>
<>
Expand All @@ -60,10 +62,37 @@ export const RLLComponent = ({ children, to, title, ...typographyProps }: RLL) =
)
}

export type RLT = {
type RLT = {
to: string
} & TabProps

export const RLTComponent = ({ to, ...tabProps }: RLT) => {
const RLTComponent = ({ to, ...tabProps }: RLT) => {
return <Tab component={ReactRouterLink} to={to} {...tabProps} />
}

export const CreateRouterLinkComponent = <M extends MatchAny>() => {
const Component = <K extends M['path']>({
to,
paramsToReplace,
query,
...props
}: RouterLinkTemplateComponentProps & {
to: K
paramsToReplace: GetParams<K>
query?: Extract<M, { path: K }>['loader']['query']
}) => {
const searchParams =
query &&
new URLSearchParams(
Object.fromEntries(Object.entries(query).filter(([_, v]) => v)) as Record<string, string>
)

const toActual = [replaceParamsInLink(paramsToReplace, to), searchParams]
.filter(Boolean)
.join('?')

return <RouterLinkTemplate {...props} to={toActual} />
}

return Component
}
40 changes: 23 additions & 17 deletions ui/packages/evidently-ui-lib/src/router-utils/fetchers.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import { useCallback, useMemo } from 'react'
import type { GetParams, MatchWithAction } from '~/router-utils/types'
import type { GetParams, MatchAny, MatchWithAction } from '~/router-utils/types'
import { useFetcher } from '~/shared-dependencies/react-router-dom'
import { REST_PARAMS_FOR_FETCHER_SUBMIT } from '~/utils/index'
import { replaceParamsInLink } from './utils'

export const useSubmitFetcherGeneral = <M extends MatchWithAction, K extends keyof M['action']>({
actionPath,
action
action,
path,
provideParams
}: {
action: K
actionPath: ({ data }: { data: M['action'][K]['requestData'] }) => {
path: M['path']
params: GetParams<M['path']>
}
path: M['path']
provideParams: ({ data }: { data: M['action'][K]['requestData'] }) => GetParams<M['path']>
}) => {
const originalFetcher = useFetcher<M['action'][K]['returnType']>()

// biome-ignore lint/correctness/useExhaustiveDependencies: fine
const submit = useCallback(
(data: M['action'][K]['requestData']) => {
const { params, path } = actionPath({ data })
const params = provideParams({ data })

originalFetcher.submit(
// @ts-ignore
Expand All @@ -30,7 +29,7 @@ export const useSubmitFetcherGeneral = <M extends MatchWithAction, K extends key
}
)
},
[originalFetcher]
[originalFetcher, path]
)

const fetcher = useMemo(
Expand All @@ -42,14 +41,21 @@ export const useSubmitFetcherGeneral = <M extends MatchWithAction, K extends key
}

// Helper function to infer K
export const createUseSubmitFetcherGeneral = <M extends MatchWithAction>() => {
const hook = <K extends keyof M['action']>(args: {
action: K
actionPath: ({ data }: { data: M['action'][K]['requestData'] }) => {
path: M['path']
params: GetParams<M['path']>
}
}) => useSubmitFetcherGeneral<M, K>(args)
export const createUseSubmitFetcherGeneral = <M extends MatchAny>() => {
type MatchesWithAction = Extract<M, MatchWithAction>

const hook = <
Path extends MatchesWithAction['path'],
ActionName extends keyof Extract<MatchesWithAction, { path: Path }>['action']
>(args: {
action: ActionName
path: Path
provideParams: ({
data
}: {
data: Extract<MatchesWithAction, { path: Path }>['action'][ActionName]['requestData']
}) => GetParams<Extract<MatchesWithAction, { path: Path }>['path']>
}) => useSubmitFetcherGeneral<Extract<MatchesWithAction, { path: Path }>, ActionName>(args)

return hook
}
11 changes: 10 additions & 1 deletion ui/packages/evidently-ui-lib/src/router-utils/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import {
type NavigateOptions,
useFetchers,
useLoaderData,
useMatch,
useMatches,
useNavigation,
useParams,
useRevalidator,
useSearchParams
} from 'react-router-dom'
import type { MatchWithLoader } from '~/router-utils/types'
import type { MatchAny, MatchWithLoader } from '~/router-utils/types'
import type { CrumbDefinition, HandleWithCrumb } from '~/router-utils/utils'

export const useCurrentRouteParams = <
Expand Down Expand Up @@ -121,3 +122,11 @@ export const useCrumbsFromHandle = () => {

return { crumbs }
}

export const createUseMatchRouter = <M extends MatchAny>() => {
const hook = <K extends M['path']>({ path }: { path: K }) => {
return Boolean(useMatch({ path, end: false }))
}

return hook
}
12 changes: 10 additions & 2 deletions ui/packages/evidently-ui-lib/src/router-utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ type ExtractAction<T extends RouteExtended> = T['actions'] extends Record<
T['lazy'] extends (args: any) => Promise<infer R>
? R extends RouteExtended
? ExtractAction<R>
: ProvideActionInfo<undefined, undefined>
: ProvideActionInfo<undefined, undefined>
: undefined
: undefined

type IsIndex<T extends RouteExtended> = T['index'] extends true
? true
Expand Down Expand Up @@ -130,6 +130,14 @@ export type Match<Path extends string, L, A> = {
params: { [Z in ExtractParams<Path>]: string }
}

export type MatchAny = Match<
string,
// biome-ignore lint/suspicious/noExplicitAny: fine
ProvideLoaderInfo<Partial<Record<string, string>>, any>,
// biome-ignore lint/suspicious/noExplicitAny: fine
Record<string, ProvideActionInfo<any, any>> | undefined
>

export type MatchWithAction = Match<
string,
// biome-ignore lint/suspicious/noExplicitAny: fine
Expand Down
44 changes: 5 additions & 39 deletions ui/service/src/routes/components.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,7 @@
import {
RouterLinkTemplate,
type RouterLinkTemplateComponentProps
} from 'evidently-ui-lib/router-utils/components/link'
import { CreateRouterLinkComponent } from 'evidently-ui-lib/router-utils/components/link'
import { createUseMatchRouter } from 'evidently-ui-lib/router-utils/hooks'
import type { Routes } from 'routes/types'

import type { GetParams } from 'evidently-ui-lib/router-utils/types'
import { useMatch } from 'evidently-ui-lib/shared-dependencies/react-router-dom'
import type { GetRouteByPath, Routes } from './types'
export const RouterLink = CreateRouterLinkComponent<Routes>()

import { replaceParamsInLink } from 'evidently-ui-lib/router-utils/utils'

type Paths = Routes['path']

type RouterLinkProps<K extends Paths> = RouterLinkTemplateComponentProps & {
to: K
paramsToReplace: GetParams<K>
query?: GetRouteByPath<K>['loader']['query']
}

export const RouterLink = <K extends Paths>({
to,
paramsToReplace,
query,
...props
}: RouterLinkProps<K>) => {
const searchParams =
query &&
new URLSearchParams(
Object.fromEntries(Object.entries(query).filter(([_, v]) => v)) as Record<string, string>
)

const toActual = [replaceParamsInLink(paramsToReplace, to), searchParams]
.filter(Boolean)
.join('?')

return <RouterLinkTemplate {...props} to={toActual} />
}

export const useMatchRouter = <K extends Paths>({ path }: { path: K }) => {
return Boolean(useMatch({ path, end: false }))
}
export const useMatchRouter = createUseMatchRouter<Routes>()
4 changes: 4 additions & 0 deletions ui/service/src/routes/hooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createUseSubmitFetcherGeneral } from 'evidently-ui-lib/router-utils/fetchers'
import type { Routes } from 'routes/types'

export const useSubmitFetcher = createUseSubmitFetcherGeneral<Routes>()
1 change: 1 addition & 0 deletions ui/service/src/routes/src/project/project-main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const TABS = {

export const Component = () => {
const { loaderData: project, params } = useCurrentRouteParams<CurrentRoute>()

const isReports = useMatchRouter({ path: '/:projectId/reports' })
const isTestSuites = useMatchRouter({ path: '/:projectId/test-suites' })

Expand Down
26 changes: 13 additions & 13 deletions ui/service/src/routes/src/projects-list/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@ import type { StrictID } from 'evidently-ui-lib/api/types/utils'
import { AddNewProjectButton, ProjectCard } from 'evidently-ui-lib/components/ProjectCard'
import { useState } from 'react'

import { createUseSubmitFetcherGeneral } from 'evidently-ui-lib/router-utils/fetchers'
import { useOnSubmitEnd } from 'evidently-ui-lib/router-utils/hooks'
import { RouterLink } from '~/routes/components'
import type { CurrentRoute } from './projects-list-main'

const useFetcher = createUseSubmitFetcherGeneral<CurrentRoute>()
import { useSubmitFetcher } from '~/routes/hooks'

export const ProjectCardWrapper = ({ project }: { project: StrictID<ProjectModel> }) => {
const [mode, setMode] = useState<'edit' | 'view'>('view')

const deleteProjectFetcher = useFetcher({
actionPath: () => ({ path: '/?index', params: {} }),
action: 'delete-project'
const deleteProjectFetcher = useSubmitFetcher({
path: '/?index',
action: 'delete-project',
provideParams: () => ({})
})

useOnSubmitEnd({
Expand All @@ -27,9 +25,10 @@ export const ProjectCardWrapper = ({ project }: { project: StrictID<ProjectModel
}
})

const editProjectFetcher = useFetcher({
actionPath: () => ({ path: '/?index', params: {} }),
action: 'edit-project'
const editProjectFetcher = useSubmitFetcher({
path: '/?index',
action: 'edit-project',
provideParams: () => ({})
})

useOnSubmitEnd({
Expand Down Expand Up @@ -67,9 +66,10 @@ const LinkToProject = ({ name, projectId }: { name: string; projectId: string })
)

export const AddNewProjectWrapper = () => {
const createProjectFetcher = useFetcher({
actionPath: () => ({ path: '/?index', params: {} }),
action: 'create-project'
const createProjectFetcher = useSubmitFetcher({
path: '/?index',
action: 'create-project',
provideParams: () => ({})
})

const [opened, setOpened] = useState<boolean>(false)
Expand Down
19 changes: 10 additions & 9 deletions ui/service/src/routes/src/reports-list/reports-list-main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import type { GetRouteByPath } from '~/routes/types'

import { clientAPI } from '~/api'

import { createUseSubmitFetcherGeneral } from 'evidently-ui-lib/router-utils/fetchers'
import {
useCurrentRouteParams,
useIsAnyLoaderOrActionRunning
} from 'evidently-ui-lib/router-utils/hooks'
import { SnapshotsListTemplate } from 'evidently-ui-lib/routes-components/snapshots'
import { getReports, getSnapshotsActions } from 'evidently-ui-lib/routes-components/snapshots/data'
import { RouterLink } from '~/routes/components'
import { useSubmitFetcher } from '~/routes/hooks'

///////////////////
// ROUTE
Expand All @@ -31,21 +31,22 @@ export const loadData = ({ params }: loadDataArgs) => {

export const actions = getSnapshotsActions({ api: clientAPI })

const useFetcher = createUseSubmitFetcherGeneral<CurrentRoute>()

// type OOO = GetRouteByPath<'/:projectId/reports/:snapshotId'>['']
export const Component = () => {
const { loaderData: reports, params } = useCurrentRouteParams<CurrentRoute>()

const { projectId } = params

const deleteSnapshotFetcher = useFetcher({
actionPath: () => ({ path: '/:projectId/reports/?index', params: { projectId } }),
action: 'delete-snapshot'
const deleteSnapshotFetcher = useSubmitFetcher({
path: '/:projectId/reports/?index',
action: 'delete-snapshot',
provideParams: () => ({ projectId })
})

const reloadSnapshotsFetcher = useFetcher({
actionPath: () => ({ path: '/:projectId/reports/?index', params: { projectId } }),
action: 'reload-snapshots'
const reloadSnapshotsFetcher = useSubmitFetcher({
path: '/:projectId/reports/?index',
action: 'reload-snapshots',
provideParams: () => ({ projectId })
})

const disabled = useIsAnyLoaderOrActionRunning()
Expand Down
Loading

0 comments on commit f06344f

Please sign in to comment.