From 77ece34d25baf7f119a6f6d93cfe18e8b79cbec6 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 13 Mar 2025 10:11:57 -0600 Subject: [PATCH] start moving to hook based updates --- web/src/api/buster_rest/metrics/interfaces.ts | 2 + .../api/buster_rest/metrics/queryRequests.ts | 64 +++++++++++++++---- .../ShareMenu/ShareMenuContentBody.tsx | 4 +- .../ShareMenu/ShareWithTeamAndGroup.tsx | 4 +- .../ui/charts/TableChart/BusterTableChart.tsx | 2 +- web/src/context/AppProviders.tsx | 2 +- .../context/Metrics/BusterMetricProvider.tsx | 39 ++--------- ...ePosthog.tsx => BusterPosthogProvider.tsx} | 0 web/src/context/Posthog/index.ts | 1 + .../MetricViewChart/MetricViewChart.tsx | 8 +-- .../MetricViewFile/MetricViewFile.tsx | 10 +-- .../useMetricRunSQL/useMetricRunSQL.ts | 19 ++---- .../MetricItemsSelectedPopup.tsx | 14 +++- 13 files changed, 93 insertions(+), 76 deletions(-) rename web/src/context/Posthog/{usePosthog.tsx => BusterPosthogProvider.tsx} (100%) diff --git a/web/src/api/buster_rest/metrics/interfaces.ts b/web/src/api/buster_rest/metrics/interfaces.ts index 9ca04fa7b..93c158426 100644 --- a/web/src/api/buster_rest/metrics/interfaces.ts +++ b/web/src/api/buster_rest/metrics/interfaces.ts @@ -41,4 +41,6 @@ export type UpdateMetricParams = { feedback?: 'negative'; /** Admin only: verification status update */ status?: VerificationStatus; + /** file in yaml format to update */ + file?: string; } & ShareRequest; diff --git a/web/src/api/buster_rest/metrics/queryRequests.ts b/web/src/api/buster_rest/metrics/queryRequests.ts index 5f652ce20..b324d5b50 100644 --- a/web/src/api/buster_rest/metrics/queryRequests.ts +++ b/web/src/api/buster_rest/metrics/queryRequests.ts @@ -10,7 +10,7 @@ import { listMetrics_server, updateMetric } from './requests'; -import type { GetMetricParams, ListMetricsParams } from './interfaces'; +import type { GetMetricParams, ListMetricsParams, UpdateMetricParams } from './interfaces'; import { upgradeMetricToIMetric } from '@/lib/chat'; import { queryKeys } from '@/api/query_keys'; import { useMemo } from 'react'; @@ -19,6 +19,8 @@ import { resolveEmptyMetric } from '@/lib/metrics/resolve'; import { useGetUserFavorites } from '../users'; import { useBusterNotifications } from '@/context/BusterNotifications'; import type { IBusterMetric } from '@/api/asset_interfaces/metric'; +import { create } from 'mutative'; +import { prepareMetricUpdateMetric } from '@/lib/metrics/saveToServerHelpers'; export const useGetMetric = ( id: string | undefined, @@ -119,11 +121,15 @@ export const prefetchGetMetricDataClient = async ( }); }; -export const useUpdateMetric = () => { +/** + * This is a mutation that saves a metric to the server. + * It will simply use the params passed in and not do any special logic. + */ +export const useSaveMetric = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: updateMetric, - onSuccess: (data, variables, context) => { + onSuccess: (data) => { const hasDraftSessionId = data.draft_session_id; const metricId = data.id; const options = queryKeys.metricsGetMetric(metricId); @@ -138,6 +144,38 @@ export const useUpdateMetric = () => { }); }; +/** + * This is a mutation that updates a metric. + * It will create a new metric with the new values combined with the old values and save it to the server. + * It will also strip out any values that are not changed from the DEFAULT_CHART_CONFIG. + * It will also update the draft_session_id if it exists. + */ +export const useUpdateMetric = () => { + const queryClient = useQueryClient(); + const { mutateAsync: saveMetric } = useSaveMetric(); + const mutationFn = useMemoizedFn( + async (newMetricPartial: Partial & { id: string }) => { + const metricId = newMetricPartial.id; + const options = queryKeys.metricsGetMetric(metricId); + const prevMetric = queryClient.getQueryData(options.queryKey); + const newMetric = create(prevMetric, (draft) => { + Object.assign(draft || {}, newMetricPartial); + }); + + if (prevMetric && newMetric) { + const changedValues = prepareMetricUpdateMetric(newMetric, prevMetric); + if (changedValues) { + return saveMetric(changedValues); + } + } + return Promise.resolve(newMetric!); + } + ); + return useMutation({ + mutationFn + }); +}; + export const useDeleteMetric = () => { const queryClient = useQueryClient(); return useMutation({ @@ -202,8 +240,8 @@ export const useMetricIndividual = ({ metricId }: { metricId: string }) => { }; export const useSaveMetricToCollection = () => { - const { mutateAsync: updateMetricMutation } = useUpdateMetric(); const { data: userFavorites, refetch: refreshFavoritesList } = useGetUserFavorites(); + const { mutateAsync: saveMetric } = useSaveMetric(); const saveMetricToCollection = useMemoizedFn( async ({ metricId, collectionIds }: { metricId: string; collectionIds: string[] }) => { @@ -212,7 +250,7 @@ export const useSaveMetricToCollection = () => { return collectionIds.includes(searchId); }); - await updateMetricMutation({ + await saveMetric({ id: metricId, add_to_collections: collectionIds }); @@ -230,8 +268,8 @@ export const useSaveMetricToCollection = () => { }; export const useRemoveMetricFromCollection = () => { - const { mutateAsync: updateMetricMutation } = useUpdateMetric(); const { data: userFavorites, refetch: refreshFavoritesList } = useGetUserFavorites(); + const { mutateAsync: saveMetric } = useSaveMetric(); const queryClient = useQueryClient(); const removeMetricFromCollection = useMemoizedFn( @@ -244,7 +282,7 @@ export const useRemoveMetricFromCollection = () => { return currentMetric.collections.some((c) => c.id === searchId); }); - await updateMetricMutation({ + await saveMetric({ id: metricId, remove_from_collections: [collectionId] }); @@ -262,11 +300,11 @@ export const useRemoveMetricFromCollection = () => { export const useSaveMetricToDashboard = () => { const queryClient = useQueryClient(); - const { mutateAsync: updateMetricMutation } = useUpdateMetric(); + const { mutateAsync: saveMetric } = useSaveMetric(); const saveMetricToDashboard = useMemoizedFn( async ({ metricId, dashboardIds }: { metricId: string; dashboardIds: string[] }) => { - await updateMetricMutation({ + await saveMetric({ id: metricId, save_to_dashboard: dashboardIds }); @@ -275,7 +313,7 @@ export const useSaveMetricToDashboard = () => { return useMutation({ mutationFn: saveMetricToDashboard, - onSuccess: (data, variables, context) => { + onSuccess: (data, variables) => { queryClient.invalidateQueries({ queryKey: variables.dashboardIds.map((id) => queryKeys.dashboardGetDashboard(id).queryKey) }); @@ -284,10 +322,8 @@ export const useSaveMetricToDashboard = () => { }; export const useRemoveMetricFromDashboard = () => { - const queryClient = useQueryClient(); const { openConfirmModal } = useBusterNotifications(); - const { mutateAsync: updateMetricMutation } = useUpdateMetric(); - + const { mutateAsync: saveMetric } = useSaveMetric(); const removeMetricFromDashboard = useMemoizedFn( async ({ metricId, @@ -299,7 +335,7 @@ export const useRemoveMetricFromDashboard = () => { useConfirmModal?: boolean; }) => { const method = async () => { - await updateMetricMutation({ + await saveMetric({ id: metricId, remove_from_dashboard: [dashboardId] }); diff --git a/web/src/components/features/ShareMenu/ShareMenuContentBody.tsx b/web/src/components/features/ShareMenu/ShareMenuContentBody.tsx index 060a19e38..91a8c5f57 100644 --- a/web/src/components/features/ShareMenu/ShareMenuContentBody.tsx +++ b/web/src/components/features/ShareMenu/ShareMenuContentBody.tsx @@ -17,7 +17,7 @@ import { UserGroup, ChevronRight } from '@/components/ui/icons'; import { cn } from '@/lib/classMerge'; import type { ShareRequest } from '@/api/asset_interfaces/shared_interfaces'; import { useUpdateCollection } from '@/api/buster_rest/collections'; -import { useUpdateMetric } from '@/api/buster_rest/metrics'; +import { useSaveMetric, useUpdateMetric } from '@/api/buster_rest/metrics'; import { useUpdateDashboard } from '@/api/buster_rest/dashboards'; export const ShareMenuContentBody: React.FC<{ @@ -77,7 +77,7 @@ const ShareMenuContentShare: React.FC<{ }> = React.memo(({ setOpenShareWithGroupAndTeam, assetType, individual_permissions, assetId }) => { const userTeams = useUserConfigContextSelector((state) => state.userTeams); const { mutateAsync: onShareDashboard } = useUpdateDashboard(); - const { mutateAsync: onShareMetric } = useUpdateMetric(); + const { mutateAsync: onShareMetric } = useSaveMetric(); const { mutateAsync: onShareCollection } = useUpdateCollection(); const [inputValue, setInputValue] = React.useState(''); const [isInviting, setIsInviting] = React.useState(false); diff --git a/web/src/components/features/ShareMenu/ShareWithTeamAndGroup.tsx b/web/src/components/features/ShareMenu/ShareWithTeamAndGroup.tsx index c4cd94342..fdf2b2394 100644 --- a/web/src/components/features/ShareMenu/ShareWithTeamAndGroup.tsx +++ b/web/src/components/features/ShareMenu/ShareWithTeamAndGroup.tsx @@ -10,7 +10,7 @@ import { UserGroup } from '@/components/ui/icons'; import { ShareAssetType } from '@/api/asset_interfaces'; import type { ShareRequest } from '@/api/asset_interfaces/shared_interfaces'; import { useGetCollection, useUpdateCollection } from '@/api/buster_rest/collections'; -import { useGetMetric, useUpdateMetric } from '@/api/buster_rest/metrics'; +import { useGetMetric, useSaveMetric } from '@/api/buster_rest/metrics'; import { useGetDashboard, useUpdateDashboard } from '@/api/buster_rest/dashboards'; export const ShareWithGroupAndTeam: React.FC<{ @@ -21,7 +21,7 @@ export const ShareWithGroupAndTeam: React.FC<{ }> = ({ assetType, assetId, goBack, onCopyLink }) => { const userTeams = useUserConfigContextSelector((state) => state.userTeams); const { mutateAsync: onShareDashboard } = useUpdateDashboard(); - const { mutateAsync: onShareMetric } = useUpdateMetric(); + const { mutateAsync: onShareMetric } = useSaveMetric(); const { mutateAsync: onShareCollection } = useUpdateCollection(); const { data: dashboardResponse } = useGetDashboard( assetType === ShareAssetType.DASHBOARD ? assetId : undefined diff --git a/web/src/components/ui/charts/TableChart/BusterTableChart.tsx b/web/src/components/ui/charts/TableChart/BusterTableChart.tsx index 7b0d0c098..fdf87ef74 100644 --- a/web/src/components/ui/charts/TableChart/BusterTableChart.tsx +++ b/web/src/components/ui/charts/TableChart/BusterTableChart.tsx @@ -22,12 +22,12 @@ const BusterTableChartBase: React.FC = ({ columnLabelFormats = DEFAULT_CHART_CONFIG.columnLabelFormats, tableColumnWidths = DEFAULT_CHART_CONFIG.tableColumnWidths, editable = true, + onInitialAnimationEnd, //TODO tableHeaderBackgroundColor, tableHeaderFontColor, isDarkMode, animate, - onInitialAnimationEnd, tableColumnFontColor }) => { const onUpdateMetricChartConfig = useBusterMetricsContextSelector( diff --git a/web/src/context/AppProviders.tsx b/web/src/context/AppProviders.tsx index 5afd99e4b..1daf6df3f 100644 --- a/web/src/context/AppProviders.tsx +++ b/web/src/context/AppProviders.tsx @@ -6,7 +6,7 @@ import { BusterReactQueryProvider } from './BusterReactQuery/BusterReactQueryAnd import { AppLayoutProvider } from './BusterAppLayout'; import { BusterUserConfigProvider } from './Users/UserConfigProvider'; import { BusterAssetsProvider } from './Assets/BusterAssetsProvider'; -import { BusterPosthogProvider } from './Posthog/usePosthog'; +import { BusterPosthogProvider } from './Posthog'; import { BusterChatProvider } from './Chats'; import { RoutePrefetcher } from './RoutePrefetcher'; import { BusterMetricsProvider } from './Metrics'; diff --git a/web/src/context/Metrics/BusterMetricProvider.tsx b/web/src/context/Metrics/BusterMetricProvider.tsx index 333bdb53f..3cac32784 100644 --- a/web/src/context/Metrics/BusterMetricProvider.tsx +++ b/web/src/context/Metrics/BusterMetricProvider.tsx @@ -3,10 +3,9 @@ import React, { PropsWithChildren, useTransition } from 'react'; import { createContext, useContextSelector } from 'use-context-selector'; import { - BusterMetric, ColumnSettings, DEFAULT_CHART_CONFIG, - IColumnLabelFormat, + type IColumnLabelFormat, type IBusterMetric, type IBusterMetricChartConfig } from '@/api/asset_interfaces/metric'; @@ -14,13 +13,9 @@ import { useParams } from 'next/navigation'; import { useQueryClient } from '@tanstack/react-query'; import { queryKeys } from '@/api/query_keys'; import { useDebounceFn, useMemoizedFn } from '@/hooks'; -import { - prepareMetricUpdateMetric, - resolveEmptyMetric, - upgradeMetricToIMetric -} from '@/lib/metrics'; +import { prepareMetricUpdateMetric, resolveEmptyMetric } from '@/lib/metrics'; import { create } from 'mutative'; -import { ShareRole, VerificationStatus } from '@/api/asset_interfaces/share'; +import { ShareRole } from '@/api/asset_interfaces/share'; import { useUpdateMetric } from '@/api/buster_rest/metrics'; const useBusterMetrics = () => { @@ -44,7 +39,7 @@ const useBusterMetrics = () => { // STATE UPDATERS - const setMetricToState = useMemoizedFn((metric: IBusterMetric) => { + const _setMetricToState = useMemoizedFn((metric: IBusterMetric) => { const metricId = getMetricId(metric.id); const options = queryKeys.metricsGetMetric(metricId); queryClient.setQueryData(options.queryKey, metric); @@ -57,7 +52,7 @@ const useBusterMetrics = () => { const newMetric = create(currentMetric, (draft) => { Object.assign(draft, newMetricPartial); }); - setMetricToState(newMetric); + _setMetricToState(newMetric); //This will trigger a rerender and push prepareMetricUpdateMetric off UI metric startTransition(() => { const isReadyOnly = currentMetric.permission === ShareRole.VIEWER; @@ -73,7 +68,7 @@ const useBusterMetrics = () => { useMemoizedFn((newMetric: IBusterMetric, oldMetric: IBusterMetric) => { const changedValues = prepareMetricUpdateMetric(newMetric, oldMetric); if (changedValues) { - updateMetricMutation(changedValues); + // updateMetricMutation(changedValues); } }), { wait: 750 } @@ -173,32 +168,12 @@ const useBusterMetrics = () => { } ); - const onSaveMetricChanges = useMemoizedFn( - async (params: { metricId: string; save_draft: boolean; save_as_metric_state?: string }) => { - return updateMetricMutation({ - id: params.metricId, - ...params - }); - } - ); - - const onVerifiedMetric = useMemoizedFn( - async ({ metricId, status }: { metricId: string; status: VerificationStatus }) => { - return await onUpdateMetric({ - id: metricId, - status - }); - } - ); - return { getMetricMemoized, onUpdateMetric, onUpdateMetricChartConfig, onUpdateColumnLabelFormat, - onUpdateColumnSetting, - onSaveMetricChanges, - onVerifiedMetric + onUpdateColumnSetting }; }; diff --git a/web/src/context/Posthog/usePosthog.tsx b/web/src/context/Posthog/BusterPosthogProvider.tsx similarity index 100% rename from web/src/context/Posthog/usePosthog.tsx rename to web/src/context/Posthog/BusterPosthogProvider.tsx diff --git a/web/src/context/Posthog/index.ts b/web/src/context/Posthog/index.ts index e69de29bb..4654ce884 100644 --- a/web/src/context/Posthog/index.ts +++ b/web/src/context/Posthog/index.ts @@ -0,0 +1 @@ +export * from './BusterPosthogProvider'; diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricViewChart.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricViewChart.tsx index 437c104b8..59dfbfc74 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricViewChart.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricViewChart.tsx @@ -1,8 +1,7 @@ import React, { useMemo } from 'react'; import { MetricViewChartContent } from './MetricViewChartContent'; import { MetricViewChartHeader } from './MetricViewChartHeader'; -import { useMetricIndividual } from '@/api/buster_rest/metrics'; -import { useBusterMetricsContextSelector } from '@/context/Metrics'; +import { useMetricIndividual, useUpdateMetric } from '@/api/buster_rest/metrics'; import { useMemoizedFn } from '@/hooks'; import { inputHasText } from '@/lib/text'; import { MetricChartEvaluation } from './MetricChartEvaluation'; @@ -11,7 +10,7 @@ import { AnimatePresence, motion } from 'framer-motion'; import { cn } from '@/lib/classMerge'; export const MetricViewChart: React.FC<{ metricId: string }> = React.memo(({ metricId }) => { - const onUpdateMetric = useBusterMetricsContextSelector((x) => x.onUpdateMetric); + const { mutateAsync: updateMetric } = useUpdateMetric(); const { metric, metricData, metricDataError, isFetchedMetricData } = useMetricIndividual({ metricId }); @@ -24,7 +23,8 @@ export const MetricViewChart: React.FC<{ metricId: string }> = React.memo(({ met const onSetTitle = useMemoizedFn((title: string) => { if (inputHasText(title)) { - onUpdateMetric({ + updateMetric({ + id: metricId, title }); } diff --git a/web/src/controllers/MetricController/MetricViewFile/MetricViewFile.tsx b/web/src/controllers/MetricController/MetricViewFile/MetricViewFile.tsx index 9459ee86e..263af715d 100644 --- a/web/src/controllers/MetricController/MetricViewFile/MetricViewFile.tsx +++ b/web/src/controllers/MetricController/MetricViewFile/MetricViewFile.tsx @@ -1,16 +1,15 @@ import React, { useEffect } from 'react'; import type { MetricViewProps } from '../config'; import { CodeCard } from '@/components/ui/card'; -import { useBusterMetricsContextSelector } from '@/context/Metrics'; import { useMemoizedFn } from '@/hooks'; import { SaveResetFilePopup } from '@/components/features/popups/SaveResetFilePopup'; import { useBusterNotifications } from '@/context/BusterNotifications'; -import { useMetricIndividual } from '@/api/buster_rest/metrics'; +import { useMetricIndividual, useUpdateMetric } from '@/api/buster_rest/metrics'; export const MetricViewFile: React.FC = React.memo(({ metricId }) => { const { metric } = useMetricIndividual({ metricId }); const { openSuccessMessage } = useBusterNotifications(); - const onUpdateMetric = useBusterMetricsContextSelector((x) => x.onUpdateMetric); + const { mutateAsync: updateMetric } = useUpdateMetric(); const { file: fileProp, file_name } = metric; @@ -23,8 +22,9 @@ export const MetricViewFile: React.FC = React.memo(({ metricId }); const onSaveFile = useMemoizedFn(async () => { - await onUpdateMetric({ - file + await updateMetric({ + file, + id: metricId }); openSuccessMessage(`${file_name} saved`); }); diff --git a/web/src/controllers/MetricController/MetricViewResults/useMetricRunSQL/useMetricRunSQL.ts b/web/src/controllers/MetricController/MetricViewResults/useMetricRunSQL/useMetricRunSQL.ts index 93538cd67..9b38c736d 100644 --- a/web/src/controllers/MetricController/MetricViewResults/useMetricRunSQL/useMetricRunSQL.ts +++ b/web/src/controllers/MetricController/MetricViewResults/useMetricRunSQL/useMetricRunSQL.ts @@ -8,14 +8,13 @@ import { useQueryClient } from '@tanstack/react-query'; import { useRef, useState } from 'react'; import { didColumnDataChange, simplifyChatConfigForSQLChange } from './helpers'; import { useRunSQL as useRunSQLQuery } from '@/api/buster_rest'; -import { useUpdateMetric } from '@/api/buster_rest/metrics'; +import { useSaveMetric, useUpdateMetric } from '@/api/buster_rest/metrics'; export const useMetricRunSQL = () => { const queryClient = useQueryClient(); - const onUpdateMetric = useBusterMetricsContextSelector((x) => x.onUpdateMetric); const getMetricMemoized = useBusterMetricsContextSelector((x) => x.getMetricMemoized); - const onSaveMetricChanges = useBusterMetricsContextSelector((x) => x.onSaveMetricChanges); const { mutateAsync: updateMetricMutation } = useUpdateMetric(); + const { mutateAsync: saveMetric } = useSaveMetric(); const { mutateAsync: runSQLMutation } = useRunSQLQuery(); const { openSuccessNotification } = useBusterNotifications(); @@ -88,7 +87,7 @@ export const useMetricRunSQL = () => { data_metadata, code: sql }); - onUpdateMetric({ + updateMetricMutation({ id: metricId, chart_config: totallyDefaultChartConfig }); @@ -121,7 +120,7 @@ export const useMetricRunSQL = () => { setWarnBeforeNavigating(false); if (!originalConfigs.current) return; const oldConfig = originalConfigs.current?.chartConfig; - onUpdateMetric({ + updateMetricMutation({ id: metricId, chart_config: oldConfig }); @@ -161,14 +160,10 @@ export const useMetricRunSQL = () => { } } - await updateMetricMutation({ + await saveMetric({ id: metricId, - sql: sql - }); - await onSaveMetricChanges({ - metricId, - save_draft: true, - save_as_metric_state: metricId + sql: sql, + save_draft: true }); setWarnBeforeNavigating(false); diff --git a/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx b/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx index 16e13cb5c..64a975ee6 100644 --- a/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx +++ b/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx @@ -14,7 +14,8 @@ import { Dots, Star, Trash, Xmark } from '@/components/ui/icons'; import { useDeleteMetric, useRemoveMetricFromCollection, - useSaveMetricToCollection + useSaveMetricToCollection, + useUpdateMetric } from '@/api/buster_rest/metrics'; import { useAddUserFavorite, @@ -130,11 +131,18 @@ const StatusButton: React.FC<{ selectedRowKeys: string[]; onSelectChange: (selectedRowKeys: string[]) => void; }> = ({ selectedRowKeys, onSelectChange }) => { - const onVerifiedMetric = useBusterMetricsContextSelector((state) => state.onVerifiedMetric); + const { mutateAsync: updateMetric } = useUpdateMetric(); const isAdmin = useUserConfigContextSelector((state) => state.isAdmin); const onVerify = useMemoizedFn(async (d: { id: string; status: VerificationStatus }[]) => { - // await onVerifiedMetric(d); + await Promise.all( + d.map(({ id, status }) => { + return updateMetric({ + id, + status + }); + }) + ); onSelectChange([]); });