Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions superset-frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ cypress/screenshots
cypress/videos
src/temp
.temp_cache/

env.sh
14 changes: 14 additions & 0 deletions superset-frontend/env.sh.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export SUPERSET

export RUN_MANIFEST_LOCAL
export BOLTIC_STREAMS_KEY
export BOLTIC_STREAMS_CHART_TRIGGER_THRESHOLDS

export ZEN_SENTRY_DSN
export ZEN_SENTRY_ENVIRONMENT
export ZEN_SENTRY_RELEASE

export ASSET_BASE_URL
export DEV_SERVER_HOST
export AI_SUMMARY_ENDPOINT

Original file line number Diff line number Diff line change
Expand Up @@ -174,69 +174,13 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
};

getClassName() {
const { className, showTrendLine, bigNumberFallback, enableClickableCard, hoverBorderEnabled } = this.props;
const hoverBorderClass = enableClickableCard && hoverBorderEnabled ? 'hover-border-enabled' : '';
const { className, showTrendLine, bigNumberFallback } = this.props;
const names = `superset-legacy-chart-big-number ${className} ${
bigNumberFallback ? 'is-fallback-value' : ''
} ${enableClickableCard ? 'clickable-card' : ''} ${hoverBorderClass}`;
}`;
if (showTrendLine) return names;
return `${names} no-trendline`;
}

componentDidMount() {
this.injectDashboardHolderStyles();
}

componentDidUpdate() {
this.injectDashboardHolderStyles();
}

injectDashboardHolderStyles() {
const { enableClickableCard, redirectUrl, hoverBorderEnabled, hoverBorderThickness = 2, hoverBorderColor = '#1890ff' } = this.props;

const styleId = 'bignumber-dashboard-holder-hover-style';

// Remove existing style if present to allow updates
const existingStyle = document.getElementById(styleId);
if (existingStyle) {
existingStyle.remove();
}

if (!enableClickableCard || !redirectUrl || !hoverBorderEnabled) {
return;
}

// Inject global CSS to style the parent dashboard-component-chart-holder
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
.dashboard-component-chart-holder:has(.hover-border-enabled) {
border: ${hoverBorderThickness}px solid transparent !important;
border-radius: 4px;
transition: border-color 0.2s ease;
box-sizing: border-box;
}

.dashboard-component-chart-holder:has(.hover-border-enabled):hover {
border-color: ${hoverBorderColor} !important;
}
`;
document.head.appendChild(style);
}

handleCardClick = () => {
const { enableClickableCard, redirectUrl } = this.props;

if (enableClickableCard && redirectUrl) {
// Validate URL is http/https only (security check)
if (!redirectUrl.match(/^https?:\/\//)) {
return;
}

// Open URL in new tab
window.open(redirectUrl, '_blank', 'noopener,noreferrer');
}
};

createTemporaryContainer() {
const container = document.createElement('div');
Expand Down Expand Up @@ -629,14 +573,11 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
kickerFontSize,
headerFontSize,
subheaderFontSize,
enableClickableCard,
redirectUrl,
} = this.props;
const className = this.getClassName();

const containerStyle: React.CSSProperties = {
position: 'relative' as const,
cursor: enableClickableCard && redirectUrl ? 'pointer' : 'default',
};

if (showTrendLine) {
Expand All @@ -647,15 +588,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
<div
className={className}
style={containerStyle}
onClick={enableClickableCard ? this.handleCardClick : undefined}
role={enableClickableCard ? 'button' : undefined}
tabIndex={enableClickableCard ? 0 : undefined}
onKeyDown={enableClickableCard ? (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.handleCardClick();
}
} : undefined}
>
<div className="text-container" style={{ height: allTextHeight }}>
{this.renderFallbackWarning()}
Expand All @@ -682,15 +614,6 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
<div
className={className}
style={{ ...containerStyle, height }}
onClick={enableClickableCard ? this.handleCardClick : undefined}
role={enableClickableCard ? 'button' : undefined}
tabIndex={enableClickableCard ? 0 : undefined}
onKeyDown={enableClickableCard ? (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.handleCardClick();
}
} : undefined}
>
{this.renderFallbackWarning()}
{this.renderKicker((kickerFontSize || 0) * height)}
Expand All @@ -702,7 +625,7 @@ class BigNumberVis extends PureComponent<BigNumberVizProps> {
}

export default styled(BigNumberVis)`
${({ theme, hoverBorderEnabled, hoverBorderThickness = 2, hoverBorderColor = '#1890ff', enableClickableCard, redirectUrl }) => `
${({ theme }) => `
font-family: ${theme.typography.families.sansSerif};
position: relative;
display: flex;
Expand Down Expand Up @@ -757,13 +680,6 @@ export default styled(BigNumberVis)`
}
}

