Skip to content

Commit ae49236

Browse files
committed
feat: add onDataView callback to visualization components
This optional callback can be used to get notified whenever the visualization loads new data. This can be used to display some extra UI elements based on the data being empty, partially empty, etc. For now added as alpha, pending some feedback from internal use. JIRA: CQ-1287 risk: low
1 parent 47ad217 commit ae49236

File tree

14 files changed

+53
-7
lines changed

14 files changed

+53
-7
lines changed

Diff for: libs/sdk-ui-ext/src/insightView/InsightRenderer.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,10 @@ class InsightRendererCore extends React.PureComponent<IInsightRendererProps & Wr
181181
this.props.onError?.(error);
182182
this.props.onLoadingChanged?.({ isLoading: false });
183183
},
184-
onLoadingChanged: ({ isLoading }) => {
185-
this.props.onLoadingChanged?.({ isLoading });
186-
},
184+
onLoadingChanged: this.props.onLoadingChanged,
187185
pushData: this.props.pushData,
188186
onDrill: this.props.onDrill,
187+
onDataView: this.props.onDataView,
189188
onExportReady: this.onExportReadyDecorator,
190189
afterRender: this.props.afterRender,
191190
},
@@ -370,6 +369,7 @@ export const InsightRenderer: React.FC<IInsightRendererProps> = (props) => {
370369
onError: onErrorCallBack,
371370
onExportReady: onExportReadyCallback,
372371
onLoadingChanged: onLoadingChangedCallback,
372+
onDataView: onDataViewCallback,
373373
...resProps
374374
} = props;
375375

