From de11467bf0ee33de3c6b413b7e95cc16d310a5a2 Mon Sep 17 00:00:00 2001 From: "Praveen K.B" Date: Wed, 23 Oct 2024 11:18:40 +0530 Subject: [PATCH 1/8] Fixed the dashboard tile loading issue (#344) --- src/api/dashboard.ts | 6 +++--- src/hooks/useDashboards.tsx | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/api/dashboard.ts b/src/api/dashboard.ts index f6f840b2..7ed9f237 100644 --- a/src/api/dashboard.ts +++ b/src/api/dashboard.ts @@ -28,7 +28,7 @@ export const putDashboard = (dashboardId: string, dashboard: UpdateDashboardType }; export const postDashboard = (dashboard: CreateDashboardType | ImportDashboardType) => { - return Axios().post(CREATE_DASHBOARDS_URL, { ...dashboard}); + return Axios().post(CREATE_DASHBOARDS_URL, { ...dashboard }); }; export const removeDashboard = (dashboardId: string) => { @@ -37,7 +37,7 @@ export const removeDashboard = (dashboardId: string) => { // using just for the dashboard tile now // refactor once the fields are included in the /query in the response -export const getQueryData = (opts?: TileQuery) => { +export const getQueryData = (opts?: TileQuery, signal?: AbortSignal) => { if (_.isEmpty(opts)) throw 'Invalid Arguments'; const { query, startTime, endTime } = opts; @@ -48,6 +48,6 @@ export const getQueryData = (opts?: TileQuery) => { startTime, endTime: optimizeEndTime(endTime), }, - {}, + { signal }, ); }; diff --git a/src/hooks/useDashboards.tsx b/src/hooks/useDashboards.tsx index d24bda84..2bb79513 100644 --- a/src/hooks/useDashboards.tsx +++ b/src/hooks/useDashboards.tsx @@ -4,7 +4,7 @@ import { AxiosError, isAxiosError } from 'axios'; import _ from 'lodash'; import { useDashboardsStore, dashboardsStoreReducers } from '@/pages/Dashboards/providers/DashboardsProvider'; import { getDashboards, getQueryData, postDashboard, putDashboard, removeDashboard } from '@/api/dashboard'; -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { CreateDashboardType, Dashboard, @@ -153,17 +153,31 @@ export const useTileQuery = (opts?: { tileId?: string; onSuccess?: (data: TileQu isError: null | boolean; isSuccess: null | boolean; }>({ isLoading: false, isError: null, isSuccess: null }); + const abortControllerRef = useRef(new AbortController()); + + useEffect(() => { + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); const fetchTileData = useCallback( async (queryOpts: TileQuery) => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + abortControllerRef.current = new AbortController(); try { setFetchState({ isLoading: true, isError: null, isSuccess: null }); - const res = await getQueryData(queryOpts); + const res = await getQueryData(queryOpts, abortControllerRef.current.signal); const tileData = _.isEmpty(res) ? { records: [], fields: [] } : res.data; opts?.tileId && setDashboardsStore((store) => setTileData(store, opts.tileId || '', tileData)); opts?.onSuccess && opts.onSuccess(tileData); setFetchState({ isLoading: false, isError: false, isSuccess: true }); } catch (e: any) { + if (e.name == 'CanceledError') return; setFetchState({ isLoading: false, isError: true, isSuccess: false }); notifyError({ message: _.isString(e.response.data) ? e.response.data : 'Unable to fetch tile data' }); } From f11483ed2cf15a331dc29dcf2f25664d7d3f5df7 Mon Sep 17 00:00:00 2001 From: "Praveen K.B" Date: Wed, 23 Oct 2024 11:27:34 +0530 Subject: [PATCH 2/8] Changed the equlaity to deep equality --- src/hooks/useDashboards.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useDashboards.tsx b/src/hooks/useDashboards.tsx index 2bb79513..626a0a13 100644 --- a/src/hooks/useDashboards.tsx +++ b/src/hooks/useDashboards.tsx @@ -177,7 +177,7 @@ export const useTileQuery = (opts?: { tileId?: string; onSuccess?: (data: TileQu opts?.onSuccess && opts.onSuccess(tileData); setFetchState({ isLoading: false, isError: false, isSuccess: true }); } catch (e: any) { - if (e.name == 'CanceledError') return; + if (e.name === 'CanceledError') return; setFetchState({ isLoading: false, isError: true, isSuccess: false }); notifyError({ message: _.isString(e.response.data) ? e.response.data : 'Unable to fetch tile data' }); } From 8f962c95a58cb6554386796a5a6f1950122532c2 Mon Sep 17 00:00:00 2001 From: "Praveen K.B" Date: Wed, 23 Oct 2024 16:43:38 +0530 Subject: [PATCH 3/8] Used react-query to make the API call. --- src/hooks/useDashboards.tsx | 82 ++++++++++++++----------- src/pages/Dashboards/CreateTileForm.tsx | 12 +++- src/pages/Dashboards/Tile.tsx | 39 +++++++----- 3 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/hooks/useDashboards.tsx b/src/hooks/useDashboards.tsx index 626a0a13..215106c6 100644 --- a/src/hooks/useDashboards.tsx +++ b/src/hooks/useDashboards.tsx @@ -4,7 +4,6 @@ import { AxiosError, isAxiosError } from 'axios'; import _ from 'lodash'; import { useDashboardsStore, dashboardsStoreReducers } from '@/pages/Dashboards/providers/DashboardsProvider'; import { getDashboards, getQueryData, postDashboard, putDashboard, removeDashboard } from '@/api/dashboard'; -import { useCallback, useEffect, useRef, useState } from 'react'; import { CreateDashboardType, Dashboard, @@ -145,48 +144,57 @@ export const useDashboardsQuery = (opts: { updateTimeRange?: (dashboard: Dashboa }; }; -export const useTileQuery = (opts?: { tileId?: string; onSuccess?: (data: TileQueryResponse) => void }) => { +export const useTileQuery = (opts: { + tileId?: string; + query: string; + startTime: Date; + endTime: Date; + onSuccess?: (data: TileQueryResponse) => void; + enabled?: boolean; +}) => { const [, setDashboardsStore] = useDashboardsStore((_store) => null); - const { onSuccess } = opts || {}; - const [fetchState, setFetchState] = useState<{ - isLoading: boolean; - isError: null | boolean; - isSuccess: null | boolean; - }>({ isLoading: false, isError: null, isSuccess: null }); - const abortControllerRef = useRef(new AbortController()); + const { onSuccess, query, startTime, endTime, tileId, enabled = true } = opts; - useEffect(() => { - return () => { - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - }; - }, []); + const fetchTileData = async (queryOpts: TileQuery, signal: AbortSignal | undefined) => { + const res = await getQueryData(queryOpts, signal); + const tileData = _.isEmpty(res) ? { records: [], fields: [] } : res.data; + if (tileId) { + setDashboardsStore((store) => setTileData(store, tileId, tileData)); + } + return tileData; + }; - const fetchTileData = useCallback( - async (queryOpts: TileQuery) => { - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - abortControllerRef.current = new AbortController(); - try { - setFetchState({ isLoading: true, isError: null, isSuccess: null }); - const res = await getQueryData(queryOpts, abortControllerRef.current.signal); - const tileData = _.isEmpty(res) ? { records: [], fields: [] } : res.data; - opts?.tileId && setDashboardsStore((store) => setTileData(store, opts.tileId || '', tileData)); - opts?.onSuccess && opts.onSuccess(tileData); - setFetchState({ isLoading: false, isError: false, isSuccess: true }); - } catch (e: any) { - if (e.name === 'CanceledError') return; - setFetchState({ isLoading: false, isError: true, isSuccess: false }); - notifyError({ message: _.isString(e.response.data) ? e.response.data : 'Unable to fetch tile data' }); - } + const { data, isLoading, isError, refetch } = useQuery( + [tileId, startTime, endTime], + async ({ signal }) => { + const tileData = await fetchTileData( + { + query: query, + startTime: startTime, + endTime: endTime, + }, + signal, + ); + return tileData; + }, + { + onSuccess, + onError: (error: AxiosError) => { + if (isAxiosError(error) && error.response) { + notifyError({ + message: _.isString(error.response.data) ? error.response.data : 'Unable to fetch tile data', + }); + } + }, + refetchOnWindowFocus: false, + enabled, }, - [onSuccess], ); return { - ...fetchState, - fetchTileData, + data, + isLoading, + isError, + refetch, }; }; diff --git a/src/pages/Dashboards/CreateTileForm.tsx b/src/pages/Dashboards/CreateTileForm.tsx index f834543d..a77b3ea0 100644 --- a/src/pages/Dashboards/CreateTileForm.tsx +++ b/src/pages/Dashboards/CreateTileForm.tsx @@ -326,7 +326,13 @@ const Query = (props: { form: TileFormType; onChangeValue: (key: string, value: props.form.validate(); }, []); - const { fetchTileData, isLoading } = useTileQuery({ onSuccess: onFetchTileSuccess }); + const { refetch, isLoading } = useTileQuery({ + onSuccess: onFetchTileSuccess, + query: '', // Initial query (will be updated in refetch) + startTime: timeRange.startTime, + endTime: timeRange.endTime, + enabled: false, + }); const validateQuery = useCallback(() => { if (_.isEmpty(dashboardId)) return; @@ -337,7 +343,9 @@ const Query = (props: { form: TileFormType; onChangeValue: (key: string, value: const santizedQuery = sanitiseSqlString(query, true, 100); onChangeValue('query', santizedQuery); const { from, to } = selectedDashboard.time_filter || { from: timeRange.startTime, to: timeRange.endTime }; - fetchTileData({ query: santizedQuery, startTime: dayjs(from).toDate(), endTime: dayjs(to).toDate() }); + refetch({ + queryKey: [santizedQuery, dayjs(from).toDate(), dayjs(to).toDate()], + }); }, [query, dashboardId, dashboards, timeRange]); const onStreamSelect = useCallback((val: string | null) => { diff --git a/src/pages/Dashboards/Tile.tsx b/src/pages/Dashboards/Tile.tsx index 2b3f6953..43561f25 100644 --- a/src/pages/Dashboards/Tile.tsx +++ b/src/pages/Dashboards/Tile.tsx @@ -23,7 +23,7 @@ import handleCapture, { makeExportClassName } from '@/utils/exportImage'; import { useDashboardsStore, dashboardsStoreReducers } from './providers/DashboardsProvider'; import _ from 'lodash'; import { useTileQuery } from '@/hooks/useDashboards'; -import { useCallback, useEffect } from 'react'; +import { useCallback } from 'react'; import { Tile as TileType, TileQueryResponse } from '@/@types/parseable/api/dashboards'; import { sanitiseSqlString } from '@/utils/sanitiseSqlString'; import Table from './Table'; @@ -115,13 +115,23 @@ const Graph = (props: { tile: TileType; data: TileQueryResponse }) => { const y_keys = _.get(graph_config, 'y_keys', []); const xUnit = getUnitTypeByKey(x_key, tick_config); const yUnit = getUnitTypeByKey(_.head(y_keys) || '', tick_config); - const orientation = _.get(graph_config, 'orientation', 'horizontal'); - const graphBasicType = _.get(graph_config, 'graph_type', 'default') - const color_config = _.get(props.tile.visualization, 'color_config', []) + const orientation = _.get(graph_config, 'orientation', 'horizontal'); + const graphBasicType = _.get(graph_config, 'graph_type', 'default'); + const color_config = _.get(props.tile.visualization, 'color_config', []); return ( - {renderGraph({ queryResponse: data, x_key, y_keys, chart: visualization_type, yUnit, xUnit, orientation, graphBasicType, color_config })} + {renderGraph({ + queryResponse: data, + x_key, + y_keys, + chart: visualization_type, + yUnit, + xUnit, + orientation, + graphBasicType, + color_config, + })} ); }; @@ -247,23 +257,22 @@ function TileControls(props: { tile: TileType; data: TileQueryResponse }) { } const Tile = (props: { id: string }) => { - // const [showJson, setShowJson] = useState(false); const [timeRange] = useLogsStore((store) => store.timeRange); const [activeDashboard] = useDashboardsStore((store) => store.activeDashboard); - const [tilesData] = useDashboardsStore((store) => store.tilesData); - const tileData = _.get(tilesData, props.id, { records: [], fields: [] }); const tile = _.chain(activeDashboard) .get('tiles', []) .find((tile) => tile.tile_id === props.id) .value(); - const { fetchTileData, isLoading } = useTileQuery({ tileId: props.id }); + const shouldNotify = false; + const santizedQuery = sanitiseSqlString(tile.query, shouldNotify, 100); - useEffect(() => { - const shouldNotify = false; - const santizedQuery = sanitiseSqlString(tile.query, shouldNotify, 100); - fetchTileData({ query: santizedQuery, startTime: timeRange.startTime, endTime: timeRange.endTime }); - }, [timeRange.startTime, timeRange.endTime]); + const { data: tileData, isLoading } = useTileQuery({ + tileId: props.id, + query: santizedQuery, + startTime: timeRange.startTime, + endTime: timeRange.endTime, + }); // const toggleJsonView = useCallback(() => { // return setShowJson((prev) => !prev); @@ -289,7 +298,7 @@ const Tile = (props: { id: string }) => { {timeRange.label} - + {tileData && } {isLoading && } From fa634c42d7a43089ab4a0ed957f71831b7f154ce Mon Sep 17 00:00:00 2001 From: "Praveen K.B" Date: Wed, 23 Oct 2024 16:48:01 +0530 Subject: [PATCH 4/8] Resolved TS type issues --- src/hooks/useDashboards.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useDashboards.tsx b/src/hooks/useDashboards.tsx index 215106c6..48412053 100644 --- a/src/hooks/useDashboards.tsx +++ b/src/hooks/useDashboards.tsx @@ -155,7 +155,7 @@ export const useTileQuery = (opts: { const [, setDashboardsStore] = useDashboardsStore((_store) => null); const { onSuccess, query, startTime, endTime, tileId, enabled = true } = opts; - const fetchTileData = async (queryOpts: TileQuery, signal: AbortSignal | undefined) => { + const fetchTileData = async (queryOpts: TileQuery, signal?: AbortSignal) => { const res = await getQueryData(queryOpts, signal); const tileData = _.isEmpty(res) ? { records: [], fields: [] } : res.data; if (tileId) { @@ -169,9 +169,9 @@ export const useTileQuery = (opts: { async ({ signal }) => { const tileData = await fetchTileData( { - query: query, - startTime: startTime, - endTime: endTime, + query, + startTime, + endTime, }, signal, ); From 77ee9efe1c1b7fe5b31e4c53b1a1d70e1b579d29 Mon Sep 17 00:00:00 2001 From: "Praveen K.B" Date: Wed, 23 Oct 2024 21:14:19 +0530 Subject: [PATCH 5/8] Removed AbortSignal implementation --- src/api/dashboard.ts | 4 ++-- src/hooks/useDashboards.tsx | 25 ++++++++++--------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/api/dashboard.ts b/src/api/dashboard.ts index 7ed9f237..f2761ba7 100644 --- a/src/api/dashboard.ts +++ b/src/api/dashboard.ts @@ -37,7 +37,7 @@ export const removeDashboard = (dashboardId: string) => { // using just for the dashboard tile now // refactor once the fields are included in the /query in the response -export const getQueryData = (opts?: TileQuery, signal?: AbortSignal) => { +export const getQueryData = (opts?: TileQuery) => { if (_.isEmpty(opts)) throw 'Invalid Arguments'; const { query, startTime, endTime } = opts; @@ -48,6 +48,6 @@ export const getQueryData = (opts?: TileQuery, signal?: AbortSignal) => { startTime, endTime: optimizeEndTime(endTime), }, - { signal }, + {}, ); }; diff --git a/src/hooks/useDashboards.tsx b/src/hooks/useDashboards.tsx index 48412053..35bf3820 100644 --- a/src/hooks/useDashboards.tsx +++ b/src/hooks/useDashboards.tsx @@ -155,8 +155,8 @@ export const useTileQuery = (opts: { const [, setDashboardsStore] = useDashboardsStore((_store) => null); const { onSuccess, query, startTime, endTime, tileId, enabled = true } = opts; - const fetchTileData = async (queryOpts: TileQuery, signal?: AbortSignal) => { - const res = await getQueryData(queryOpts, signal); + const fetchTileData = async (queryOpts: TileQuery) => { + const res = await getQueryData(queryOpts); const tileData = _.isEmpty(res) ? { records: [], fields: [] } : res.data; if (tileId) { setDashboardsStore((store) => setTileData(store, tileId, tileData)); @@ -164,19 +164,14 @@ export const useTileQuery = (opts: { return tileData; }; - const { data, isLoading, isError, refetch } = useQuery( - [tileId, startTime, endTime], - async ({ signal }) => { - const tileData = await fetchTileData( - { - query, - startTime, - endTime, - }, - signal, - ); - return tileData; - }, + const { data, isLoading, isError, refetch } = useQuery( + [tileId, query, startTime, endTime], + () => + fetchTileData({ + query, + startTime, + endTime, + }), { onSuccess, onError: (error: AxiosError) => { From 2ba0d4b73ba39741804a28becd5f65ed7ace6afc Mon Sep 17 00:00:00 2001 From: "Praveen K.B" Date: Thu, 24 Oct 2024 16:49:40 +0530 Subject: [PATCH 6/8] Handled tileData via store. --- src/hooks/useDashboards.tsx | 25 +++++++++---------------- src/pages/Dashboards/Tile.tsx | 4 +++- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/hooks/useDashboards.tsx b/src/hooks/useDashboards.tsx index 35bf3820..9ef490c5 100644 --- a/src/hooks/useDashboards.tsx +++ b/src/hooks/useDashboards.tsx @@ -8,7 +8,6 @@ import { CreateDashboardType, Dashboard, ImportDashboardType, - TileQuery, TileQueryResponse, UpdateDashboardType, } from '@/@types/parseable/api/dashboards'; @@ -153,27 +152,22 @@ export const useTileQuery = (opts: { enabled?: boolean; }) => { const [, setDashboardsStore] = useDashboardsStore((_store) => null); - const { onSuccess, query, startTime, endTime, tileId, enabled = true } = opts; - - const fetchTileData = async (queryOpts: TileQuery) => { - const res = await getQueryData(queryOpts); - const tileData = _.isEmpty(res) ? { records: [], fields: [] } : res.data; - if (tileId) { - setDashboardsStore((store) => setTileData(store, tileId, tileData)); - } - return tileData; - }; - - const { data, isLoading, isError, refetch } = useQuery( + const { query, startTime, endTime, tileId, enabled = true } = opts; + const { isLoading, isError, refetch } = useQuery( [tileId, query, startTime, endTime], () => - fetchTileData({ + getQueryData({ query, startTime, endTime, }), { - onSuccess, + onSuccess: (res) => { + const tileData = _.isEmpty(res) ? { records: [], fields: [] } : res.data; + if (tileId) { + setDashboardsStore((store) => setTileData(store, tileId, tileData)); + } + }, onError: (error: AxiosError) => { if (isAxiosError(error) && error.response) { notifyError({ @@ -187,7 +181,6 @@ export const useTileQuery = (opts: { ); return { - data, isLoading, isError, refetch, diff --git a/src/pages/Dashboards/Tile.tsx b/src/pages/Dashboards/Tile.tsx index 43561f25..ba34d333 100644 --- a/src/pages/Dashboards/Tile.tsx +++ b/src/pages/Dashboards/Tile.tsx @@ -258,6 +258,8 @@ function TileControls(props: { tile: TileType; data: TileQueryResponse }) { const Tile = (props: { id: string }) => { const [timeRange] = useLogsStore((store) => store.timeRange); + const [tilesData] = useDashboardsStore((store) => store.tilesData); + const tileData = _.get(tilesData, props.id, { records: [], fields: [] }); const [activeDashboard] = useDashboardsStore((store) => store.activeDashboard); const tile = _.chain(activeDashboard) .get('tiles', []) @@ -267,7 +269,7 @@ const Tile = (props: { id: string }) => { const shouldNotify = false; const santizedQuery = sanitiseSqlString(tile.query, shouldNotify, 100); - const { data: tileData, isLoading } = useTileQuery({ + const { isLoading } = useTileQuery({ tileId: props.id, query: santizedQuery, startTime: timeRange.startTime, From 54a50df620767e95cbc372a308af448dc267a039 Mon Sep 17 00:00:00 2001 From: "Praveen K.B" Date: Thu, 24 Oct 2024 16:56:52 +0530 Subject: [PATCH 7/8] Added isFetching for useQuery to fix the loading states --- src/hooks/useDashboards.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hooks/useDashboards.tsx b/src/hooks/useDashboards.tsx index 9ef490c5..3b6a5426 100644 --- a/src/hooks/useDashboards.tsx +++ b/src/hooks/useDashboards.tsx @@ -153,7 +153,7 @@ export const useTileQuery = (opts: { }) => { const [, setDashboardsStore] = useDashboardsStore((_store) => null); const { query, startTime, endTime, tileId, enabled = true } = opts; - const { isLoading, isError, refetch } = useQuery( + const { isLoading, isFetching, isError, refetch } = useQuery( [tileId, query, startTime, endTime], () => getQueryData({ @@ -180,8 +180,10 @@ export const useTileQuery = (opts: { }, ); + const isLoadingState = isLoading || isFetching; + return { - isLoading, + isLoading: isLoadingState, isError, refetch, }; From 06cba6697dc711560c884acaeedfbf46656eceee Mon Sep 17 00:00:00 2001 From: "Praveen K.B" Date: Thu, 24 Oct 2024 17:01:09 +0530 Subject: [PATCH 8/8] Removed unwanted tilesData check --- src/pages/Dashboards/Tile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Dashboards/Tile.tsx b/src/pages/Dashboards/Tile.tsx index ba34d333..6c4fd90f 100644 --- a/src/pages/Dashboards/Tile.tsx +++ b/src/pages/Dashboards/Tile.tsx @@ -300,7 +300,7 @@ const Tile = (props: { id: string }) => { {timeRange.label} - {tileData && } + {isLoading && }