&.clickable-card {
&:focus {
outline: 2px solid ${theme.colors.primary.base};
outline-offset: 2px;
}
}

.comparison-indicator {
@keyframes fadeInScale {
from {
Expand Down
19 changes: 1 addition & 18 deletions superset-frontend/run.local.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,2 @@
export SUPERSET=https://superset.intelligence.fynd.com
# export SUPERSET=https://superset.intelligence.sit.fyndx1.de
export RUN_MANIFEST_LOCAL=true
# export BOLTIC_STREAMS_METHODS=trackSubmit,trackClick,trackLink,trackForm,pageview,identify,reset,group,track,ready,alias,debug,page,once,off,on,addSourceMiddleware,addIntegrationMiddleware,setAnonymousId,addDestinationMiddleware
export BOLTIC_STREAMS_KEY=Zc7Og8DXevJG85xZyjKPJBAgkOITWUdtpDB8EsyQWxyx8JgqZIsGz-smhWqpsCcLJ0_wPw-3_NMkljT0x1SLsQ
export BOLTIC_STREAMS_CHART_TRIGGER_THRESHOLDS=[{"chartId":152,"threshold":330000},{"chartId":248,"threshold":10000}]

# Sentry Configuration
export ZEN_SENTRY_DSN=https://2b84b4e66780eedb23cac77ee8a579ac@o4510101081161728.ingest.us.sentry.io/4510101085093888
export ZEN_SENTRY_ENVIRONMENT=development
export ZEN_SENTRY_RELEASE=superset@4.1.3

# Run dev server on custom host and port
export ASSET_BASE_URL=/superset
export DEV_SERVER_HOST=localdev.intelligence.fynd.com
export AI_SUMMARY_ENDPOINT=https://dummyjson.com/c/bc46-ae16-4ffc-80b1

# npm run dev-server -- --port=9001
npm run dev-server -- --port=9001 --host=localdev.intelligence.fynd.com
source env.sh && npm run dev-server -- --port=9001 --host=localdev.intelligence.fynd.com
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
GRID_MIN_COLUMN_COUNT,
GRID_MIN_ROW_UNITS,
} from 'src/dashboard/util/constants';
import getFormDataWithExtraFilters from 'src/dashboard/util/charts/getFormDataWithExtraFilters';
import { getAppliedFilterValues } from 'src/dashboard/util/activeDashboardFilters';

export const CHART_MARGIN = 32;

Expand Down Expand Up @@ -126,6 +128,10 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
const dashboardState = useSelector(
(state: RootState) => state.dashboardState,
);
const charts = useSelector((state: RootState) => state.charts);
const dashboardInfo = useSelector((state: RootState) => state.dashboardInfo);
const dataMask = useSelector((state: RootState) => state.dataMask);
const nativeFilters = useSelector((state: RootState) => state.nativeFilters);
const [extraControls, setExtraControls] = useState<Record<string, unknown>>(
{},
);
Expand Down Expand Up @@ -260,6 +266,92 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
}));
}, []);

// Clickable card support for BigNumber charts
const clickableCardConfig = useMemo(() => {
const chart = charts[chartId];
if (!chart) return { redirectUrl: undefined };

const vizType = chart.form_data?.viz_type;
if (!['big_number', 'big_number_total', 'big_number_with_trendline', 'big_number_period_over_period'].includes(vizType)) {
return { redirectUrl: undefined };
}

// Get formData with extraControls merged
const chartPayload = chart as any;
const formData = getFormDataWithExtraFilters({
chart: chartPayload,
chartConfiguration: dashboardInfo?.metadata?.chart_configuration || {},
filters: getAppliedFilterValues(chartId.toString()),
colorNamespace: (dashboardState as any)?.colorNamespace,
colorScheme: dashboardState?.colorScheme,
ownColorScheme: chart.form_data?.color_scheme,
sliceId: chartId,
nativeFilters: nativeFilters?.filters || {},
allSliceIds: dashboardState?.sliceIds || [],
dataMask,
extraControls: extraControls as Record<string, string | boolean | null>,
labelsColor: dashboardInfo?.metadata?.label_colors || {},
labelsColorMap: dashboardInfo?.metadata?.map_label_colors || {},
sharedLabelsColors: [],
});

if (!formData?.enable_clickable_card || !chart.queriesResponse) {
return { redirectUrl: undefined };
}

// Extract redirectUrl
const queriesData = Array.isArray(chart.queriesResponse) ? chart.queriesResponse : [chart.queriesResponse];
const clickableCardUrl = formData.clickable_card_url as string | undefined;
const urlColumn = formData.url_column as string | undefined;

let redirectUrl: string | undefined = clickableCardUrl;
if (!redirectUrl && urlColumn && queriesData[1]?.data?.[0]) {
redirectUrl = queriesData[1].data[0][urlColumn] || Object.values(queriesData[1].data[0])[0] as string;
}
if (!redirectUrl && urlColumn && queriesData[0]?.data?.[0]) {
const row = queriesData[0].data[0];
redirectUrl = row[urlColumn] || ['MAX', 'MIN', 'ANY_VALUE', 'FIRST', 'LAST']
.map(agg => row[`${agg}(${urlColumn})`])
.find(Boolean) as string | undefined;
}

return { redirectUrl: typeof redirectUrl === 'string' ? redirectUrl : undefined };
}, [charts, chartId, dashboardInfo, dashboardState, dataMask, nativeFilters, extraControls]);

const handleCardClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
const target = e.target as HTMLElement;
const currentTarget = e.currentTarget as HTMLElement;

if (editMode || !clickableCardConfig.redirectUrl || e.button === 2 || e.ctrlKey || e.metaKey || e.shiftKey) {
return;
}

// Allow clicks directly on the chart holder
if (target === currentTarget) {
if (clickableCardConfig.redirectUrl.match(/^https?:\/\//)) {
window.open(clickableCardConfig.redirectUrl, '_blank', 'noopener,noreferrer');
}
return;
}

// Check for interactive elements within the chart holder (but not the chart holder itself)
let element: HTMLElement | null = target;
while (element && element !== currentTarget && currentTarget.contains(element)) {
if (element.matches('a, button, input, select, textarea, [role="button"], [role="link"], [role="menuitem"], .slice-header-controls, .hover-menu, canvas, svg, .echarts-for-react, [class*="echarts"]')) {
return;
}
element = element.parentElement;
}

if (e.defaultPrevented || (e.nativeEvent as any).dataTransfer?.effectAllowed) {
return;
}

if (clickableCardConfig.redirectUrl.match(/^https?:\/\//)) {
window.open(clickableCardConfig.redirectUrl, '_blank', 'noopener,noreferrer');
}
}, [clickableCardConfig, editMode]);

return (
<Draggable
component={component}
Expand Down Expand Up @@ -291,15 +383,28 @@ const ChartHolder: React.FC<ChartHolderProps> = ({
<div
ref={dragSourceRef}
data-test="dashboard-component-chart-holder"
style={focusHighlightStyles}
style={{
...focusHighlightStyles,
cursor: clickableCardConfig.redirectUrl ? 'pointer' : 'default',
}}
css={isFullSize ? fullSizeStyle : undefined}
className={cx(
'dashboard-component',
'dashboard-component-chart-holder',
// The following class is added to support custom dashboard styling via the CSS editor
`dashboard-chart-id-${chartId}`,
outlinedComponentId ? 'fade-in' : 'fade-out',
clickableCardConfig.redirectUrl ? 'clickable-card' : '',
)}
onClick={clickableCardConfig.redirectUrl && !editMode ? handleCardClick : undefined}
role={clickableCardConfig.redirectUrl ? 'button' : undefined}
tabIndex={clickableCardConfig.redirectUrl ? 0 : undefined}
onKeyDown={clickableCardConfig.redirectUrl && !editMode ? (e) => {
if ((e.key === 'Enter' || e.key === ' ') && clickableCardConfig.redirectUrl?.match(/^https?:\/\//)) {
e.preventDefault();
window.open(clickableCardConfig.redirectUrl, '_blank', 'noopener,noreferrer');
}
} : undefined}
>
{!editMode && (
<AnchorLink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,25 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
setTooltipTitle(
getTooltipTitle(labelIsTruncated, value, formattedADR),
);
} else if (guessedFrame === 'Custom') {
setActualTimeRange(formattedADR || '');
setEvalResponse(formattedADR || '');
const customTooltipText = formattedADR
? `${formattedADR}`
: null;
setTooltipTitle(
labelIsTruncated && formattedADR
? (
<div
css={(theme: SupersetTheme) => css`
margin-top: ${theme.gridUnit}px;
`}
>
{formattedADR}
</div>
)
: customTooltipText
);
} else {
// Pill shows ADR (user-friendly formatted); tooltip shows HRT (value)
// "Actual time range" shows formatted ADR
Expand Down