diff --git a/web/package-lock.json b/web/package-lock.json index 90014103a..468c2fd91 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -55,11 +55,11 @@ "monaco-sql-languages": "^0.13.1", "monaco-yaml": "^5.3.1", "mutative": "^1.1.0", - "next": "14.2.24", + "next": "14.2.25", "next-themes": "^0.4.6", "papaparse": "^5.5.2", "pluralize": "^8.0.0", - "posthog-js": "^1.231.2", + "posthog-js": "^1.231.3", "prettier": "^3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", "react": "^18", @@ -3989,9 +3989,9 @@ } }, "node_modules/@next/env": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.24.tgz", - "integrity": "sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.25.tgz", + "integrity": "sha512-JnzQ2cExDeG7FxJwqAksZ3aqVJrHjFwZQAEJ9gQZSoEhIow7SNoKZzju/AwQ+PLIR4NY8V0rhcVozx/2izDO0w==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -4005,9 +4005,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.24.tgz", - "integrity": "sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.25.tgz", + "integrity": "sha512-09clWInF1YRd6le00vt750s3m7SEYNehz9C4PUcSu3bAdCTpjIV4aTYQZ25Ehrr83VR1rZeqtKUPWSI7GfuKZQ==", "cpu": [ "arm64" ], @@ -4021,9 +4021,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.24.tgz", - "integrity": "sha512-lXR2WQqUtu69l5JMdTwSvQUkdqAhEWOqJEYUQ21QczQsAlNOW2kWZCucA6b3EXmPbcvmHB1kSZDua/713d52xg==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.25.tgz", + "integrity": "sha512-V+iYM/QR+aYeJl3/FWWU/7Ix4b07ovsQ5IbkwgUK29pTHmq+5UxeDr7/dphvtXEq5pLB/PucfcBNh9KZ8vWbug==", "cpu": [ "x64" ], @@ -4037,9 +4037,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.24.tgz", - "integrity": "sha512-nxvJgWOpSNmzidYvvGDfXwxkijb6hL9+cjZx1PVG6urr2h2jUqBALkKjT7kpfurRWicK6hFOvarmaWsINT1hnA==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.25.tgz", + "integrity": "sha512-LFnV2899PJZAIEHQ4IMmZIgL0FBieh5keMnriMY1cK7ompR+JUd24xeTtKkcaw8QmxmEdhoE5Mu9dPSuDBgtTg==", "cpu": [ "arm64" ], @@ -4053,9 +4053,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.24.tgz", - "integrity": "sha512-PaBgOPhqa4Abxa3y/P92F3kklNPsiFjcjldQGT7kFmiY5nuFn8ClBEoX8GIpqU1ODP2y8P6hio6vTomx2Vy0UQ==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.25.tgz", + "integrity": "sha512-QC5y5PPTmtqFExcKWKYgUNkHeHE/z3lUsu83di488nyP0ZzQ3Yse2G6TCxz6nNsQwgAx1BehAJTZez+UQxzLfw==", "cpu": [ "arm64" ], @@ -4069,9 +4069,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.24.tgz", - "integrity": "sha512-vEbyadiRI7GOr94hd2AB15LFVgcJZQWu7Cdi9cWjCMeCiUsHWA0U5BkGPuoYRnTxTn0HacuMb9NeAmStfBCLoQ==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.25.tgz", + "integrity": "sha512-y6/ML4b9eQ2D/56wqatTJN5/JR8/xdObU2Fb1RBidnrr450HLCKr6IJZbPqbv7NXmje61UyxjF5kvSajvjye5w==", "cpu": [ "x64" ], @@ -4085,9 +4085,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.24.tgz", - "integrity": "sha512-df0FC9ptaYsd8nQCINCzFtDWtko8PNRTAU0/+d7hy47E0oC17tI54U/0NdGk7l/76jz1J377dvRjmt6IUdkpzQ==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.25.tgz", + "integrity": "sha512-sPX0TSXHGUOZFvv96GoBXpB3w4emMqKeMgemrSxI7A6l55VBJp/RKYLwZIB9JxSqYPApqiREaIIap+wWq0RU8w==", "cpu": [ "x64" ], @@ -4101,9 +4101,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.24.tgz", - "integrity": "sha512-ZEntbLjeYAJ286eAqbxpZHhDFYpYjArotQ+/TW9j7UROh0DUmX7wYDGtsTPpfCV8V+UoqHBPU7q9D4nDNH014Q==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.25.tgz", + "integrity": "sha512-ReO9S5hkA1DU2cFCsGoOEp7WJkhFzNbU/3VUF6XxNGUCQChyug6hZdYL/istQgfT/GWE6PNIg9cm784OI4ddxQ==", "cpu": [ "arm64" ], @@ -4117,9 +4117,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.24.tgz", - "integrity": "sha512-9KuS+XUXM3T6v7leeWU0erpJ6NsFIwiTFD5nzNg8J5uo/DMIPvCp3L1Ao5HjbHX0gkWPB1VrKoo/Il4F0cGK2Q==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.25.tgz", + "integrity": "sha512-DZ/gc0o9neuCDyD5IumyTGHVun2dCox5TfPQI/BJTYwpSNYM3CZDI4i6TOdjeq1JMo+Ug4kPSMuZdwsycwFbAw==", "cpu": [ "ia32" ], @@ -4133,9 +4133,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.24.tgz", - "integrity": "sha512-cXcJ2+x0fXQ2CntaE00d7uUH+u1Bfp/E0HsNQH79YiLaZE5Rbm7dZzyAYccn3uICM7mw+DxoMqEfGXZtF4Fgaw==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.25.tgz", + "integrity": "sha512-KSznmS6eFjQ9RJ1nEc66kJvtGIL1iZMYmGEXsZPh2YtnLtqrgdVvKXJY2ScjjoFnG6nGLyPFR0UiEvDwVah4Tw==", "cpu": [ "x64" ], @@ -16547,12 +16547,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.2.24", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.24.tgz", - "integrity": "sha512-En8VEexSJ0Py2FfVnRRh8gtERwDRaJGNvsvad47ShkC2Yi8AXQPXEA2vKoDJlGFSj5WE5SyF21zNi4M5gyi+SQ==", + "version": "14.2.25", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.25.tgz", + "integrity": "sha512-N5M7xMc4wSb4IkPvEV5X2BRRXUmhVHNyaXwEM86+voXthSZz8ZiRyQW4p9mwAoAPIm6OzuVZtn7idgEJeAJN3Q==", "license": "MIT", "dependencies": { - "@next/env": "14.2.24", + "@next/env": "14.2.25", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -16567,15 +16567,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.24", - "@next/swc-darwin-x64": "14.2.24", - "@next/swc-linux-arm64-gnu": "14.2.24", - "@next/swc-linux-arm64-musl": "14.2.24", - "@next/swc-linux-x64-gnu": "14.2.24", - "@next/swc-linux-x64-musl": "14.2.24", - "@next/swc-win32-arm64-msvc": "14.2.24", - "@next/swc-win32-ia32-msvc": "14.2.24", - "@next/swc-win32-x64-msvc": "14.2.24" + "@next/swc-darwin-arm64": "14.2.25", + "@next/swc-darwin-x64": "14.2.25", + "@next/swc-linux-arm64-gnu": "14.2.25", + "@next/swc-linux-arm64-musl": "14.2.25", + "@next/swc-linux-x64-gnu": "14.2.25", + "@next/swc-linux-x64-musl": "14.2.25", + "@next/swc-win32-arm64-msvc": "14.2.25", + "@next/swc-win32-ia32-msvc": "14.2.25", + "@next/swc-win32-x64-msvc": "14.2.25" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -17578,9 +17578,9 @@ "license": "MIT" }, "node_modules/posthog-js": { - "version": "1.231.2", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.231.2.tgz", - "integrity": "sha512-KTOZTvPbVZoZYC4vqZUmPoG/brbyNqqeeUllH4MZBIW0gyC6JJJYsU+za1of9La73F4KBN1ZM3O0tXpFrLSAww==", + "version": "1.231.3", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.231.3.tgz", + "integrity": "sha512-IHbdMpI9btpgn7xIlq+cFH47227NTxD6so9jWYLdVD7Qt5rLTm0Ho61iUFHj1gwd+pFXP3H2TCBJ9+dBgZ0zFw==", "license": "MIT", "dependencies": { "core-js": "^3.38.1", diff --git a/web/package.json b/web/package.json index 49de20809..556708176 100644 --- a/web/package.json +++ b/web/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbo", "build": "next build", "start": "next start", "lint": "next lint", @@ -63,11 +63,11 @@ "monaco-sql-languages": "^0.13.1", "monaco-yaml": "^5.3.1", "mutative": "^1.1.0", - "next": "14.2.24", + "next": "14.2.25", "next-themes": "^0.4.6", "papaparse": "^5.5.2", "pluralize": "^8.0.0", - "posthog-js": "^1.231.2", + "posthog-js": "^1.231.3", "prettier": "^3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", "react": "^18", diff --git a/web/src/api/asset_interfaces/metric/defaults.ts b/web/src/api/asset_interfaces/metric/defaults.ts index 9dd96303d..9c6cf69d8 100644 --- a/web/src/api/asset_interfaces/metric/defaults.ts +++ b/web/src/api/asset_interfaces/metric/defaults.ts @@ -144,7 +144,7 @@ export const DEFAULT_IBUSTER_METRIC: Required = { version_number: 1, description: '', time_frame: '', - code: null, + sql: null, feedback: null, dataset_id: '', dataset_name: null, diff --git a/web/src/api/asset_interfaces/metric/interfaces.ts b/web/src/api/asset_interfaces/metric/interfaces.ts index 864b6808b..c0ac62a8e 100644 --- a/web/src/api/asset_interfaces/metric/interfaces.ts +++ b/web/src/api/asset_interfaces/metric/interfaces.ts @@ -24,7 +24,7 @@ export type BusterMetric = { sent_by_id: string; sent_by_name: string; sent_by_avatar_url: string | null; - code: string | null; + sql: string | null; feedback: 'negative' | null; dashboards: { id: string; diff --git a/web/src/api/buster_rest/metrics/queryRequests.ts b/web/src/api/buster_rest/metrics/queryRequests.ts index c4cb54e3e..3384ab357 100644 --- a/web/src/api/buster_rest/metrics/queryRequests.ts +++ b/web/src/api/buster_rest/metrics/queryRequests.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { QueryClient } from '@tanstack/react-query'; -import { useMemoizedFn } from '@/hooks'; +import { useDebounceFn, useMemoizedFn } from '@/hooks'; import { deleteMetrics, duplicateMetric, @@ -23,7 +23,7 @@ import type { IBusterMetric } from '@/api/asset_interfaces/metric'; import { dashboardQueryKeys } from '@/api/query_keys/dashboard'; export const useGetMetric = ( - id: string | undefined, + { id, version_number }: { id: string | undefined; version_number?: number }, select?: (data: IBusterMetric) => TData ) => { const getAssetPassword = useBusterAssetsContextSelector((x) => x.getAssetPassword); @@ -101,27 +101,33 @@ export const prefetchGetMetricsList = async ( return queryClient; }; -export const useGetMetricData = (params: { id: string }) => { +export const useGetMetricData = ({ + id, + version_number +}: { + id: string; + version_number?: number; +}) => { const queryFn = useMemoizedFn(() => { - return getMetricData(params); + return getMetricData({ id, version_number }); }); return useQuery({ - ...metricsQueryKeys.metricsGetData(params.id), + ...metricsQueryKeys.metricsGetData(id), queryFn, - enabled: !!params.id + enabled: !!id }); }; export const prefetchGetMetricDataClient = async ( - params: { id: string }, + { id }: { id: string }, queryClient: QueryClient ) => { - const options = metricsQueryKeys.metricsGetData(params.id); + const options = metricsQueryKeys.metricsGetData(id); const existingData = queryClient.getQueryData(options.queryKey); if (!existingData) { await queryClient.prefetchQuery({ ...options, - queryFn: () => getMetricData(params) + queryFn: () => getMetricData({ id }) }); } }; @@ -156,49 +162,6 @@ export const useDeleteMetric = () => { }); }; -export const useMetricIndividual = ({ metricId }: { metricId: string }) => { - const { - data: metric, - isFetched: isMetricFetched, - error: metricError, - refetch: refetchMetric - } = useGetMetric(metricId); - - const { - data: metricData, - isFetched: isFetchedMetricData, - refetch: refetchMetricData, - dataUpdatedAt: metricDataUpdatedAt, - error: metricDataError - } = useGetMetricData({ id: metricId }); - - return useMemo( - () => ({ - metric: resolveEmptyMetric(metric, metricId), - isMetricFetched, - refetchMetric, - metricError, - metricData, - isFetchedMetricData, - refetchMetricData, - metricDataUpdatedAt, - metricDataError - }), - [ - metric, - metricId, - isMetricFetched, - refetchMetric, - metricError, - metricData, - isFetchedMetricData, - refetchMetricData, - metricDataUpdatedAt, - metricDataError - ] - ); -}; - export const useSaveMetricToCollection = () => { const { data: userFavorites, refetch: refreshFavoritesList } = useGetUserFavorites(); const { mutateAsync: saveMetric } = useSaveMetric(); diff --git a/web/src/api/buster_rest/metrics/queryRequestsClient.ts b/web/src/api/buster_rest/metrics/queryRequestsClient.ts index 0174495aa..fe8d32a3a 100644 --- a/web/src/api/buster_rest/metrics/queryRequestsClient.ts +++ b/web/src/api/buster_rest/metrics/queryRequestsClient.ts @@ -1,11 +1,8 @@ -'use client'; - import { IBusterMetric } from '@/api/asset_interfaces'; import { queryKeys } from '@/api/query_keys'; import { useMemoizedFn, useDebounceFn } from '@/hooks'; import { prepareMetricUpdateMetric } from '@/lib/metrics'; import { useQueryClient, useMutation } from '@tanstack/react-query'; -import { useTransition, useMemo } from 'react'; import { useSaveMetric } from './queryRequests'; import { create } from 'mutative'; /** @@ -14,11 +11,19 @@ import { create } from 'mutative'; * 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 = (params?: { wait?: number }) => { - const [isPending, startTransition] = useTransition(); +export const useUpdateMetric = () => { const queryClient = useQueryClient(); const { mutateAsync: saveMetric } = useSaveMetric(); - const waitTime = params?.wait || 0; + + const { run: saveMetricDebounced } = useDebounceFn( + (newMetric: IBusterMetric, prevMetric: IBusterMetric) => { + const changedValues = prepareMetricUpdateMetric(newMetric, prevMetric); + if (changedValues) { + saveMetric(changedValues); + } + }, + { wait: 650 } + ); const combineAndSaveMetric = useMemoizedFn( async (newMetricPartial: Partial & { id: string }) => { @@ -41,12 +46,7 @@ export const useUpdateMetric = (params?: { wait?: number }) => { async (newMetricPartial: Partial & { id: string }) => { const { newMetric, prevMetric } = await combineAndSaveMetric(newMetricPartial); if (newMetric && prevMetric) { - startTransition(() => { - const changedValues = prepareMetricUpdateMetric(newMetric, prevMetric); - if (changedValues) { - saveMetric(changedValues); - } - }); + saveMetricDebounced(newMetric, prevMetric); } return Promise.resolve(newMetric!); } @@ -56,13 +56,5 @@ export const useUpdateMetric = (params?: { wait?: number }) => { mutationFn: mutationFn }); - const { run: mutateDebounced } = useDebounceFn(mutationRes.mutateAsync, { wait: waitTime }); - - return useMemo( - () => ({ - ...mutationRes, - mutateDebounced - }), - [mutationRes, mutateDebounced] - ); + return mutationRes; }; diff --git a/web/src/api/buster_rest/metrics/requests.ts b/web/src/api/buster_rest/metrics/requests.ts index 9ad11760d..0d3ec66eb 100644 --- a/web/src/api/buster_rest/metrics/requests.ts +++ b/web/src/api/buster_rest/metrics/requests.ts @@ -21,8 +21,16 @@ export const getMetric_server = async ({ id, password }: GetMetricParams) => { }); }; -export const getMetricData = async ({ id }: { id: string }) => { - return mainApi.get(`/metrics/${id}/data`).then((res) => res.data); +export const getMetricData = async ({ + id, + version_number +}: { + id: string; + version_number?: number; +}) => { + return mainApi + .get(`/metrics/${id}/data`, { params: { version_number } }) + .then((res) => res.data); }; export const listMetrics = async (params: ListMetricsParams) => { diff --git a/web/src/app/embed/metrics/[metricId]/page.tsx b/web/src/app/embed/metrics/[metricId]/page.tsx index 6b02148b4..db53e8386 100644 --- a/web/src/app/embed/metrics/[metricId]/page.tsx +++ b/web/src/app/embed/metrics/[metricId]/page.tsx @@ -6,7 +6,7 @@ import { MetricViewChart } from '@/controllers/MetricController/MetricViewChart/ export default function EmbedMetricsPage({ params }: { params: { metricId: string } }) { const { metricId } = params; - const { isFetched } = useGetMetric(metricId); + const { isFetched } = useGetMetric({ id: metricId }); if (!isFetched) { return ; diff --git a/web/src/components/features/ShareMenu/ShareMenuContent.tsx b/web/src/components/features/ShareMenu/ShareMenuContent.tsx index 68262fe9e..4db7c5634 100644 --- a/web/src/components/features/ShareMenu/ShareMenuContent.tsx +++ b/web/src/components/features/ShareMenu/ShareMenuContent.tsx @@ -6,7 +6,7 @@ import { useMemoizedFn } from '@/hooks'; import { BusterRoutes, createBusterRoute } from '@/routes'; import { useBusterNotifications } from '@/context/BusterNotifications'; import { ShareMenuContentEmbedFooter } from './ShareMenuContentEmbed'; -import { isEffectiveOwner } from '@/lib/share'; +import { getIsEffectiveOwner } from '@/lib/share'; export const ShareMenuContent: React.FC<{ shareAssetConfig: BusterShare; @@ -21,7 +21,7 @@ export const ShareMenuContent: React.FC<{ const permission = shareAssetConfig?.permission; const publicly_accessible = shareAssetConfig?.publicly_accessible; - const isOwner = isEffectiveOwner(permission); + const isOwner = getIsEffectiveOwner(permission); const onCopyLink = useMemoizedFn(() => { let url = ''; diff --git a/web/src/components/features/ShareMenu/ShareWithTeamAndGroup.tsx b/web/src/components/features/ShareMenu/ShareWithTeamAndGroup.tsx index fdf2b2394..aac91ca54 100644 --- a/web/src/components/features/ShareMenu/ShareWithTeamAndGroup.tsx +++ b/web/src/components/features/ShareMenu/ShareWithTeamAndGroup.tsx @@ -29,7 +29,9 @@ export const ShareWithGroupAndTeam: React.FC<{ const { data: collection } = useGetCollection( assetType === ShareAssetType.COLLECTION ? assetId : undefined ); - const { data: metric } = useGetMetric(assetType === ShareAssetType.METRIC ? assetId : undefined); + const { data: metric } = useGetMetric( + assetType === ShareAssetType.METRIC ? { id: assetId } : { id: undefined } + ); const onUpdateShareRole = useMemoizedFn( async ({ teamId, role }: { teamId: string; role: ShareRole | null }) => { diff --git a/web/src/components/features/buttons/ShareDashboardButton.tsx b/web/src/components/features/buttons/ShareDashboardButton.tsx index fbe2aba74..c2d06a5bd 100644 --- a/web/src/components/features/buttons/ShareDashboardButton.tsx +++ b/web/src/components/features/buttons/ShareDashboardButton.tsx @@ -3,9 +3,10 @@ import { ShareButton } from './ShareButton'; import { ShareMenu } from '../ShareMenu'; import { ShareAssetType } from '@/api/asset_interfaces'; import { useGetDashboard } from '@/api/buster_rest/dashboards'; +import { getShareAssetConfig } from '../ShareMenu/helpers'; export const ShareDashboardButton = React.memo(({ dashboardId }: { dashboardId: string }) => { - const { data: dashboardResponse } = useGetDashboard(dashboardId); + const { data: dashboardResponse } = useGetDashboard(dashboardId, getShareAssetConfig); return ( { - const { data: shareAssetConfig } = useGetMetric(metricId, getShareAssetConfig); + const { data: shareAssetConfig } = useGetMetric({ id: metricId }, getShareAssetConfig); return ( { const params = useParams<{ metricId?: string }>(); const metricId = props?.metricId ?? params.metricId ?? ''; - const { mutateDebounced: onUpdateMetricDebounced } = useUpdateMetric({ wait: 600 }); + const { mutate: onUpdateMetricDebounced } = useUpdateMetric(); const getMetricMemoized = useGetMetricMemoized(); const onUpdateMetricChartConfig = useMemoizedFn( diff --git a/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/DashboardMetricItem.tsx b/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/DashboardMetricItem.tsx index 7d7edbee1..11e0896fe 100644 --- a/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/DashboardMetricItem.tsx +++ b/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/DashboardMetricItem.tsx @@ -34,21 +34,21 @@ const DashboardMetricItemBase: React.FC<{ } = useDashboardMetric({ metricId }); const loadingMetricData = !!metric && !isFetchedMetricData; - const chartOptions = metric.chart_config; + const chartOptions = metric?.chart_config; const data = metricData?.data || null; const loading = loadingMetricData; const dataLength = metricData?.data?.length || 1; const animate = !initialAnimationEnded && !isDragOverlay && dataLength < 125 && numberOfMetrics <= 30; - const isTable = metric.chart_config.selectedChartType === 'table'; + const isTable = metric?.chart_config.selectedChartType === 'table'; const error = useMemo(() => { - if (metric.error) { + if (metric?.error) { return metric.error; } return undefined; - }, [metric.error, metric.code]); + }, [metric?.error]); const metricLink = useMemo(() => { return createBusterRoute({ @@ -62,12 +62,7 @@ const DashboardMetricItemBase: React.FC<{ setInitialAnimationEnded(metricId); }); - // const cardClassNamesMemoized = useMemo(() => { - // return { - // body: `h-full w-full overflow-hidden ${isTable ? 'p-0!' : 'px-2! pt-2! pb-0.5!'} relative`, - // header: cx(`p-0! min-h-[52px]! mb-0!`, styles.cardTitle) - // }; - // }, [isTable]); + if (!chartOptions) return null; return ( diff --git a/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/useDashboardMetric.ts b/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/useDashboardMetric.ts index ba22d3cd6..ce2d71e47 100644 --- a/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/useDashboardMetric.ts +++ b/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/useDashboardMetric.ts @@ -1,13 +1,15 @@ import { useDashboardContentControllerContextSelector } from '../DashboardContentControllerContext'; import { useEffect, useMemo, useRef } from 'react'; import { useInViewport } from '@/hooks'; -import { useMetricIndividual } from '@/api/buster_rest/metrics'; +import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics'; export const useDashboardMetric = ({ metricId }: { metricId: string }) => { - const { metric, metricData, isMetricFetched, metricDataUpdatedAt, isFetchedMetricData } = - useMetricIndividual({ - metricId - }); + const { data: metric, isFetched: isMetricFetched } = useGetMetric({ id: metricId }); + const { + data: metricData, + isFetched: isFetchedMetricData, + dataUpdatedAt: metricDataUpdatedAt + } = useGetMetricData({ id: metricId }); const dashboard = useDashboardContentControllerContextSelector(({ dashboard }) => dashboard); const metricMetadata = useDashboardContentControllerContextSelector( ({ metricMetadata }) => metricMetadata[metricId] diff --git a/web/src/controllers/MetricController/MetricController.tsx b/web/src/controllers/MetricController/MetricController.tsx index 3a02d238e..dd398d606 100644 --- a/web/src/controllers/MetricController/MetricController.tsx +++ b/web/src/controllers/MetricController/MetricController.tsx @@ -6,22 +6,30 @@ import { useChatLayoutContextSelector } from '@/layouts/ChatLayout/ChatLayoutContext'; import { MetricViewComponents } from './config'; -import { useMetricIndividual } from '@/api/buster_rest/metrics'; import { FileIndeterminateLoader } from '@/components/features/FileIndeterminateLoader'; +import { useMount } from '@/hooks'; +import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics'; export const MetricController: React.FC<{ metricId: string; }> = React.memo(({ metricId }) => { - const { isMetricFetched, isFetchedMetricData } = useMetricIndividual({ metricId }); + const { isFetched: isMetricFetched } = useGetMetric({ id: metricId }); + const { isFetched: isMetricDataFetched } = useGetMetricData({ id: metricId }); const selectedFileView = useChatLayoutContextSelector((x) => x.selectedFileView) || 'chart'; - const showLoader = !isMetricFetched || !isFetchedMetricData; + const showLoader = !isMetricFetched || !isMetricDataFetched; const Component = selectedFileView in MetricViewComponents ? MetricViewComponents[selectedFileView as MetricFileView] : () => <>; + console.log('here', metricId); + + useMount(() => { + console.log('mounted', metricId); + }); + return ( <> {showLoader && } diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricChartEvaluation.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricChartEvaluation.tsx index 45f311e28..f0df53748 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricChartEvaluation.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricChartEvaluation.tsx @@ -9,8 +9,8 @@ import { Popover } from '@/components/ui/tooltip/Popover'; import { Button, type ButtonProps } from '@/components/ui/buttons'; export const MetricChartEvaluation: React.FC<{ - evaluationScore: IBusterMetric['evaluation_score']; - evaluationSummary: string; + evaluationScore: IBusterMetric['evaluation_score'] | undefined; + evaluationSummary: string | undefined; }> = React.memo(({ evaluationScore, evaluationSummary }) => { const text = useMemo(() => { if (evaluationScore === 'High') return 'High confidence'; diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/MetricStylingApp.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/MetricStylingApp.tsx index d06342137..568f001bf 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/MetricStylingApp.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/MetricStylingApp.tsx @@ -10,7 +10,7 @@ import { ChartType, ScatterAxis } from '@/api/asset_interfaces/metric/charts'; -import { useMetricIndividual } from '@/api/buster_rest/metrics'; +import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics'; export const MetricStylingApp: React.FC<{ metricId: string; @@ -18,7 +18,8 @@ export const MetricStylingApp: React.FC<{ const [segment, setSegment] = useState( MetricStylingAppSegments.VISUALIZE ); - const { metric, metricData } = useMetricIndividual({ metricId }); + const { data: metric } = useGetMetric({ id: metricId }); + const { data: metricData } = useGetMetricData({ id: metricId }); if (!metric) return null; diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppVisualize/SelectChartType/SelectChartType.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppVisualize/SelectChartType/SelectChartType.tsx index 045e91a05..dac4cbe04 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppVisualize/SelectChartType/SelectChartType.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppVisualize/SelectChartType/SelectChartType.tsx @@ -56,6 +56,7 @@ export const SelectChartType: React.FC = ({ const onSelectChartType = useMemoizedFn((chartIconType: ChartIconType) => { const chartConfig = selectedChartTypeMethod(chartIconType, columnSettings); + console.log('chartConfig', chartConfig); onUpdateMetricChartConfig({ chartConfig }); }); diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricViewChart.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricViewChart.tsx index 161aaacb8..0074aea0a 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricViewChart.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricViewChart.tsx @@ -1,14 +1,13 @@ import React, { useMemo } from 'react'; import { MetricViewChartContent } from './MetricViewChartContent'; import { MetricViewChartHeader } from './MetricViewChartHeader'; -import { useMetricIndividual, useUpdateMetric } from '@/api/buster_rest/metrics'; +import { useGetMetric, useGetMetricData, useUpdateMetric } from '@/api/buster_rest/metrics'; import { useMemoizedFn } from '@/hooks'; import { inputHasText } from '@/lib/text'; import { MetricChartEvaluation } from './MetricChartEvaluation'; import { ChartType } from '@/api/asset_interfaces/metric/charts/enum'; import { AnimatePresence, motion } from 'framer-motion'; import { cn } from '@/lib/classMerge'; -import { ShareRole } from '@/api/asset_interfaces'; import { canEdit } from '@/lib/share'; export const MetricViewChart: React.FC<{ @@ -18,14 +17,19 @@ export const MetricViewChart: React.FC<{ cardClassName?: string; }> = React.memo( ({ metricId, readOnly: readOnlyProp = false, className = '', cardClassName = '' }) => { - const { metric, metricData, metricDataError, isFetchedMetricData } = useMetricIndividual({ - metricId - }); - const { mutateAsync: updateMetric } = useUpdateMetric(); - const { title, description, time_frame, evaluation_score, evaluation_summary } = metric; - const isTable = metric.chart_config.selectedChartType === ChartType.Table; + const { data: metric, isFetched: isMetricFetched } = useGetMetric({ id: metricId }); + const { + data: metricData, + isFetched: isFetchedMetricData, - const readOnly = readOnlyProp || !canEdit(metric.permission); + error: metricDataError + } = useGetMetricData({ id: metricId }); + + const { mutateAsync: updateMetric } = useUpdateMetric(); + const { title, description, time_frame, evaluation_score, evaluation_summary } = metric || {}; + const isTable = metric?.chart_config.selectedChartType === ChartType.Table; + + const readOnly = readOnlyProp || !canEdit(metric?.permission); const loadingData = !isFetchedMetricData; const errorData = !!metricDataError; @@ -40,6 +44,8 @@ export const MetricViewChart: React.FC<{ } }); + if (!metric) return null; + return (
= React.memo(({ metricId }) => { - const { metric } = useMetricIndividual({ metricId }); + const { data: metric } = useGetMetric({ id: metricId }); const { openSuccessMessage } = useBusterNotifications(); const { mutateAsync: updateMetric } = useUpdateMetric(); - const { file: fileProp, file_name } = metric; + const { file: fileProp, file_name } = metric || {}; const [file, setFile] = React.useState(fileProp); @@ -36,9 +36,9 @@ export const MetricViewFile: React.FC = React.memo(({ metricId return (
diff --git a/web/src/controllers/MetricController/MetricViewResults/MetricViewResultsController.tsx b/web/src/controllers/MetricController/MetricViewResults/MetricViewResultsController.tsx index 11d4e22dc..bbb21d803 100644 --- a/web/src/controllers/MetricController/MetricViewResults/MetricViewResultsController.tsx +++ b/web/src/controllers/MetricController/MetricViewResults/MetricViewResultsController.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useMemo } from 'react'; import type { MetricViewProps } from '../config'; -import { useMetricIndividual } from '@/api/buster_rest/metrics'; import { useMemoizedFn, useUnmount } from '@/hooks'; import { IDataResult } from '@/api/asset_interfaces'; import { useMetricLayout } from '../useMetricLayout'; @@ -8,6 +7,7 @@ import { useChatLayoutContextSelector } from '@/layouts/ChatLayout/ChatLayoutCon import { AppSplitterRef } from '@/components/ui/layouts'; import { AppVerticalCodeSplitter } from '@/components/features/layouts/AppVerticalCodeSplitter'; import { useMetricRunSQL } from './useMetricRunSQL'; +import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics'; const autoSaveId = 'metric-view-results'; @@ -21,17 +21,18 @@ export const MetricViewResults: React.FC = React.memo(({ metric const { runSQL, resetRunSQLData, saveSQL, warnBeforeNavigating, setWarnBeforeNavigating } = useMetricRunSQL(); - const { metric, metricData } = useMetricIndividual({ metricId }); + const { data: metric } = useGetMetric({ id: metricId }); + const { data: metricData } = useGetMetricData({ id: metricId }); - const [sql, setSQL] = React.useState(metric.code || ''); + const [sql, setSQL] = React.useState(metric?.sql || ''); const [fetchingData, setFetchingData] = React.useState(false); - const dataSourceId = metric?.data_source_id; + const dataSourceId = metric?.data_source_id || ''; const data: IDataResult = metricData?.dataFromRerun || metricData?.data || null; const disableSave = useMemo(() => { - return !sql || fetchingData || sql === metric.code; - }, [sql, fetchingData, metric.code]); + return !sql || fetchingData || sql === metric?.sql; + }, [sql, fetchingData, metric?.sql]); const onRunQuery = useMemoizedFn(async () => { setFetchingData(true); @@ -71,10 +72,10 @@ export const MetricViewResults: React.FC = React.memo(({ metric }); useEffect(() => { - if (metric.code) { - setSQL(metric.code); + if (metric?.sql) { + setSQL(metric.sql); } - }, [metric.code]); + }, [metric?.sql]); useUnmount(() => { resetRunSQLData({ metricId }); diff --git a/web/src/controllers/MetricController/MetricViewResults/useMetricRunSQL/useMetricRunSQL.ts b/web/src/controllers/MetricController/MetricViewResults/useMetricRunSQL/useMetricRunSQL.ts index 9978808cc..45e24cfca 100644 --- a/web/src/controllers/MetricController/MetricViewResults/useMetricRunSQL/useMetricRunSQL.ts +++ b/web/src/controllers/MetricController/MetricViewResults/useMetricRunSQL/useMetricRunSQL.ts @@ -29,7 +29,7 @@ export const useMetricRunSQL = () => { const originalConfigs = useRef<{ chartConfig: IBusterMetricChartConfig; - code: string; + sql: string; data: BusterMetricData['data']; dataMetadata: BusterMetricData['data_metadata']; } | null>(null); @@ -43,7 +43,6 @@ export const useMetricRunSQL = () => { metricId: string; data: BusterMetricData['data']; data_metadata: BusterMetricData['data_metadata']; - code: string; isDataFromRerun: boolean; }) => { const options = queryKeys.metricsGetData(metricId); @@ -65,7 +64,7 @@ export const useMetricRunSQL = () => { if (!originalConfigs.current) { originalConfigs.current = { chartConfig: metricMessage?.chart_config!, - code: metricMessage?.code!, + sql: metricMessage?.sql!, data: currentMessageData?.data!, dataMetadata: currentMessageData?.data_metadata! }; @@ -84,8 +83,7 @@ export const useMetricRunSQL = () => { metricId, data, isDataFromRerun: true, - data_metadata, - code: sql + data_metadata }); updateMetricMutation({ id: metricId, @@ -128,7 +126,6 @@ export const useMetricRunSQL = () => { metricId, data: originalConfigs.current?.data!, data_metadata: originalConfigs.current?.dataMetadata!, - code: originalConfigs.current?.code!, isDataFromRerun: false }); originalConfigs.current = null; @@ -148,7 +145,7 @@ export const useMetricRunSQL = () => { const currentMetric = getMetricMemoized(metricId); const dataSourceId = dataSourceIdProp || currentMetric?.data_source_id; - if ((!ogConfigs || ogConfigs.code !== sql) && dataSourceId) { + if ((!ogConfigs || ogConfigs.sql !== sql) && dataSourceId) { try { await runSQL({ metricId, @@ -173,7 +170,6 @@ export const useMetricRunSQL = () => { metricId, data: originalConfigs.current?.data!, data_metadata: originalConfigs.current?.dataMetadata!, - code: originalConfigs.current?.code!, isDataFromRerun: false }); } diff --git a/web/src/layouts/ChatLayout/ChatLayout/ChatLayout.tsx b/web/src/layouts/ChatLayout/ChatLayout/ChatLayout.tsx index dd5b3d2a6..1e2e1239d 100644 --- a/web/src/layouts/ChatLayout/ChatLayout/ChatLayout.tsx +++ b/web/src/layouts/ChatLayout/ChatLayout/ChatLayout.tsx @@ -15,7 +15,6 @@ interface ChatSplitterProps { export const ChatLayout: React.FC = ({ children }) => { const appSplitterRef = useRef(null); - const useChatLayoutProps = useChatLayout({ appSplitterRef }); const { renderViewLayoutKey, selectedLayout, selectedFile, onSetSelectedFile, chatId } = useChatLayoutProps; diff --git a/web/src/layouts/ChatLayout/ChatLayoutContext/useChatFileLayout/useChatFileLayout.ts b/web/src/layouts/ChatLayout/ChatLayoutContext/useChatFileLayout/useChatFileLayout.ts index d17d48bde..e264e9854 100644 --- a/web/src/layouts/ChatLayout/ChatLayoutContext/useChatFileLayout/useChatFileLayout.ts +++ b/web/src/layouts/ChatLayout/ChatLayoutContext/useChatFileLayout/useChatFileLayout.ts @@ -13,6 +13,23 @@ export const useChatFileLayout = ({ }) => { const [fileViews, setFileViews] = useState>({}); + const selectedFileView: FileView | undefined = useMemo(() => { + if (!selectedFileId) return undefined; + return ( + fileViews[selectedFileId]?.selectedFileView || defaultFileView[selectedFileType as FileType] + ); + }, [fileViews, selectedFileId, selectedFileType]); + + const selectedFileViewConfig: FileViewConfig | undefined = useMemo(() => { + if (!selectedFileId) return undefined; + return fileViews[selectedFileId]?.fileViewConfig; + }, [fileViews, selectedFileId]); + + const selectedFileViewSecondary: FileViewSecondary | null = useMemo(() => { + if (!selectedFileId || !selectedFileViewConfig || !selectedFileView) return null; + return selectedFileViewConfig?.[selectedFileView]?.secondaryView ?? null; + }, [selectedFileViewConfig, selectedFileId, selectedFileView]); + const onSetFileView = useMemoizedFn( ({ fileView, @@ -51,23 +68,6 @@ export const useChatFileLayout = ({ } ); - const selectedFileView: FileView | undefined = useMemo(() => { - if (!selectedFileId) return undefined; - return ( - fileViews[selectedFileId]?.selectedFileView || defaultFileView[selectedFileType as FileType] - ); - }, [fileViews, selectedFileId, selectedFileType]); - - const selectedFileViewConfig: FileViewConfig | undefined = useMemo(() => { - if (!selectedFileId) return undefined; - return fileViews[selectedFileId]?.fileViewConfig; - }, [fileViews, selectedFileId]); - - const selectedFileViewSecondary: FileViewSecondary | null = useMemo(() => { - if (!selectedFileId || !selectedFileViewConfig || !selectedFileView) return null; - return selectedFileViewConfig?.[selectedFileView]?.secondaryView ?? null; - }, [selectedFileViewConfig, selectedFileId, selectedFileView]); - const closeSecondaryView = useMemoizedFn(() => { if (!selectedFileId || !selectedFileViewConfig || !selectedFileView) return; setFileViews((prev) => { diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/DashboardContainerHeaderButtons/DashboardThreeDotMenu.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/DashboardContainerHeaderButtons/DashboardThreeDotMenu.tsx index 775a7f9e2..3083355a6 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/DashboardContainerHeaderButtons/DashboardThreeDotMenu.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/DashboardContainerHeaderButtons/DashboardThreeDotMenu.tsx @@ -24,14 +24,15 @@ import { Button } from '@/components/ui/buttons'; import React from 'react'; import { timeFromNow } from '@/lib/date'; import { ASSET_ICONS } from '@/components/features/config/assetIcons'; -import { useMemoizedFn, useWhyDidYouUpdate } from '@/hooks'; +import { useMemoizedFn } from '@/hooks'; import { useSaveToCollectionsDropdownContent } from '@/components/features/dropdowns/SaveToCollectionsDropdown'; -import { ShareAssetType, ShareRole } from '@/api/asset_interfaces/share'; +import { ShareAssetType } from '@/api/asset_interfaces/share'; import { useFavoriteStar } from '@/components/features/list/FavoriteStar'; import { timeout } from '@/lib'; import { ShareMenuContent } from '@/components/features/ShareMenu/ShareMenuContent'; import { DASHBOARD_TITLE_INPUT_ID } from '@/controllers/DashboardController/DashboardViewDashboardController/DashboardEditTitle'; -import { isEffectiveOwner } from '@/lib/share'; +import { canEdit, canFilter, getIsEffectiveOwner } from '@/lib/share'; +import { getShareAssetConfig } from '@/components/features/ShareMenu/helpers'; export const DashboardThreeDotMenu = React.memo(({ dashboardId }: { dashboardId: string }) => { const versionHistoryItems = useVersionHistorySelectMenu({ dashboardId }); @@ -42,19 +43,23 @@ export const DashboardThreeDotMenu = React.memo(({ dashboardId }: { dashboardId: const shareMenu = useShareMenuSelectMenu({ dashboardId }); const addContentToDashboardMenu = useAddContentToDashboardSelectMenu(); const filterDashboardMenu = useFilterDashboardSelectMenu(); + const { data: permission } = useGetDashboard(dashboardId, (x) => x.permission); + const isOwner = getIsEffectiveOwner(permission); + const isFilter = canFilter(permission); + const isEditor = canEdit(permission); const items: DropdownItems = useMemo( () => [ - filterDashboardMenu, - addContentToDashboardMenu, + isFilter && filterDashboardMenu, + isEditor && addContentToDashboardMenu, { type: 'divider' }, - shareMenu, + isOwner && shareMenu, collectionSelectMenu, favoriteDashboard, versionHistoryItems, { type: 'divider' }, - renameDashboardMenu, - deleteDashboardMenu + isEditor && renameDashboardMenu, + isOwner && deleteDashboardMenu ], [ filterDashboardMenu, @@ -63,7 +68,8 @@ export const DashboardThreeDotMenu = React.memo(({ dashboardId }: { dashboardId: collectionSelectMenu, favoriteDashboard, versionHistoryItems, - renameDashboardMenu + renameDashboardMenu, + deleteDashboardMenu ] ); @@ -211,8 +217,8 @@ const useRenameDashboardSelectMenu = ({ dashboardId }: { dashboardId: string }) }; export const useShareMenuSelectMenu = ({ dashboardId }: { dashboardId: string }) => { - const { data: dashboard } = useGetDashboard(dashboardId); - const isOwner = isEffectiveOwner(dashboard?.permission); + const { data: dashboard } = useGetDashboard(dashboardId, getShareAssetConfig); + const isOwner = getIsEffectiveOwner(dashboard?.permission); return useMemo( () => ({ diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx index 1de41760b..8587ce166 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { FileContainerButtonsProps } from '../interfaces'; import { MetricFileViewSecondary, useChatLayoutContextSelector } from '../../../ChatLayoutContext'; import { useMemoizedFn } from '@/hooks'; @@ -18,7 +18,7 @@ export const MetricContainerHeaderButtons: React.FC = const renderViewLayoutKey = useChatLayoutContextSelector((x) => x.renderViewLayoutKey); const selectedFileId = useChatIndividualContextSelector((x) => x.selectedFileId)!; const metricId = selectedFileId; - const { isFetched: isMetricFetched } = useGetMetric(metricId); + const { isFetched: isMetricFetched } = useGetMetric({ id: metricId }); if (!isMetricFetched) return null; diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx index 0c6625df5..7006df1ef 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx @@ -45,10 +45,12 @@ import { METRIC_CHART_CONTAINER_ID } from '@/controllers/MetricController/Metric import { timeout } from '@/lib'; import { METRIC_CHART_TITLE_INPUT_ID } from '@/controllers/MetricController/MetricViewChart/MetricViewChartHeader'; import { ShareMenuContent } from '@/components/features/ShareMenu/ShareMenuContent'; -import { isEffectiveOwner } from '@/lib/share'; +import { canEdit, getIsEffectiveOwner, getIsOwner } from '@/lib/share'; +import { getShareAssetConfig } from '@/components/features/ShareMenu/helpers'; export const ThreeDotMenuButton = React.memo(({ metricId }: { metricId: string }) => { const { openSuccessMessage } = useBusterNotifications(); + const { data: permission } = useGetMetric({ id: metricId }, (x) => x.permission); const onSetSelectedFile = useChatLayoutContextSelector((x) => x.onSetSelectedFile); const dashboardSelectMenu = useDashboardSelectMenu({ metricId }); const versionHistoryItems = useVersionHistorySelectMenu({ metricId }); @@ -64,27 +66,35 @@ export const ThreeDotMenuButton = React.memo(({ metricId }: { metricId: string } const renameMetricMenu = useRenameMetricSelectMenu({ metricId }); const shareMenu = useShareMenuSelectMenu({ metricId }); + const isEditor = canEdit(permission); + const isOwnerEffective = getIsEffectiveOwner(permission); + const isOwner = getIsOwner(permission); + const items: DropdownItems = useMemo( - () => [ - shareMenu, - statusSelectMenu, - { type: 'divider' }, - dashboardSelectMenu, - collectionSelectMenu, - favoriteMetric, - { type: 'divider' }, - editChartMenu, - resultsViewMenu, - sqlEditorMenu, - versionHistoryItems, - { type: 'divider' }, - downloadCSVMenu, - downloadPNGMenu, - { type: 'divider' }, - renameMetricMenu, - deleteMetricMenu - ], + () => + [ + isOwnerEffective && shareMenu, + isEditor && statusSelectMenu, + { type: 'divider' }, + dashboardSelectMenu, + collectionSelectMenu, + favoriteMetric, + { type: 'divider' }, + isEditor && editChartMenu, + resultsViewMenu, + sqlEditorMenu, + isEditor && versionHistoryItems, + { type: 'divider' }, + downloadCSVMenu, + downloadPNGMenu, + { type: 'divider' }, + isEditor && renameMetricMenu, + isOwner && deleteMetricMenu + ].filter(Boolean) as DropdownItems, [ + isEditor, + isOwner, + isOwnerEffective, renameMetricMenu, dashboardSelectMenu, deleteMetricMenu, @@ -115,7 +125,7 @@ ThreeDotMenuButton.displayName = 'ThreeDotMenuButton'; const useDashboardSelectMenu = ({ metricId }: { metricId: string }) => { const { mutateAsync: saveMetricToDashboard } = useSaveMetricToDashboard(); const { mutateAsync: removeMetricFromDashboard } = useRemoveMetricFromDashboard(); - const { data: dashboards } = useGetMetric(metricId, (x) => x.dashboards); + const { data: dashboards } = useGetMetric({ id: metricId }, (x) => x.dashboards); const onSaveToDashboard = useMemoizedFn(async (dashboardIds: string[]) => { await saveMetricToDashboard({ metricId, dashboardIds }); @@ -156,7 +166,7 @@ const useDashboardSelectMenu = ({ metricId }: { metricId: string }) => { }; const useVersionHistorySelectMenu = ({ metricId }: { metricId: string }) => { - const { data } = useGetMetric(metricId, (x) => ({ + const { data } = useGetMetric({ id: metricId }, (x) => ({ versions: x.versions, version_number: x.version_number })); @@ -185,7 +195,7 @@ const useVersionHistorySelectMenu = ({ metricId }: { metricId: string }) => { const useCollectionSelectMenu = ({ metricId }: { metricId: string }) => { const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollection(); const { mutateAsync: removeMetricFromCollection } = useRemoveMetricFromCollection(); - const { data: collections } = useGetMetric(metricId, (x) => x.collections); + const { data: collections } = useGetMetric({ id: metricId }, (x) => x.collections); const { openInfoMessage } = useBusterNotifications(); const selectedCollections = useMemo(() => { @@ -233,7 +243,7 @@ const useCollectionSelectMenu = ({ metricId }: { metricId: string }) => { }; const useStatusSelectMenu = ({ metricId }: { metricId: string }) => { - const { data: metric } = useGetMetric(metricId, (x) => x); + const { data: metric } = useGetMetric({ id: metricId }, (x) => x); const { mutateAsync: updateMetric } = useUpdateMetric(); const onChangeStatus = useMemoizedFn(async (status: VerificationStatus) => { @@ -264,7 +274,7 @@ const useStatusSelectMenu = ({ metricId }: { metricId: string }) => { }; const useFavoriteMetricSelectMenu = ({ metricId }: { metricId: string }) => { - const { data: title } = useGetMetric(metricId, (x) => x.title); + const { data: title } = useGetMetric({ id: metricId }, (x) => x.title); const { isFavorited, onFavoriteClick } = useFavoriteStar({ id: metricId, type: ShareAssetType.METRIC, @@ -341,7 +351,7 @@ const useSQLEditorSelectMenu = () => { const useDownloadCSVSelectMenu = ({ metricId }: { metricId: string }) => { const [isDownloading, setIsDownloading] = useState(false); const { data: metricData } = useGetMetricData({ id: metricId }); - const { data: title } = useGetMetric(metricId, (x) => x.title); + const { data: title } = useGetMetric({ id: metricId }, (x) => x.title); return useMemo( () => ({ @@ -363,10 +373,10 @@ const useDownloadCSVSelectMenu = ({ metricId }: { metricId: string }) => { }; const useDownloadPNGSelectMenu = ({ metricId }: { metricId: string }) => { - const { openSuccessMessage, openErrorMessage } = useBusterNotifications(); - const { data: title } = useGetMetric(metricId, (x) => x.title); + const { openErrorMessage } = useBusterNotifications(); + const { data: title } = useGetMetric({ id: metricId }, (x) => x.title); const { data: selectedChartType } = useGetMetric( - metricId, + { id: metricId }, (x) => x.chart_config?.selectedChartType ); @@ -433,25 +443,22 @@ const useRenameMetricSelectMenu = ({ metricId }: { metricId: string }) => { }; export const useShareMenuSelectMenu = ({ metricId }: { metricId: string }) => { - const { data: metric } = useGetMetric(metricId); - const isOwner = isEffectiveOwner(metric?.permission); + const { data: metric } = useGetMetric({ id: metricId }, getShareAssetConfig); return useMemo( () => ({ label: 'Share metric', value: 'share-metric', icon: , - disabled: !isOwner, - items: isOwner - ? [ - - ] - : undefined + + items: ( + + ) }), [metricId] ); diff --git a/web/src/layouts/ChatLayout/hooks/useSelectedFileAndLayout/useSelectedFileAndLayout.ts b/web/src/layouts/ChatLayout/hooks/useSelectedFileAndLayout/useSelectedFileAndLayout.ts index 7b0bb6631..ed3440083 100644 --- a/web/src/layouts/ChatLayout/hooks/useSelectedFileAndLayout/useSelectedFileAndLayout.ts +++ b/web/src/layouts/ChatLayout/hooks/useSelectedFileAndLayout/useSelectedFileAndLayout.ts @@ -4,7 +4,7 @@ import { useMemo, useState, useTransition } from 'react'; import type { ChatLayoutView, SelectedFile } from '../../interfaces'; import { usePathname } from 'next/navigation'; import { parsePathnameSegments } from './parsePathnameSegments'; -import { useMemoizedFn } from '@/hooks'; +import { useMemoizedFn, useWhyDidYouUpdate } from '@/hooks'; import { createChatAssetRoute, createChatRoute } from '../../ChatLayoutContext/helpers'; import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; import { initializeSelectedFile } from './initializeSelectedFile'; @@ -67,21 +67,23 @@ export const useSelectedFileAndLayout = ({ setRenderViewLayoutKey('both'); setSelectedFile(file); await onChangePage(route); - startTransition(() => { onChangePage(route); //this is hack for now... animateOpenSplitter(isSameAsCurrentFile ? 'left' : 'both'); }); }); - return { - onSetSelectedFile, - selectedFile, - selectedLayout, - chatId, - renderViewLayoutKey, - setRenderViewLayoutKey - }; + return useMemo( + () => ({ + onSetSelectedFile, + selectedFile, + selectedLayout, + chatId, + renderViewLayoutKey, + setRenderViewLayoutKey + }), + [onSetSelectedFile, selectedFile, selectedLayout, chatId, renderViewLayoutKey] + ); }; export type SelectedFileParams = ReturnType; diff --git a/web/src/lib/metrics/saveToServerHelpers.ts b/web/src/lib/metrics/saveToServerHelpers.ts index fb4bf845d..fcb325eb6 100644 --- a/web/src/lib/metrics/saveToServerHelpers.ts +++ b/web/src/lib/metrics/saveToServerHelpers.ts @@ -22,7 +22,7 @@ const DEFAULT_COLUMN_SETTINGS_ENTRIES = Object.entries(DEFAULT_COLUMN_SETTINGS); const DEFAULT_COLUMN_LABEL_FORMATS_ENTRIES = Object.entries(DEFAULT_COLUMN_LABEL_FORMAT); const getChangedTopLevelMessageValues = (newMetric: IBusterMetric, oldMetric: IBusterMetric) => { - return getChangedValues(oldMetric, newMetric, ['title', 'feedback', 'status', 'code', 'file']); + return getChangedValues(oldMetric, newMetric, ['title', 'feedback', 'status', 'sql', 'file']); }; const keySpecificHandlers: Partial any>> = { diff --git a/web/src/lib/share.ts b/web/src/lib/share.ts index 82121cf43..f183e3486 100644 --- a/web/src/lib/share.ts +++ b/web/src/lib/share.ts @@ -1,10 +1,10 @@ import { ShareRole } from '@/api/asset_interfaces'; -export const isOwner = (role: ShareRole | null | undefined) => { +export const getIsOwner = (role: ShareRole | null | undefined) => { return role === ShareRole.OWNER; }; -export const isEffectiveOwner = (role: ShareRole | null | undefined) => { +export const getIsEffectiveOwner = (role: ShareRole | null | undefined) => { return role === ShareRole.FULL_ACCESS || role === ShareRole.OWNER; }; @@ -16,7 +16,7 @@ export const canShare = (role: ShareRole | null | undefined) => { return role === ShareRole.FULL_ACCESS || role === ShareRole.OWNER; }; -export const canFilter = (role: ShareRole) => { +export const canFilter = (role: ShareRole | null | undefined) => { return ( role === ShareRole.CAN_FILTER || role === ShareRole.FULL_ACCESS || diff --git a/web/src/mocks/metric.ts b/web/src/mocks/metric.ts index 61c379c6c..d728dcff1 100644 --- a/web/src/mocks/metric.ts +++ b/web/src/mocks/metric.ts @@ -140,7 +140,7 @@ export const createMockMetric = (id: string): IBusterMetric => { sent_by_id: '', sent_by_name: '', sent_by_avatar_url: '', - code: `WITH records AS ( + sql: `WITH records AS ( SELECT response_time_id, interaction_id,