drizzle-cube
Version:
Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.
488 lines (487 loc) • 20.7 kB
TypeScript
import { ReactNode } from 'react';
import { StoreApi } from 'zustand';
import { Filter, ChartType, ChartAxisConfig, ChartDisplayConfig, CubeQuery, QueryMergeStrategy, MultiQueryConfig, FunnelBindingKey, FunnelConfig, AnalysisType, FunnelStepState } from '../types';
import { ServerFunnelQuery } from '../types/funnel';
import { ServerFlowQuery, FlowStartingStep } from '../types/flow';
import { AnalysisBuilderState, QueryPanelTab, AIState } from '../components/AnalysisBuilder/types';
import { AnalysisConfig, ChartConfig, AnalysisWorkspace } from '../types/analysisConfig';
/**
* Field modal mode for field search
*/
export type FieldModalMode = 'metrics' | 'breakdown';
/**
* Complete store state interface
*/
export interface AnalysisBuilderStoreState {
/** Explicit analysis type - determines which mode is active */
analysisType: AnalysisType;
/**
* Per-mode chart configuration map.
* Each mode owns its own chart settings, enabling mode switching
* without losing chart configurations.
*/
charts: {
[K in AnalysisType]?: ChartConfig;
};
/**
* Per-mode active view (table or chart) map.
* Each mode owns its own view preference, enabling mode switching
* without losing view preferences.
*/
activeViews: {
[K in AnalysisType]?: 'table' | 'chart';
};
/** Array of query states (one per tab) */
queryStates: AnalysisBuilderState[];
/** Index of the currently active query tab */
activeQueryIndex: number;
/** Strategy for merging multi-query results (used when queryStates.length > 1) */
mergeStrategy: QueryMergeStrategy;
/** Whether user manually selected chart type (disables auto-switch) */
userManuallySelectedChart: boolean;
/** Current color palette name */
localPaletteName: string;
/** Active tab in query panel */
activeTab: QueryPanelTab;
/** Active view (table or chart) */
activeView: 'table' | 'chart';
/** Display limit for table */
displayLimit: number;
/** Whether field search modal is open */
showFieldModal: boolean;
/** Current mode for field search modal */
fieldModalMode: FieldModalMode;
/** AI panel state */
aiState: AIState;
/** Selected cube for funnel mode (all steps use this cube) */
funnelCube: string | null;
/** Dedicated funnel steps (separate from queryStates) */
funnelSteps: FunnelStepState[];
/** Index of currently active funnel step */
activeFunnelStepIndex: number;
/** Time dimension for funnel temporal ordering */
funnelTimeDimension: string | null;
/** Binding key dimension that links funnel steps together */
funnelBindingKey: FunnelBindingKey | null;
/** @deprecated Use funnelSteps[].timeToConvert instead - kept for backward compat */
stepTimeToConvert: (string | null)[];
/** Selected cube for flow mode (must be an eventStream cube) */
flowCube: string | null;
/** Binding key that links events to entities */
flowBindingKey: FunnelBindingKey | null;
/** Time dimension for event ordering */
flowTimeDimension: string | null;
/** Starting step configuration (anchor point for exploration) */
startingStep: FlowStartingStep;
/** Number of steps to explore before starting step (1-5) */
stepsBefore: number;
/** Number of steps to explore after starting step (1-5) */
stepsAfter: number;
/** Event dimension that categorizes events (node labels) */
eventDimension: string | null;
/** Join strategy for flow execution */
joinStrategy: 'auto' | 'lateral' | 'window';
}
/**
* Store actions interface
*/
export interface AnalysisBuilderStoreActions {
/** Set the analysis type (switches between Query/Multi/Funnel modes) */
setAnalysisType: (type: AnalysisType) => void;
/** Set all query states */
setQueryStates: (states: AnalysisBuilderState[]) => void;
/** Update a specific query state by index */
updateQueryState: (index: number, updater: (state: AnalysisBuilderState) => AnalysisBuilderState) => void;
/** Set active query index */
setActiveQueryIndex: (index: number) => void;
/** Set merge strategy */
setMergeStrategy: (strategy: QueryMergeStrategy) => void;
/** Open field modal in metrics mode */
openMetricsModal: () => void;
/** Add a metric to current query */
addMetric: (field: string, label?: string) => void;
/** Remove a metric from current query */
removeMetric: (id: string) => void;
/** Toggle a metric (add if not present, remove if present) */
toggleMetric: (fieldName: string) => void;
/** Reorder metrics */
reorderMetrics: (fromIndex: number, toIndex: number) => void;
/** Open field modal in breakdown mode */
openBreakdownsModal: () => void;
/** Add a breakdown to current query */
addBreakdown: (field: string, isTimeDimension: boolean, granularity?: string) => void;
/** Remove a breakdown from current query */
removeBreakdown: (id: string) => void;
/** Toggle a breakdown (add if not present, remove if present) */
toggleBreakdown: (fieldName: string, isTimeDimension: boolean, granularity?: string) => void;
/** Change granularity for a time dimension breakdown */
setBreakdownGranularity: (id: string, granularity: string) => void;
/** Toggle comparison mode for a time dimension breakdown */
toggleBreakdownComparison: (id: string) => void;
/** Reorder breakdowns */
reorderBreakdowns: (fromIndex: number, toIndex: number) => void;
/** Set filters for current query */
setFilters: (filters: Filter[]) => void;
/** Drop a field to create a filter */
dropFieldToFilter: (field: string) => void;
/** Set sort order for a field */
setOrder: (fieldName: string, direction: 'asc' | 'desc' | null) => void;
/** Add a new query tab */
addQuery: () => void;
/** Remove a query tab */
removeQuery: (index: number) => void;
/** Set chart type */
setChartType: (type: ChartType) => void;
/** Set chart type with manual selection flag */
setChartTypeManual: (type: ChartType) => void;
/** Set chart config */
setChartConfig: (config: ChartAxisConfig) => void;
/** Set display config */
setDisplayConfig: (config: ChartDisplayConfig) => void;
/** Set color palette name */
setLocalPaletteName: (name: string) => void;
/** Set user manually selected chart flag */
setUserManuallySelectedChart: (value: boolean) => void;
/** Set active tab */
setActiveTab: (tab: QueryPanelTab) => void;
/** Set active view */
setActiveView: (view: 'table' | 'chart') => void;
/** Set display limit */
setDisplayLimit: (limit: number) => void;
/** Close field modal */
closeFieldModal: () => void;
/** Open AI panel */
openAI: () => void;
/** Close AI panel */
closeAI: () => void;
/** Set AI prompt */
setAIPrompt: (prompt: string) => void;
/** Set AI generating state */
setAIGenerating: (generating: boolean) => void;
/** Set AI error */
setAIError: (error: string | null) => void;
/** Set AI has generated query */
setAIHasGeneratedQuery: (hasQuery: boolean) => void;
/** Save previous state for AI undo */
saveAIPreviousState: () => void;
/** Restore previous state (AI cancel/undo) */
restoreAIPreviousState: () => void;
/** Add a new funnel step */
addFunnelStep: () => void;
/** Remove a funnel step by index */
removeFunnelStep: (index: number) => void;
/** Update a funnel step by index */
updateFunnelStep: (index: number, updates: Partial<FunnelStepState>) => void;
/** Set the active funnel step index */
setActiveFunnelStepIndex: (index: number) => void;
/** Reorder funnel steps */
reorderFunnelSteps: (fromIndex: number, toIndex: number) => void;
/** Set the time dimension for funnel */
setFunnelTimeDimension: (dimension: string | null) => void;
/** Set the funnel binding key */
setFunnelBindingKey: (bindingKey: FunnelBindingKey | null) => void;
/** Set the funnel cube (clears binding key/time dimension, updates all steps) */
setFunnelCube: (cube: string | null) => void;
/** @deprecated Set time window for a specific step - use updateFunnelStep instead */
setStepTimeToConvert: (stepIndex: number, duration: string | null) => void;
/** @deprecated Build FunnelConfig from queryStates - use buildFunnelQueryFromSteps instead */
buildFunnelConfig: () => FunnelConfig | null;
/** Build ServerFunnelQuery from dedicated funnelSteps */
buildFunnelQueryFromSteps: () => ServerFunnelQuery | null;
/** Check if in funnel mode (analysisType === 'funnel') */
isFunnelMode: () => boolean;
/** Check if funnel mode is properly configured and ready for execution */
isFunnelModeEnabled: () => boolean;
/** Set funnel chart type */
setFunnelChartType: (type: ChartType) => void;
/** Set funnel chart config */
setFunnelChartConfig: (config: ChartAxisConfig) => void;
/** Set funnel display config */
setFunnelDisplayConfig: (config: ChartDisplayConfig) => void;
/** Set the flow cube (clears binding key/time dimension) */
setFlowCube: (cube: string | null) => void;
/** Set the flow binding key */
setFlowBindingKey: (key: FunnelBindingKey | null) => void;
/** Set the flow time dimension */
setFlowTimeDimension: (dim: string | null) => void;
/** Set the event dimension */
setEventDimension: (dim: string | null) => void;
/** Set the starting step name */
setStartingStepName: (name: string) => void;
/** Set all starting step filters at once */
setStartingStepFilters: (filters: Filter[]) => void;
/** Add a filter to the starting step */
addStartingStepFilter: (filter: Filter) => void;
/** Remove a filter from the starting step by index */
removeStartingStepFilter: (index: number) => void;
/** Update a filter in the starting step by index */
updateStartingStepFilter: (index: number, filter: Filter) => void;
/** Set the number of steps to explore before starting step */
setStepsBefore: (count: number) => void;
/** Set the number of steps to explore after starting step */
setStepsAfter: (count: number) => void;
/** Set the join strategy for flow execution */
setJoinStrategy: (strategy: 'auto' | 'lateral' | 'window') => void;
/** Check if in flow mode (analysisType === 'flow') */
isFlowMode: () => boolean;
/** Check if flow mode is properly configured and ready for execution */
isFlowModeEnabled: () => boolean;
/** Build ServerFlowQuery from flow state */
buildFlowQuery: () => ServerFlowQuery | null;
/** Clear only the current mode's state (preserves other modes) */
clearCurrentMode: () => void;
/** @deprecated Clear the current query - use clearCurrentMode instead */
clearQuery: () => void;
/** Get current state (helper accessor) */
getCurrentState: () => AnalysisBuilderState;
/** Get merge keys (computed from Q1 breakdowns) */
getMergeKeys: () => string[] | undefined;
/** Check if in multi-query mode */
isMultiQueryMode: () => boolean;
/** Build current CubeQuery */
buildCurrentQuery: () => CubeQuery;
/** Build all queries */
buildAllQueries: () => CubeQuery[];
/** Build MultiQueryConfig */
buildMultiQueryConfig: () => MultiQueryConfig | null;
/** Reset store to initial state */
reset: () => void;
/**
* Save current state to AnalysisConfig format.
* Delegates to the appropriate adapter based on analysisType.
* Use for share URLs and portlets (single-mode).
*/
save: () => AnalysisConfig;
/**
* Load state from AnalysisConfig.
* Delegates to the appropriate adapter based on config.analysisType.
* Use for share URLs and portlets (single-mode).
*/
load: (config: AnalysisConfig) => void;
/**
* Save ALL modes to AnalysisWorkspace format.
* Used for localStorage persistence to preserve state across mode switches.
*/
saveWorkspace: () => AnalysisWorkspace;
/**
* Load ALL modes from AnalysisWorkspace.
* Used for localStorage persistence to restore state for all modes.
*/
loadWorkspace: (workspace: AnalysisWorkspace) => void;
/**
* Validate current state using the adapter for the current analysis type.
* Returns validation errors and warnings that can be displayed to the user.
*/
getValidation: () => import('../adapters/modeAdapter').ValidationResult;
}
/**
* Combined store type
*/
export type AnalysisBuilderStore = AnalysisBuilderStoreState & AnalysisBuilderStoreActions;
/**
* Initial funnel state for creating a store instance.
* Chart configuration is handled via CreateStoreOptions.initialChartConfig instead.
*/
export interface InitialFunnelState {
funnelCube?: string | null;
funnelSteps?: FunnelStepState[];
funnelTimeDimension?: string | null;
funnelBindingKey?: FunnelBindingKey | null;
}
export interface InitialFlowState {
flowCube?: string | null;
flowBindingKey?: FunnelBindingKey | null;
flowTimeDimension?: string | null;
startingStep?: FlowStartingStep;
stepsBefore?: number;
stepsAfter?: number;
eventDimension?: string | null;
joinStrategy?: 'auto' | 'lateral' | 'window';
}
/**
* Options for creating a store instance
*/
export interface CreateStoreOptions {
/** Initial query configuration */
initialQuery?: CubeQuery | MultiQueryConfig;
/** Initial chart configuration */
initialChartConfig?: {
chartType?: ChartType;
chartConfig?: ChartAxisConfig;
displayConfig?: ChartDisplayConfig;
};
/** Disable localStorage persistence */
disableLocalStorage?: boolean;
/** Initial analysis type (query or funnel) */
initialAnalysisType?: AnalysisType;
/** Initial funnel state (when analysisType === 'funnel') */
initialFunnelState?: InitialFunnelState;
/** Initial flow state (when analysisType === 'flow') */
initialFlowState?: InitialFlowState;
/** Initial active view (table or chart) - used to prevent flash when loading from share */
initialActiveView?: 'table' | 'chart';
}
/**
* Create a new store instance with optional persistence.
* Composes slices: coreSlice, querySlice, funnelSlice, uiSlice.
*/
export declare function createAnalysisBuilderStore(options?: CreateStoreOptions): Omit<Omit<StoreApi<AnalysisBuilderStore>, "setState" | "devtools"> & {
setState(partial: AnalysisBuilderStore | Partial<AnalysisBuilderStore> | ((state: AnalysisBuilderStore) => AnalysisBuilderStore | Partial<AnalysisBuilderStore>), replace?: false | undefined, action?: (string | {
[x: string]: unknown;
[x: number]: unknown;
[x: symbol]: unknown;
type: string;
}) | undefined): void;
setState(state: AnalysisBuilderStore | ((state: AnalysisBuilderStore) => AnalysisBuilderStore), replace: true, action?: (string | {
[x: string]: unknown;
[x: number]: unknown;
[x: symbol]: unknown;
type: string;
}) | undefined): void;
devtools: {
cleanup: () => void;
};
}, "subscribe"> & {
subscribe: {
(listener: (selectedState: AnalysisBuilderStore, previousSelectedState: AnalysisBuilderStore) => void): () => void;
<U>(selector: (state: AnalysisBuilderStore) => U, listener: (selectedState: U, previousSelectedState: U) => void, options?: {
equalityFn?: ((a: U, b: U) => boolean) | undefined;
fireImmediately?: boolean;
} | undefined): () => void;
};
};
/**
* Provider props
*/
export interface AnalysisBuilderStoreProviderProps {
children: ReactNode;
/** Initial query configuration */
initialQuery?: CubeQuery | MultiQueryConfig;
/** Initial chart configuration */
initialChartConfig?: {
chartType?: ChartType;
chartConfig?: ChartAxisConfig;
displayConfig?: ChartDisplayConfig;
};
/** Disable localStorage persistence */
disableLocalStorage?: boolean;
/** Initial analysis type (query or funnel) */
initialAnalysisType?: AnalysisType;
/** Initial funnel state (when analysisType === 'funnel') */
initialFunnelState?: InitialFunnelState;
/** Initial flow state (when analysisType === 'flow') */
initialFlowState?: InitialFlowState;
/** Initial active view (table or chart) - used to prevent flash when loading from share */
initialActiveView?: 'table' | 'chart';
}
/**
* Provider component that creates a store instance per AnalysisBuilder
*/
export declare function AnalysisBuilderStoreProvider({ children, initialQuery, initialChartConfig, disableLocalStorage, initialAnalysisType, initialFunnelState, initialFlowState, initialActiveView, }: AnalysisBuilderStoreProviderProps): import("react/jsx-runtime").JSX.Element;
/**
* Hook to access the store from context
* @throws Error if used outside of provider
*/
export declare function useAnalysisBuilderStore<T>(selector: (state: AnalysisBuilderStore) => T): T;
/**
* Hook to get the raw store API (for actions that need direct access)
*/
export declare function useAnalysisBuilderStoreApi(): StoreApi<AnalysisBuilderStore>;
/**
* Select current query state
*/
export declare const selectCurrentState: (state: AnalysisBuilderStore) => AnalysisBuilderState;
/**
* Select current metrics
*/
export declare const selectMetrics: (state: AnalysisBuilderStore) => import('../components/AnalysisBuilder/types').MetricItem[];
/**
* Select current breakdowns
*/
export declare const selectBreakdowns: (state: AnalysisBuilderStore) => import('../components/AnalysisBuilder/types').BreakdownItem[];
/**
* Select current filters
*/
export declare const selectFilters: (state: AnalysisBuilderStore) => Filter[];
/**
* Select current chart config from the charts map (NEW - Phase 2)
* This is the preferred way to access chart configuration.
* Falls back to default chart config if mode's config is missing.
*/
export declare const selectCurrentChartConfig: (state: AnalysisBuilderStore) => ChartConfig;
/**
* Select chart type from current mode's chart config
*/
export declare const selectChartType: (state: AnalysisBuilderStore) => ChartType;
/**
* Select chart axis config from current mode's chart config
*/
export declare const selectChartAxisConfig: (state: AnalysisBuilderStore) => ChartAxisConfig;
/**
* Select display config from current mode's chart config
*/
export declare const selectChartDisplayConfig: (state: AnalysisBuilderStore) => ChartDisplayConfig;
/**
* Select chart configuration (returns mode-appropriate config)
* @deprecated Use selectCurrentChartConfig instead (reads from charts map)
*/
export declare const selectChartConfig: (state: AnalysisBuilderStore) => {
chartType: ChartType;
chartConfig: ChartAxisConfig;
displayConfig: ChartDisplayConfig;
};
/**
* Select query mode chart configuration (always returns query mode config)
*/
export declare const selectQueryModeChartConfig: (state: AnalysisBuilderStore) => {
chartType: ChartType;
chartConfig: ChartAxisConfig;
displayConfig: ChartDisplayConfig;
};
/**
* Select funnel mode chart configuration (always returns funnel mode config)
*/
export declare const selectFunnelModeChartConfig: (state: AnalysisBuilderStore) => {
chartType: ChartType;
chartConfig: ChartAxisConfig;
displayConfig: ChartDisplayConfig;
};
/**
* Select UI state
*/
export declare const selectUIState: (state: AnalysisBuilderStore) => {
activeTab: QueryPanelTab;
activeView: "table" | "chart";
displayLimit: number;
showFieldModal: boolean;
fieldModalMode: FieldModalMode;
};
/**
* Select analysis type
*/
export declare const selectAnalysisType: (state: AnalysisBuilderStore) => AnalysisType;
/**
* Select multi-query state
*/
export declare const selectMultiQueryState: (state: AnalysisBuilderStore) => {
queryStates: AnalysisBuilderState[];
activeQueryIndex: number;
mergeStrategy: QueryMergeStrategy;
isMultiQueryMode: boolean;
};
/**
* Select funnel cube
*/
export declare const selectFunnelCube: (state: AnalysisBuilderStore) => string | null;
/**
* Select funnel state (new dedicated state)
*/
export declare const selectFunnelState: (state: AnalysisBuilderStore) => {
funnelCube: string | null;
funnelSteps: FunnelStepState[];
activeFunnelStepIndex: number;
funnelTimeDimension: string | null;
funnelBindingKey: FunnelBindingKey | null;
isFunnelMode: boolean;
stepTimeToConvert: (string | null)[];
};