From 7a153e583934e5a8ca39e715ed8c114f5008e039 Mon Sep 17 00:00:00 2001 From: akshaykumar2505 Date: Thu, 6 Aug 2026 16:27:54 +0530 Subject: [PATCH] refactor(Explore): update styles and improve clipboard functionality - Changed button styles in ExploreDataSection for better visibility and user experience. - Enhanced SQL copy functionality to provide user feedback upon successful copy. - Updated reset button behavior to clear selections instead of resetting queries. - Introduced hasPlaygroundSelection utility to manage selection state more effectively. Co-authored-by: Cursor --- .../ExploreDataSection/index.module.less | 12 +++--- src/components/ExploreDataSection/index.tsx | 22 +++++----- src/components/ExploreSettingsForm/index.tsx | 10 ++++- src/components/ExploreWorkspace/index.tsx | 14 ++++--- src/hooks/useAnalyticsQuery.ts | 7 ++++ src/hooks/usePlayground.ts | 42 ++++++++++++++++--- 6 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/components/ExploreDataSection/index.module.less b/src/components/ExploreDataSection/index.module.less index a4f7da1f..9ce6113e 100644 --- a/src/components/ExploreDataSection/index.module.less +++ b/src/components/ExploreDataSection/index.module.less @@ -145,15 +145,15 @@ align-items: center; justify-content: center; white-space: nowrap; - color: #fff; - background: #4a7faa; - border-color: #4a7faa; + color: rgba(0, 0, 0, 0.85); + background: #fff; + border-color: #d9d9d9; &:hover, &:focus { - color: #fff !important; - background: #3d6d93 !important; - border-color: #3d6d93 !important; + color: rgba(0, 0, 0, 0.85) !important; + background: #fff !important; + border-color: #4a7faa !important; } } diff --git a/src/components/ExploreDataSection/index.tsx b/src/components/ExploreDataSection/index.tsx index 227b4d83..85321451 100644 --- a/src/components/ExploreDataSection/index.tsx +++ b/src/components/ExploreDataSection/index.tsx @@ -7,6 +7,7 @@ import { useEffect, useRef, useState } from "react"; import useAnalyticsQueryMembers from "@/hooks/useAnalyticsQueryMembers"; import useFormatExport from "@/hooks/useFormatExport"; +import { hasPlaygroundSelection } from "@/hooks/useAnalyticsQuery"; import Button from "@/components/Button"; import VirtualTable, { cellRenderer } from "@/components/VirtualTable"; import PrismCode from "@/components/PrismCode"; @@ -312,7 +313,11 @@ const ExploreDataSection: FC = (props) => { type="default" className={s.sqlCopy} icon={} - onClick={() => navigator.clipboard.writeText(rawSql.sql || "")} + onClick={() => { + navigator.clipboard.writeText(rawSql.sql || "").then(() => { + message.success("SQL copied to clipboard"); + }); + }} > Copy @@ -326,6 +331,8 @@ const ExploreDataSection: FC = (props) => { ); }, [empty, queryState, t]); + const hasSelection = hasPlaygroundSelection(playgroundState); + const RestApi = useMemo(() => { if (dataSource?.id && currentBranch?.id) { return ( @@ -350,11 +357,6 @@ const ExploreDataSection: FC = (props) => { updateState, ]); - const handleResetQuery = () => { - onResetQuery?.(); - message.success("Selection cleared"); - }; - const onChange = (values: Partial) => { if (values.limit !== limit) { onQueryChange("limit", values.limit); @@ -386,11 +388,11 @@ const ExploreDataSection: FC = (props) => { {onResetQuery && ( )} {queryTimeMs != null && !loading && ( diff --git a/src/components/ExploreSettingsForm/index.tsx b/src/components/ExploreSettingsForm/index.tsx index c6da0593..fe6c74a6 100644 --- a/src/components/ExploreSettingsForm/index.tsx +++ b/src/components/ExploreSettingsForm/index.tsx @@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next"; import { useForm } from "react-hook-form"; import cn from "classnames"; import { useResponsive } from "ahooks"; +import { useEffect } from "react"; import Input from "@/components/Input"; @@ -31,10 +32,17 @@ const ExploreSettingsForm: FC = ({ const windowSize = useResponsive(); const isMobile = windowSize.lg === false; - const { control, watch } = useForm({ + const { control, watch, reset } = useForm({ defaultValues, }); + useEffect(() => { + reset({ + limit: defaultValues.limit, + offset: defaultValues.offset, + }); + }, [defaultValues.limit, defaultValues.offset, reset]); + useEffect(() => { const { unsubscribe } = watch((value) => { onChange(value); diff --git a/src/components/ExploreWorkspace/index.tsx b/src/components/ExploreWorkspace/index.tsx index 78cec235..77dabd1c 100644 --- a/src/components/ExploreWorkspace/index.tsx +++ b/src/components/ExploreWorkspace/index.tsx @@ -1,12 +1,11 @@ -import { Spin } from "antd"; -import { useTranslation } from "react-i18next"; +import { Spin, message } from "antd"; +import { useCallback, useMemo } from "react"; import SidebarLayout from "@/layouts/SidebarLayout"; import ExploreDataSection from "@/components/ExploreDataSection"; import ErrorFound from "@/components/ErrorFound"; import ExploreCubes from "@/components/ExploreCubes"; import usePlayground, { queryStateKeys } from "@/hooks/usePlayground"; -import { initialState } from "@/hooks/useAnalyticsQuery"; import useExploreWorkspace from "@/hooks/useExploreWorkspace"; import useDimensions from "@/hooks/useDimensions"; import useLocation from "@/hooks/useLocation"; @@ -61,7 +60,6 @@ const ExploreWorkspace: FC = (props) => { icon, } = props; - const { t } = useTranslation(["common"]); const [location, setLocation] = useLocation(); const { screenshotMode } = location?.query || {}; const isScreenshotMode = screenshotMode !== undefined; @@ -89,6 +87,7 @@ const ExploreWorkspace: FC = (props) => { settings, dispatchSettings, selectors, + clearSelection, } = usePlayground({ explorationData, meta: meta.data, @@ -100,6 +99,11 @@ const ExploreWorkspace: FC = (props) => { selectedQueryMembers, }); + const handleClearSelection = useCallback(() => { + clearSelection(); + message.success("Selection cleared"); + }, [clearSelection]); + const tableHeight = useMemo( () => DEFAULT_ROW_HEIGHT * explorationState.rows.length + DEFAULT_HEADER_HEIGHT, @@ -190,7 +194,7 @@ const ExploreWorkspace: FC = (props) => { onExec={onRunQuery} onQueryChange={onQueryChange} onApplyQuery={doReset} - onResetQuery={() => doReset(initialState)} + onResetQuery={handleClearSelection} onOpenModal={onOpenModal} disabled={!isQueryChanged} state={state} diff --git a/src/hooks/useAnalyticsQuery.ts b/src/hooks/useAnalyticsQuery.ts index 6d8613db..015b21ef 100644 --- a/src/hooks/useAnalyticsQuery.ts +++ b/src/hooks/useAnalyticsQuery.ts @@ -205,6 +205,13 @@ export const initialState: PlaygroundState = { ...queryState, }; +export const hasPlaygroundSelection = (state: PlaygroundState): boolean => + state.measures.length > 0 || + state.dimensions.length > 0 || + state.filters.length > 0 || + state.timeDimensions.length > 0 || + state.segments.length > 0; + const getName = (member: { name?: string }): any => member.name; const getOperatorType = (member: CubeMember) => diff --git a/src/hooks/usePlayground.ts b/src/hooks/usePlayground.ts index bd1d8ade..bdd2a1a7 100644 --- a/src/hooks/usePlayground.ts +++ b/src/hooks/usePlayground.ts @@ -1,4 +1,11 @@ -import { useMemo, useState, useEffect, useReducer, useCallback } from "react"; +import { + useMemo, + useState, + useEffect, + useReducer, + useCallback, + useRef, +} from "react"; import { useDeepCompareEffect } from "ahooks"; import useDataSourceMeta from "@/hooks/useDataSourcesMeta"; @@ -234,6 +241,7 @@ export default ({ meta = [], explorationData, rawSql }: Props) => { ); const [isQueryChanged, setChangedStatus] = useState(false); + const syncedExplorationIdRef = useRef(); useEffect(() => { const playgroundState = exploration?.playground_state || queryState; @@ -248,21 +256,44 @@ export default ({ meta = [], explorationData, rawSql }: Props) => { } }, [isQueryChanged, currPlaygroundState, exploration, selectorDirty]); + // Hydrate from saved exploration only when opening a (new) exploration — not on + // every refetch, so Apply to Explore / local edits are not overwritten by stale DB state. useEffect(() => { + const id = exploration?.id; const newState = exploration?.playground_state; - if (newState) { - doReset(newState as unknown as PlaygroundState); - setSelectorDirty(false); + if (!id || !newState || syncedExplorationIdRef.current === id) { + return; } - }, [exploration?.playground_state, doReset]); + + doReset(newState as unknown as PlaygroundState); + setSelectorDirty(false); + syncedExplorationIdRef.current = id; + }, [exploration?.id, exploration?.playground_state, doReset]); useEffect(() => { if (!exploration?.id) { doReset(initialState); + syncedExplorationIdRef.current = undefined; } }, [exploration?.id, doReset]); + const clearSelection = useCallback(() => { + doReset({ + measures: [], + dimensions: [], + filters: [], + timeDimensions: [], + segments: [], + order: [], + timezone: "UTC", + limit: 1000, + offset: 0, + }); + setSelectorValues({}); + setSelectorDirty(false); + }, [doReset]); + return { state: explorationState, selectedQueryMembers, @@ -286,5 +317,6 @@ export default ({ meta = [], explorationData, rawSql }: Props) => { setSelectorValue, selectorFilters, }, + clearSelection, }; };