Sunil Katkar
07/01/2025, 11:12 AMJulius Kos
07/01/2025, 1:27 PMSunil Katkar
07/01/2025, 2:26 PMJulius Kos
07/01/2025, 6:39 PMJulius Kos
07/01/2025, 6:41 PMSunil Katkar
07/01/2025, 7:45 PMJulius Kos
07/02/2025, 12:26 PMSunil Katkar
07/08/2025, 6:41 AMJulius Kos
07/08/2025, 7:59 AMSunil Katkar
07/09/2025, 11:33 AM@gooddata/sdk-ui-dashboard
package. The integration is working fine and the dashboard renders successfully.
Below is the React component we are using for this integration:
import React, { useState } from 'react';
import { Dashboard } from "@gooddata/sdk-ui-dashboard";
import tigerBackendFactory, { TigerTokenAuthProvider } from "@gooddata/sdk-backend-tiger";
import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui";
function Report() {
const [dashboardId] = useState('dummy_dashboard_id'); // example: "ad56b32abc1243d78e2aa03e905c70e7"
const [workspaceId] = useState('dummy_workspace_id'); // example: "acme_workspace_123"
const personalToken = process.env.REACT_APP_PERSONAL_TOKEN;
const goodDataUrl = process.env.REACT_APP_GOOD_DATA;
const backend = tigerBackendFactory({
hostname: goodDataUrl
}).withAuthentication(new TigerTokenAuthProvider(personalToken));
return (
<BackendProvider backend={backend}>
<WorkspaceProvider workspace={workspaceId}>
<Dashboard dashboard={dashboardId} />
</WorkspaceProvider>
</BackendProvider>
);
}
export default Report;
Now, we’d like to dynamically apply filters to the dashboard — for example:
• A specific project ID stored in sessionStorage
• A date range selected by the user from the UI
• Other user-specific or context-driven filters
We would like to know:
1. How can we pass these filters to the dashboard at runtime?
2. Do we need to use DashboardView
instead of Dashboard
to inject filters?
3. Are there any official code examples or best practices to follow using the React SDK?
We’d appreciate detailed guidance or code snippets for implementing this correctly.
Hope this helps.Radek Novacek
07/10/2025, 9:13 AMimport React, { useEffect, useState } from "react";
import {
changeAttributeFilterSelection,
CustomDashboardWidgetComponent,
DashboardConfig,
DashboardContext,
DashboardPluginV1,
IDashboardCustomizer,
IDashboardEventHandling,
newCustomWidget,
newDashboardItem,
newDashboardSection,
resetAttributeFilterSelection,
selectFilterContextAttributeFilterByDisplayForm,
useDashboardSelector,
useDispatchDashboardCommand,
} from "@gooddata/sdk-ui-dashboard";
import { idRef } from "@gooddata/sdk-model";
import { useDashboardLoader } from "@gooddata/sdk-ui-loaders";
import { LoadingComponent } from "@gooddata/sdk-ui";
const dashboardRef = import.meta.env.VITE_GD_DASHBOARD;
const config: DashboardConfig = { isReadOnly: true };
// Custom widget with default filter applied on mount
const MyCustomWidget: CustomDashboardWidgetComponent = () => {
const changeAttributeFilterSelectionCmd = useDispatchDashboardCommand(
changeAttributeFilterSelection
);
const resetAttributeFilter = useDispatchDashboardCommand(
resetAttributeFilterSelection
);
const filterLocalId = useDashboardSelector(
selectFilterContextAttributeFilterByDisplayForm(idRef("customer_country"))
)?.attributeFilter.localIdentifier;
// Avoid infinite loops by only running once
const [initialized, setInitialized] = useState(false);
useEffect(() => {
if (!initialized && filterLocalId) {
changeAttributeFilterSelectionCmd(
filterLocalId,
{ values: ["Canada"] },
"IN"
);
setInitialized(true);
}
}, [filterLocalId, initialized, changeAttributeFilterSelectionCmd]);
// Helper function to apply filter values
const applyFilter = (values: string[]) => {
if (!filterLocalId) return;
changeAttributeFilterSelectionCmd(filterLocalId, { values }, "IN");
};
const resetFilterSelection = () => {
if (filterLocalId) {
resetAttributeFilter(filterLocalId);
}
};
return (
<div>
<button onClick={() => applyFilter(["United States"])}>
Filter: United States
</button>
<button onClick={() => applyFilter(["Canada"])}>Filter: Canada</button>
<button onClick={() => applyFilter(["United States", "Canada"])}>
Filter: Both
</button>
<button onClick={resetFilterSelection}>Reset Filter</button>
</div>
);
};
// Dashboard plugin registering the widget and layout
class LocalPlugin extends DashboardPluginV1 {
public readonly author = "GD Support";
public readonly displayName = "Sample filter plugin";
public readonly version = "1.0";
public register(
_ctx: DashboardContext,
customize: IDashboardCustomizer,
_handlers: IDashboardEventHandling
): void {
customize.customWidgets().addCustomWidget("myCustomWidget", MyCustomWidget);
customize.layout().customizeFluidLayout((_layout, customizer) => {
customizer.addSection(
0,
newDashboardSection(
"Buttons to dispatch filter commands",
newDashboardItem(newCustomWidget("myWidget1", "myCustomWidget"), {
xl: {
gridWidth: 12,
gridHeight: 3,
},
})
)
);
});
}
}
const LocalExtraPlugin = {
factory: () => new LocalPlugin(),
};
const App: React.FC = () => {
const { status, result, error } = useDashboardLoader({
dashboard: dashboardRef,
loadingMode: "staticOnly",
extraPlugins: LocalExtraPlugin,
});
if (status === "loading" || status === "pending") {
return <LoadingComponent />;
}
if (error) {
return <div>Error loading dashboard...</div>;
}
const { DashboardComponent, props: dashboardProps } = result!;
return (
<div>
<DashboardComponent {...dashboardProps} config={config} />
</div>
);
};
export default App;
In my case, it's important to note that the App component itself is wrapped by the Backend and Workspace providers, and I'm using Vite env variables for the references - so don't forget to replace those in testing 🙂
I think that if you were to use sessionStorage, you can use the value from there in the useEffect filter. If you do that, and change idRef("customer_country")
to the correct filter ID in filterLocalId
, you're most of the way there.Radek Novacek
07/15/2025, 6:15 AMSunil Katkar
07/15/2025, 1:41 PMRadek Novacek
07/15/2025, 1:43 PMSunil Katkar
07/15/2025, 3:47 PMSunil Katkar
07/16/2025, 2:25 PMRadek Novacek
07/17/2025, 6:39 AMSunil Katkar
07/21/2025, 11:50 AMRadek Novacek
07/21/2025, 12:14 PMconst dashboardRef = import.meta.env.VITE_GD_DASHBOARD;
const config: DashboardConfig = { isReadOnly: true };
// Custom widget with default filter applied on mount
const MyCustomWidget: CustomDashboardWidgetComponent = () => {
const changeAttributeFilterSelectionCmd = useDispatchDashboardCommand(
changeAttributeFilterSelection
);
const resetAttributeFilter = useDispatchDashboardCommand(
resetAttributeFilterSelection
);
const filterLocalId = useDashboardSelector(
selectFilterContextAttributeFilterByDisplayForm(idRef("customer_country"))
)?.attributeFilter.localIdentifier;
// Avoid infinite loops by only running once
const [initialized, setInitialized] = useState(false);
useEffect(() => {
if (!initialized && filterLocalId) {
changeAttributeFilterSelectionCmd(
filterLocalId,
{ values: ["Canada"] },
"IN"
);
setInitialized(true);
}
}, [filterLocalId, initialized, changeAttributeFilterSelectionCmd]);
// Helper function to apply filter values
const applyFilter = (values: string[]) => {
if (!filterLocalId) return;
changeAttributeFilterSelectionCmd(filterLocalId, { values }, "IN");
};
const resetFilterSelection = () => {
if (filterLocalId) {
resetAttributeFilter(filterLocalId);
}
};
return (
<div>
<button onClick={() => applyFilter(["United States"])}>
Filter: United States
</button>
<button onClick={() => applyFilter(["Canada"])}>Filter: Canada</button>
<button onClick={() => applyFilter(["United States", "Canada"])}>
Filter: Both
</button>
<button onClick={resetFilterSelection}>Reset Filter</button>
</div>
);
};
The section below creates the Dashboard plugin - used to place the widget we created above into the layout of the Dashboard itself. It adds a full width section to the Dashboard to place the buttons into.
// Dashboard plugin registering the widget and layout
class LocalPlugin extends DashboardPluginV1 {
public readonly author = "GD Support";
public readonly displayName = "Sample filter plugin";
public readonly version = "1.0";
public register(
_ctx: DashboardContext,
customize: IDashboardCustomizer,
_handlers: IDashboardEventHandling
): void {
customize.customWidgets().addCustomWidget("myCustomWidget", MyCustomWidget);
customize.layout().customizeFluidLayout((_layout, customizer) => {
customizer.addSection(
0,
newDashboardSection(
"Buttons to dispatch filter commands",
newDashboardItem(newCustomWidget("myWidget1", "myCustomWidget"), {
xl: {
gridWidth: 12,
gridHeight: 3,
},
})
)
);
});
}
}
const LocalExtraPlugin = {
factory: () => new LocalPlugin(),
};
Last but not least, the App itself, which then loads the Dashboard with the plugin (and with simple loading logic):
const App: React.FC = () => {
const { status, result, error } = useDashboardLoader({
dashboard: dashboardRef,
loadingMode: "staticOnly",
extraPlugins: LocalExtraPlugin,
});
if (status === "loading" || status === "pending") {
return <LoadingComponent />;
}
if (error) {
return <div>Error loading dashboard...</div>;
}
const { DashboardComponent, props: dashboardProps } = result!;
return (
<div>
<DashboardComponent {...dashboardProps} config={config} />
</div>
);
};
Hope this helps! As I mentioned, if you run into specific errors trying to implement this, please do share them here and I will see if I can point you in the right direction.Sunil Katkar
07/22/2025, 6:11 PMimport React from "react";
import GoodDataDashboard from "./GoodDataDashboard";
import tigerBackendFactory, {
TigerTokenAuthProvider
} from "@gooddata/sdk-backend-tiger";
import { BackendProvider, WorkspaceProvider } from "@gooddata/sdk-ui";
const personalToken = 'cmFtX3ZpZ25lc2g6cmFtc190b2tlbjpwYVE1czBQU241L3QxcjdsSG80aThqR2lNU20r======';
const goodDataUrl = '<https://sustain360-dev.cloud.gooddata.com>';
const backend = tigerBackendFactory({
hostname: goodDataUrl
}).withAuthentication(
new TigerTokenAuthProvider(personalToken)
);
const workspace = "your_workspace_id";
const App: React.FC = () => {
return (
<BackendProvider backend={backend}>
<WorkspaceProvider workspace={workspace}>
<h1>Custom Dashboard</h1>
<GoodDataDashboard />
</WorkspaceProvider>
</BackendProvider>
);
};
export default App;
In App component for backend am facing below issue,
Type 'import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-backend-base/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAnalyticalBackend' is not assignable to type 'import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-ui-loaders/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAnalyticalBackend'.
Types of property 'withAuthentication' are incompatible.
Type '(provider: import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-backend-base/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAuthenticationProvider) => import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_mo...' is not assignable to type '(provider: import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-ui-loaders/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAuthenticationProvider) => import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modu...'.
Types of parameters 'provider' and 'provider' are incompatible.
Property 'disablePrincipalCache' is missing in type 'import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-ui-loaders/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAuthenticationProvider' but required in type 'import("c:/Users/DELL/Downloads/gooddata-dashboard-webpack/gooddata-dashboard-webpack/node_modules/@gooddata/sdk-backend-base/node_modules/@gooddata/sdk-backend-spi/esm/backend/index").IAuthenticationProvider'.ts(2322)
index.d.ts(235, 5): 'disablePrincipalCache' is declared here.
BackendContext.d.ts(11, 5): The expected type comes from property 'backend' which is declared here on type 'IntrinsicAttributes & IBackendProviderProps'
Am also adding my gooddata packages version here for your reference, please take a look,
"@gooddata/api-client-bear": "^9.9.0",
"@gooddata/api-client-tiger": "^10.4.0",
"@gooddata/sdk-backend-tiger": "^10.4.0",
"@gooddata/sdk-ui": "^10.4.0",
"@gooddata/sdk-ui-charts": "^10.4.0",
"@gooddata/sdk-ui-dashboard": "^10.4.0",
"@gooddata/sdk-ui-kit": "^10.4.0",
"@gooddata/sdk-ui-loaders": "^10.34.0",
"@gooddata/sdk-ui-theme-provider": "^10.4.0",
Just to highlight, I’m using the PAT (Personal Access Token) approach to load the dashboard.
Please let me know if you need anything else from my end.
Thank you in advance!!!Radek Novacek
07/23/2025, 6:13 AMRadek Novacek
07/23/2025, 6:22 AMsdk-backend-spi
is being loaded from two different places - there could be multiple reasons for this, but the most likely is that your SDK versions are very much mismatched; can you try to update all of them to 10.34?
Also, api-client-bear
is not a necessary package to include unless you also had a GoodData Platform instance you wanted to interact with, so you can safely get rid of that one 🙂Radek Novacek
07/23/2025, 6:29 AMSunil Katkar
07/23/2025, 9:16 AMSunil Katkar
07/23/2025, 9:20 AMimport React, { useState, useEffect, useCallback } from 'react';
import { Dashboard } from '@gooddata/sdk-ui-dashboard';
import tigerBackendFactory, {
TigerTokenAuthProvider
} from '@gooddata/sdk-backend-tiger';
import { BackendProvider, WorkspaceProvider } from '@gooddata/sdk-ui';
// --- CORRECTED IMPORT FOR FILTERS ---
import { newPositiveAttributeFilter, uriRef } from '@gooddata/sdk-model'; // Use newPositiveAttributeFilter
// --- END CORRECTED IMPORT ---
const GOODDATA_HOST = '<https://test-dev.cloud.gooddata.com/>';
const GOODDATA_WORKSPACE_ID = '62dd165920bd44feb45fb89def254196';
const GOODDATA_DASHBOARD_ID = '2a6e103a-6b29-483d-9e0c-95651d24ca12';
const GOODDATA_PAT = 'bmF2ZWVuX2NoYXVyYXNpYToxMjM0OjRUOTBKWm===========EZWNWtzNTNHN3BzRlls';
// Define the identifiers for your Organization and Sub-organization attributes/labels
const ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.organization_id';
const SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.sub_organization_id'; // <-- VERIFY THIS AGAINST YOUR GOODDATA LDM
const GoodDataDashboardEmbed = ({ selectedOrganization, selectedSubOrganization }) => {
const [backend, setBackend] = useState(null);
const [isLoadingBackend, setIsLoadingBackend] = useState(true);
const [backendError, setBackendError] = useState(null);
useEffect(() => {
const initializeBackend = async () => {
try {
const authProvider = new TigerTokenAuthProvider(GOODDATA_PAT);
const newBackend = tigerBackendFactory({
hostname: GOODDATA_HOST
}).withAuthentication(
new TigerTokenAuthProvider(GOODDATA_PAT)
);
setBackend(newBackend);
setIsLoadingBackend(false);
} catch (error) {
console.error("Failed to initialize GoodData backend:", error);
setBackendError("Failed to initialize dashboard. Check console for details.");
setIsLoadingBackend(false);
}
};
initializeBackend();
}, []);
const dashboardFilters = useCallback(() => {
const filters = [];
if (selectedOrganization) {
const values = selectedOrganization.split(',').map(item => item.trim()).filter(Boolean);
if (values.length > 0) {
// --- CORRECTED FILTER CREATION ---
filters.push(
newPositiveAttributeFilter(
uriRef(ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Attribute ObjRef
values, // Array of selected values
true // 'in' operator (include)
)
);
// --- END CORRECTED FILTER CREATION ---
}
}
if (selectedSubOrganization) {
const values = selectedSubOrganization.split(',').map(item => item.trim()).filter(Boolean);
if (values.length > 0) {
// --- CORRECTED FILTER CREATION ---
filters.push(
newPositiveAttributeFilter(
uriRef(SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Attribute ObjRef
values, // Array of selected values
true // 'in' operator (include)
)
);
// --- END CORRECTED FILTER CREATION ---
}
}
return filters;
}, [selectedOrganization, selectedSubOrganization]);
if (isLoadingBackend) {
return <div style={{ textAlign: 'center', padding: '20px' }}>Loading GoodData services...</div>;
}
if (backendError) {
return <div style={{ color: 'red', textAlign: 'center', padding: '20px' }}>Error: {backendError}</div>;
}
if (!backend) {
return <div style={{ textAlign: 'center', padding: '20px' }}>Backend not available.</div>;
}
return (
<div style={{ height: '800px', width: '100%', border: '1px solid #ddd', borderRadius: '8px', overflow: 'hidden' }}>
<BackendProvider backend={backend}>
<WorkspaceProvider workspace={GOODDATA_WORKSPACE_ID}>
<Dashboard
dashboard={GOODDATA_DASHBOARD_ID}
filters={dashboardFilters()}
config={{
menuVisible: false,
}}
onLoadingChanged={(loading) => console.log('Dashboard loading:', loading)}
onError={(error) => console.error('Dashboard error:', error)}
/>
</WorkspaceProvider>
</BackendProvider>
</div>
);
};
export default GoodDataDashboardEmbed;
but getting below issue,
CorrelationContext.tsx:94 Uncaught TypeError: effectiveBackend.withCorrelation is not a function
at CorrelationContext.tsx:94:1
at mountMemo (react-dom.development.js:17225:1)
at Object.useMemo (react-dom.development.js:17670:1)
at useMemo (react.development.js:1650:1)
at useBackendWithCorrelation (CorrelationContext.tsx:90:1)
at useBackendWithDashboardCorrelation (Dashboard.tsx:59:1)
at Dashboard (Dashboard.tsx:22:1)
at renderWithHooks (react-dom.development.js:16305:1)
at mountIndeterminateComponent (react-dom.development.js:20074:1)
at beginWork (react-dom.development.js:21587:1)
Could you please help me here?Radek Novacek
07/23/2025, 9:26 AMSunil Katkar
07/24/2025, 10:55 AMRadek Novacek
07/24/2025, 10:57 AMSunil Katkar
07/24/2025, 11:32 AMimport React, { useState, useEffect, useCallback } from 'react';
import { Dashboard } from '@gooddata/sdk-ui-dashboard';
import tigerBackendFactory, { // Default export for the backend factory
TigerTokenAuthProvider // Named export for PAT authentication
} from '@gooddata/sdk-backend-tiger';
import { BackendProvider, WorkspaceProvider } from '@gooddata/sdk-ui';
// Import newPositiveAttributeFilter as it was found in your possible exports list
import { newPositiveAttributeFilter, uriRef } from '@gooddata/sdk-model';
const GOODDATA_HOST = '<https://test-dev.cloud.gooddata.com>';
const GOODDATA_WORKSPACE_ID = 'WorkspaceId';
const GOODDATA_DASHBOARD_ID = 'Dashboard ID';
const GOODDATA_PAT = 'TOken';
// --- Attribute Identifiers for Filtering ---
// Based on your previous JSON, 'label.organization_id' is correct for Organization.
// !!! VERIFY 'label.sub_organization_id' against your GoodData Logical Data Model !!!
// You can find these in GoodData UI (e.g., Analytical Designer) or API Explorer.
const ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.organization_id';
const SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.suborganization_id'; // Example, please verify!
const GoodDataDashboardEmbed = ({ selectedOrganization, selectedSubOrganization }) => {
const [backend, setBackend] = useState(null);
const [isLoadingBackend, setIsLoadingBackend] = useState(true);
const [backendError, setBackendError] = useState(null);
useEffect(() => {
const initializeGoodDataBackend = async () => {
try {
const goodDataBackendInstance = tigerBackendFactory({
hostname: GOODDATA_HOST
}).withAuthentication(
new TigerTokenAuthProvider(GOODDATA_PAT)
);
setBackend(goodDataBackendInstance);
setIsLoadingBackend(false);
} catch (error) {
console.error("GoodData Backend Initialization Error:", error);
setBackendError("Failed to initialize GoodData dashboard. Please check console for details and ensure configuration (host, PAT) is correct.");
setIsLoadingBackend(false);
}
};
initializeGoodDataBackend();
}, []); // Empty dependency array ensures this runs only once on component mount
// Memoize the filters to avoid unnecessary re-renders of the dashboard.
// This function will re-run only when selectedOrganization or selectedSubOrganization props change.
const getDashboardFilters = useCallback(() => {
const filters = [];
// Apply Organization filter if a value is provided
if (selectedOrganization) {
// Split comma-separated values, trim whitespace, and filter out empty strings
const orgValues = selectedOrganization.split(',').map(item => item.trim()).filter(Boolean);
if (orgValues.length > 0) {
filters.push(
// Use newPositiveAttributeFilter with uriRef for the attribute and the array of values
newPositiveAttributeFilter(
uriRef(ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Convert string identifier to ObjRef
orgValues, // Array of values to filter by
true // 'true' for 'in' (include) operator
)
);
}
}
// Apply Sub-organization filter if a value is provided
if (selectedSubOrganization) {
const subOrgValues = selectedSubOrganization.split(',').map(item => item.trim()).filter(Boolean);
if (subOrgValues.length > 0) {
filters.push(
newPositiveAttributeFilter(
uriRef(SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Convert string identifier to ObjRef
subOrgValues, // Array of values to filter by
true // 'true' for 'in' (include) operator
)
);
}
}
return filters;
}, [selectedOrganization, selectedSubOrganization]);
// --- Loading and Error Handling UI ---
if (isLoadingBackend) {
return <div style={{ textAlign: 'center', padding: '20px', fontSize: '1.2em' }}>Loading GoodData services...</div>;
}
if (backendError) {
return <div style={{ color: 'red', textAlign: 'center', padding: '20px', border: '1px solid red', borderRadius: '5px' }}>
<strong>Error:</strong> {backendError}
</div>;
}
if (!backend) {
// This case should ideally be caught by isLoadingBackend and backendError, but serves as a final fallback.
return <div style={{ textAlign: 'center', padding: '20px' }}>GoodData backend could not be initialized.</div>;
}
// --- Render the GoodData Dashboard ---
return (
<>
<BackendProvider backend={backend}>
<WorkspaceProvider workspace={GOODDATA_WORKSPACE_ID}>
<Dashboard dashboard={GOODDATA_DASHBOARD_ID} />
</WorkspaceProvider>
</BackendProvider>
</>
);
};
export default GoodDataDashboardEmbed;
Radek Novacek
07/24/2025, 12:07 PMSunil Katkar
07/24/2025, 12:32 PMimport React, { useState, useEffect, useCallback } from 'react';
import { Dashboard } from '@gooddata/sdk-ui-dashboard';
import tigerBackendFactory, {
TigerTokenAuthProvider
} from '@gooddata/sdk-backend-tiger';
import { BackendProvider, WorkspaceProvider } from '@gooddata/sdk-ui';
import { newPositiveAttributeFilter, uriRef } from '@gooddata/sdk-model';
const GOODDATA_HOST = '<https://test-dev.cloud.gooddata.com>';
const GOODDATA_WORKSPACE_ID = '=======workspace id========';
const GOODDATA_DASHBOARD_ID = '=======dashboard id========';
const GOODDATA_PAT = '========personal access token==========';
const ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.organization_id';
const SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER = 'label.suborganization_id'; // Example, please verify!
const GoodDataDashboardEmbed = ({ selectedOrganization, selectedSubOrganization }) => {
const [backend, setBackend] = useState(null);
const [isLoadingBackend, setIsLoadingBackend] = useState(true);
const [backendError, setBackendError] = useState(null);
useEffect(() => {
const initializeGoodDataBackend = async () => {
try {
const goodDataBackendInstance = tigerBackendFactory({
hostname: GOODDATA_HOST
}).withAuthentication(
new TigerTokenAuthProvider(GOODDATA_PAT)
);
setBackend(goodDataBackendInstance);
setIsLoadingBackend(false);
} catch (error) {
console.error("GoodData Backend Initialization Error:", error);
setBackendError("Failed to initialize GoodData dashboard. Please check console for details and ensure configuration (host, PAT) is correct.");
setIsLoadingBackend(false);
}
};
initializeGoodDataBackend();
}, []); // Empty dependency array ensures this runs only once on component mount
// Memoize the filters to avoid unnecessary re-renders of the dashboard.
// This function will re-run only when selectedOrganization or selectedSubOrganization props change.
const getDashboardFilters = useCallback(() => {
const filters = [];
// Apply Organization filter if a value is provided
if (selectedOrganization) {
// Split comma-separated values, trim whitespace, and filter out empty strings
const orgValues = selectedOrganization.split(',').map(item => item.trim()).filter(Boolean);
if (orgValues.length > 0) {
filters.push(
// Use newPositiveAttributeFilter with uriRef for the attribute and the array of values
newPositiveAttributeFilter(
uriRef(ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Convert string identifier to ObjRef
orgValues, // Array of values to filter by
true // 'true' for 'in' (include) operator
)
);
}
}
// Apply Sub-organization filter if a value is provided
if (selectedSubOrganization) {
const subOrgValues = selectedSubOrganization.split(',').map(item => item.trim()).filter(Boolean);
if (subOrgValues.length > 0) {
filters.push(
newPositiveAttributeFilter(
uriRef(SUB_ORGANIZATION_ATTRIBUTE_IDENTIFIER), // Convert string identifier to ObjRef
subOrgValues, // Array of values to filter by
true // 'true' for 'in' (include) operator
)
);
}
}
return filters;
}, [selectedOrganization, selectedSubOrganization]);
// --- Loading and Error Handling UI ---
if (isLoadingBackend) {
return <div style={{ textAlign: 'center', padding: '20px', fontSize: '1.2em' }}>Loading GoodData services...</div>;
}
if (backendError) {
return <div style={{ color: 'red', textAlign: 'center', padding: '20px', border: '1px solid red', borderRadius: '5px' }}>
<strong>Error:</strong> {backendError}
</div>;
}
if (!backend) {
// This case should ideally be caught by isLoadingBackend and backendError, but serves as a final fallback.
return <div style={{ textAlign: 'center', padding: '20px' }}>GoodData backend could not be initialized.</div>;
}
// --- Render the GoodData Dashboard ---
return (
<>
{/* <BackendProvider backend={backend}>
<WorkspaceProvider workspace={'62dd165920bd44feb45fb89def254196'}>
<Dashboard dashboard={'2a6e103a-6b29-483d-9e0c-95651d24ca12'} />
</WorkspaceProvider>
</BackendProvider> */}
<BackendProvider backend={backend}>
<WorkspaceProvider workspace={GOODDATA_WORKSPACE_ID}>
<Dashboard
dashboard={GOODDATA_DASHBOARD_ID}
filters={getDashboardFilters()}
onLoadingChanged={(loading) => console.log('Dashboard loading:', loading)}
onError={(error) => console.error('Dashboard error:', error)}
/>
</WorkspaceProvider>
</BackendProvider>
</>
);
};
export default GoodDataDashboardEmbed;
Also App.js below,
import React, { useState } from 'react';
import GoodDataDashboardEmbed from './GoodDataDashboardEmbed';
import "@gooddata/sdk-ui-filters/styles/css/main.css";
import "@gooddata/sdk-ui-charts/styles/css/main.css";
import "@gooddata/sdk-ui-geo/styles/css/main.css";
import "@gooddata/sdk-ui-pivot/styles/css/main.css";
import "@gooddata/sdk-ui-kit/styles/css/main.css";
import "@gooddata/sdk-ui-ext/styles/css/main.css";
function App() {
// State to hold the dynamic filter values entered by the user
// You can set default values here, or leave them empty ('')
const [organizationValue, setOrganizationValue] = useState('ifc'); // Example default: starts with 'ifc'
const [subOrganizationValue, setSubOrganizationValue] = useState(''); // Example default: empty
// Handlers to update state when input fields change
const handleOrgChange = (e) => {
setOrganizationValue(e.target.value);
};
const handleSubOrgChange = (e) => {
setSubOrganizationValue(e.target.value);
};
return (
<div className="App" style={{ fontFamily: 'Arial, sans-serif', padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
<h1 style={{ textAlign: 'center', color: '#2c3e50', marginBottom: '30px' }}>
GoodData Dashboard with Dynamic React Filters
</h1>
{/* The GoodData Dashboard Embed Component */}
<GoodDataDashboardEmbed
selectedOrganization='ifc'
selectedSubOrganization='ifc_boston-metal'
/>
</div>
);
}
export default App;
Package.json below,
{
"name": "gooddata-dashboard-webpack",
"version": "1.0.0",
"private": true,
"dependencies": {
"@gooddata/sdk-backend-tiger": "~10.4.0",
"@gooddata/sdk-model": "~10.4.0",
"@gooddata/sdk-ui": "~10.4.0",
"@gooddata/sdk-ui-dashboard": "~10.4.0",
"@reduxjs/toolkit": "^2.8.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/react-dom": "^19.1.6"
}
}
Please check and let me know if anything am missing.