@@ -378,6 +378,7 @@ export const InsightRenderer: React.FC<IInsightRendererProps> = (props) => {
378378
const onError = useUpdatableCallback(onErrorCallBack);
379379
const onExportReady = useUpdatableCallback(onExportReadyCallback);
380380
const onLoadingChanged = useUpdatableCallback(onLoadingChangedCallback);
381+
const onDataView = useUpdatableCallback(onDataViewCallback);
381382

382383
return (
383384
<IntlWrapper locale={props.locale}>
@@ -387,6 +388,7 @@ export const InsightRenderer: React.FC<IInsightRendererProps> = (props) => {
387388
onError={onError}
388389
onExportReady={onExportReady}
389390
onLoadingChanged={onLoadingChanged}
391+
onDataView={onDataView}
390392
{...resProps}
391393
/>
392394
</IntlWrapper>

Diff for: libs/sdk-ui-ext/src/insightView/InsightView.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const InsightViewCore: React.FC<IInsightViewProps & WrappedComponentProps> = (pr
6060
onLoadingChanged,
6161
onExportReady,
6262
onError,
63+
onDataView,
6364
onInsightLoaded,
6465
pushData,
6566

@@ -248,6 +249,7 @@ const InsightViewCore: React.FC<IInsightViewProps & WrappedComponentProps> = (pr
248249
onError={handleError}
249250
onExportReady={onExportReady}
250251
onLoadingChanged={handleLoadingChanged}
252+
onDataView={onDataView}
251253
pushData={pushData}
252254
/>
253255
</div>

Diff for: libs/sdk-ui-ext/src/internal/components/BaseVisualization.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export class BaseVisualization extends React.PureComponent<IBaseVisualizationPro
272272
console.error(`Error: unsupported visualization type - ${visUri}`);
273273
}
274274

275-
let visualizationId;
275+
let visualizationId: string;
276276
if (isInsight(insight)) {
277277
visualizationId = insight.insight.identifier;
278278
} else {
@@ -304,6 +304,7 @@ export class BaseVisualization extends React.PureComponent<IBaseVisualizationPro
304304
onExportReady: props.onExportReady,
305305
pushData: props.pushData,
306306
onDrill: props.onDrill,
307+
onDataView: props.onDataView,
307308
},
308309
featureFlags,
309310
visualizationProperties: insightProperties(props.insight),

Diff for: libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/AbstractPluggableVisualization.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
UnexpectedSdkError,
3636
isForecastNotReceived,
3737
isClusteringNotReceived,
38+
DataViewFacade,
3839
} from "@gooddata/sdk-ui";
3940
import { IntlShape } from "react-intl";
4041
import { createInternalIntl } from "../../utils/internalIntlProvider.js";
@@ -292,6 +293,10 @@ export abstract class AbstractPluggableVisualization implements IVisualization {
292293
return this.callbacks.onDrill ? this.callbacks.onDrill(event) : true;
293294
};
294295

296+
protected onDataView = (dataView: DataViewFacade): void => {
297+
this.callbacks.onDataView?.(dataView);
298+
};
299+
295300
//
296301
// Templated implementation of addNewDerivedBucketItems contract
297302
//

Diff for: libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/baseChart/PluggableBaseChart.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ export class PluggableBaseChart extends AbstractPluggableVisualization {
289289
onExportReady={this.onExportReady}
290290
onLoadingChanged={this.onLoadingChanged}
291291
pushData={this.handlePushData}
292+
onDataView={this.onDataView}
292293
height={resultingHeight}
293294
type={this.type}
294295
locale={locale}

Diff for: libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/geoChart/PluggableGeoPushpinChart.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ export class PluggableGeoPushpinChart extends PluggableBaseChart {
308308
onError: this.onError,
309309
onExportReady: this.onExportReady,
310310
onLoadingChanged: this.onLoadingChanged,
311+
onDataView: this.onDataView,
311312
LoadingComponent: null,
312313
ErrorComponent: null,
313314
theme,

Diff for: libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/headline/PluggableHeadline.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ export class PluggableHeadline extends AbstractPluggableVisualization {
242242
config={headlineConfig}
243243
afterRender={this.afterRender}
244244
onLoadingChanged={this.onLoadingChanged}
245+
onDataView={this.onDataView}
245246
pushData={this.pushData}
246247
onError={this.onError}
247248
LoadingComponent={null}

Diff for: libs/sdk-ui-ext/src/internal/components/pluggableVisualizations/pivotTable/PluggablePivotTable.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ export class PluggablePivotTable extends AbstractPluggableVisualization {
404404
pushData: this.handlePushData,
405405
onError: this.onError,
406406
onExportReady: this.onExportReady,
407+
onDataView: this.onDataView,
407408
onColumnResized,
408409
};
409410
};

Diff for: libs/sdk-ui-pivot/src/CorePivotTable.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export class CorePivotTableAgImpl extends React.Component<ICorePivotTableProps,
188188
| "onExportReady"
189189
| "onLoadingChanged"
190190
| "onError"
191+
| "onDataView"
191192
| "onDrill"
192193
| "ErrorComponent"
193194
| "LoadingComponent"
@@ -202,6 +203,7 @@ export class CorePivotTableAgImpl extends React.Component<ICorePivotTableProps,
202203
onExportReady: noop,
203204
onLoadingChanged: noop,
204205
onError: noop,
206+
onDataView: noop,
205207
onDrill: () => true,
206208
ErrorComponent,
207209
LoadingComponent,
@@ -739,7 +741,9 @@ export class CorePivotTableAgImpl extends React.Component<ICorePivotTableProps,
739741
this.internal.isAltKeyPressed = event.altKey;
740742
};
741743

742-
private onPageLoaded = (_dv: DataViewFacade, newResult: boolean): void => {
744+
private onPageLoaded = (dv: DataViewFacade, newResult: boolean): void => {
745+
this.props.onDataView?.(dv);
746+
743747
if (!this.internal.table) {
744748
return;
745749
}

Diff for: libs/sdk-ui/api/sdk-ui.api.md

+5
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,8 @@ PlaceholderValue<T> | undefined,
13931393
export interface IVisualizationCallbacks {
13941394
// @internal (undocumented)
13951395
afterRender?: () => void;
1396+
// @alpha
1397+
onDataView?: OnDataView;
13961398
onDrill?: OnFiredDrillEvent;
13971399
onError?: OnError;
13981400
onExportReady?: OnExportReady;
@@ -1520,6 +1522,9 @@ export function objMatch(obj: any): IHeaderPredicate;
15201522
// @public
15211523
export function objRefMatch(objRef: ObjRef): IHeaderPredicate;
15221524

1525+
// @alpha (undocumented)
1526+
export type OnDataView = (dataView: DataViewFacade) => void;
1527+
15231528
// @public (undocumented)
15241529
export type OnError = (error: GoodDataSdkError) => void;
15251530

Diff for: libs/sdk-ui/src/base/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ export type {
280280
OnError,
281281
OnExportReady,
282282
OnLoadingChanged,
283+
OnDataView,
283284
ILoadingState,
284285
IExportFunction,
285286
IExtendedExportConfig,

Diff for: libs/sdk-ui/src/base/react/legacy/withEntireDataView.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export function withEntireDataView<T extends IDataVisualizationProps>(
119119
this.onLoadingChanged = this.onLoadingChanged.bind(this);
120120
this.onDataTooLarge = this.onDataTooLarge.bind(this);
121121
this.onNegativeValues = this.onNegativeValues.bind(this);
122+
this.onDataView = this.onDataView.bind(this);
122123
this.abortController = new AbortController();
123124
}
124125

@@ -169,6 +170,7 @@ export function withEntireDataView<T extends IDataVisualizationProps>(
169170
public componentWillUnmount() {
170171
this.hasUnmounted = true;
171172
this.onLoadingChanged = noop;
173+
this.onDataView = noop;
172174
this.onError = noop;
173175
this.refreshAbortController();
174176
}
@@ -196,6 +198,12 @@ export function withEntireDataView<T extends IDataVisualizationProps>(
196198
this.setState(state);
197199
}
198200

201+
private onDataView(dataView: IDataView) {
202+
const { onDataView } = this.props;
203+
204+
onDataView?.(DataViewFacade.for(dataView));
205+
}
206+
199207
private onError(error: GoodDataSdkError) {
200208
const { onExportReady } = this.props;
201209

@@ -301,6 +309,7 @@ export function withEntireDataView<T extends IDataVisualizationProps>(
301309

302310
this.setState({ dataView, error: null, executionResult });
303311
this.onLoadingChanged({ isLoading: false });
312+
this.onDataView(dataView);
304313

305314
if (onExportReady) {
306315
onExportReady(createExportFunction(dataView.result, exportTitle));

Diff for: libs/sdk-ui/src/base/vis/Events.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// (C) 2007-2023 GoodData Corporation
1+
// (C) 2007-2025 GoodData Corporation
22
import { IDataView, IExportConfig, IExportResult } from "@gooddata/sdk-backend-spi";
33
import {
44
IColor,
@@ -10,6 +10,7 @@ import {
1010
} from "@gooddata/sdk-model";
1111
import { GoodDataSdkError } from "../errors/GoodDataSdkError.js";
1212
import { IMappingHeader } from "../headerMatching/MappingHeader.js";
13+
import { DataViewFacade } from "../results/facade.js";
1314

1415
/**
1516
* @public
@@ -28,6 +29,11 @@ export type OnError = (error: GoodDataSdkError) => void;
2829
*/
2930
export type OnLoadingChanged = (loadingState: ILoadingState) => void;
3031

32+
/**
33+
* @alpha
34+
*/
35+
export type OnDataView = (dataView: DataViewFacade) => void;
36+
3137
/**
3238
* @public
3339
*/

Diff for: libs/sdk-ui/src/base/vis/VisualizationProps.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ExplicitDrill, OnFiredDrillEvent } from "./DrillEvents.js";
44
import React from "react";
55
import { IErrorProps } from "../react/ErrorComponent.js";
66
import { ILoadingProps } from "../react/LoadingComponent.js";
7-
import { IPushData, OnError, OnExportReady, OnLoadingChanged } from "./Events.js";
7+
import { IPushData, OnDataView, OnError, OnExportReady, OnLoadingChanged } from "./Events.js";
88
import { IClusteringConfig, IForecastConfig, IPreparedExecution } from "@gooddata/sdk-backend-spi";
99

1010
/**
@@ -80,6 +80,13 @@ export interface IVisualizationCallbacks {
8080
*/
8181
onDrill?: OnFiredDrillEvent;
8282

83+
/**
84+
* Called when the visualization loads a DataView, i.e. it receives data from the backend.
85+
*
86+
* @alpha
87+
*/
88+
onDataView?: OnDataView;
89+
8390
/**
8491
* @internal
8592
*/

0 commit comments

Comments
 (0)