Skip to content

Commit b004a3f

Browse files
committed
feat(gooddata-sdk): [AUTO] Add WidgetDescriptor, DashboardContext, UIContext schemas; extend UserContext
1 parent 37d0593 commit b004a3f

4 files changed

Lines changed: 432 additions & 4 deletions

File tree

packages/gooddata-sdk/src/gooddata_sdk/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,18 @@
316316
PopDatesetMetric,
317317
SimpleMetric,
318318
)
319+
from gooddata_sdk.compute.model.user_context import (
320+
CatalogActiveObjectIdentification,
321+
CatalogDashboardContext,
322+
CatalogInsightWidgetDescriptor,
323+
CatalogObjectReference,
324+
CatalogObjectReferenceGroup,
325+
CatalogRichTextWidgetDescriptor,
326+
CatalogUIContext,
327+
CatalogUserContext,
328+
CatalogVisualizationSwitcherWidgetDescriptor,
329+
CatalogWidgetDescriptor,
330+
)
319331
from gooddata_sdk.compute.service import ComputeService
320332
from gooddata_sdk.sdk import GoodDataSdk
321333
from gooddata_sdk.table import ExecutionTable, TableService
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# (C) 2026 GoodData Corporation
2+
from __future__ import annotations
3+
4+
from typing import Any, Union
5+
6+
import attrs
7+
from gooddata_api_client.model.active_object_identification import ActiveObjectIdentification
8+
from gooddata_api_client.model.dashboard_context import DashboardContext
9+
from gooddata_api_client.model.insight_widget_descriptor import InsightWidgetDescriptor
10+
from gooddata_api_client.model.object_reference import ObjectReference
11+
from gooddata_api_client.model.object_reference_group import ObjectReferenceGroup
12+
from gooddata_api_client.model.rich_text_widget_descriptor import RichTextWidgetDescriptor
13+
from gooddata_api_client.model.ui_context import UIContext
14+
from gooddata_api_client.model.user_context import UserContext
15+
from gooddata_api_client.model.visualization_switcher_widget_descriptor import VisualizationSwitcherWidgetDescriptor
16+
17+
18+
@attrs.define(kw_only=True)
19+
class CatalogActiveObjectIdentification:
20+
"""Identifies the currently active object in the user's UI."""
21+
22+
id: str
23+
type: str
24+
workspace_id: str
25+
26+
def as_api_model(self) -> ActiveObjectIdentification:
27+
return ActiveObjectIdentification(
28+
id=self.id,
29+
type=self.type,
30+
workspace_id=self.workspace_id,
31+
_check_type=False,
32+
)
33+
34+
35+
@attrs.define(kw_only=True)
36+
class CatalogObjectReference:
37+
"""Reference to a GoodData object (widget, metric, attribute, or dashboard)."""
38+
39+
id: str
40+
type: str # WIDGET, METRIC, ATTRIBUTE, DASHBOARD
41+
42+
def as_api_model(self) -> ObjectReference:
43+
return ObjectReference(id=self.id, type=self.type, _check_type=False)
44+
45+
46+
@attrs.define(kw_only=True)
47+
class CatalogObjectReferenceGroup:
48+
"""A group of explicitly referenced objects, optionally scoped by a context."""
49+
50+
objects: list[CatalogObjectReference] = attrs.field(factory=list)
51+
context: CatalogObjectReference | None = None
52+
53+
def as_api_model(self) -> ObjectReferenceGroup:
54+
kwargs: dict[str, Any] = {}
55+
if self.context is not None:
56+
kwargs["context"] = self.context.as_api_model()
57+
return ObjectReferenceGroup(
58+
objects=[o.as_api_model() for o in self.objects],
59+
_check_type=False,
60+
**kwargs,
61+
)
62+
63+
64+
@attrs.define(kw_only=True)
65+
class CatalogInsightWidgetDescriptor:
66+
"""Insight widget visible on a dashboard."""
67+
68+
title: str
69+
widget_id: str
70+
visualization_id: str
71+
result_id: str | None = None
72+
73+
def as_api_model(self) -> InsightWidgetDescriptor:
74+
kwargs: dict[str, Any] = {}
75+
if self.result_id is not None:
76+
kwargs["result_id"] = self.result_id
77+
return InsightWidgetDescriptor(
78+
title=self.title,
79+
visualization_id=self.visualization_id,
80+
widget_id=self.widget_id,
81+
_check_type=False,
82+
**kwargs,
83+
)
84+
85+
86+
@attrs.define(kw_only=True)
87+
class CatalogRichTextWidgetDescriptor:
88+
"""Rich text widget visible on a dashboard."""
89+
90+
title: str
91+
widget_id: str
92+
93+
def as_api_model(self) -> RichTextWidgetDescriptor:
94+
return RichTextWidgetDescriptor(title=self.title, widget_id=self.widget_id, _check_type=False)
95+
96+
97+
@attrs.define(kw_only=True)
98+
class CatalogVisualizationSwitcherWidgetDescriptor:
99+
"""Visualization switcher widget visible on a dashboard."""
100+
101+
title: str
102+
widget_id: str
103+
active_visualization_id: str
104+
visualization_ids: list[str] = attrs.field(factory=list)
105+
result_id: str | None = None
106+
107+
def as_api_model(self) -> VisualizationSwitcherWidgetDescriptor:
108+
kwargs: dict[str, Any] = {}
109+
if self.result_id is not None:
110+
kwargs["result_id"] = self.result_id
111+
return VisualizationSwitcherWidgetDescriptor(
112+
active_visualization_id=self.active_visualization_id,
113+
title=self.title,
114+
visualization_ids=self.visualization_ids,
115+
widget_id=self.widget_id,
116+
_check_type=False,
117+
**kwargs,
118+
)
119+
120+
121+
# Union of all concrete widget descriptor types.
122+
CatalogWidgetDescriptor = Union[
123+
CatalogInsightWidgetDescriptor,
124+
CatalogRichTextWidgetDescriptor,
125+
CatalogVisualizationSwitcherWidgetDescriptor,
126+
]
127+
128+
129+
@attrs.define(kw_only=True)
130+
class CatalogDashboardContext:
131+
"""Dashboard the user is currently viewing."""
132+
133+
id: str
134+
widgets: list[CatalogWidgetDescriptor] = attrs.field(factory=list)
135+
136+
def as_api_model(self) -> DashboardContext:
137+
return DashboardContext(
138+
id=self.id,
139+
widgets=[w.as_api_model() for w in self.widgets],
140+
_check_type=False,
141+
)
142+
143+
144+
@attrs.define(kw_only=True)
145+
class CatalogUIContext:
146+
"""Ambient UI state describing what the user currently sees."""
147+
148+
dashboard: CatalogDashboardContext | None = None
149+
150+
def as_api_model(self) -> UIContext:
151+
kwargs: dict[str, Any] = {}
152+
if self.dashboard is not None:
153+
kwargs["dashboard"] = self.dashboard.as_api_model()
154+
return UIContext(_check_type=False, **kwargs)
155+
156+
157+
@attrs.define(kw_only=True)
158+
class CatalogUserContext:
159+
"""User context that can influence AI feature behavior.
160+
161+
Provides ambient UI state (``view``) and explicitly referenced objects
162+
(``referenced_objects``) in addition to the optionally active object
163+
(``active_object``).
164+
"""
165+
166+
active_object: CatalogActiveObjectIdentification | None = None
167+
referenced_objects: list[CatalogObjectReferenceGroup] = attrs.field(factory=list)
168+
view: CatalogUIContext | None = None
169+
170+
def as_api_model(self) -> UserContext:
171+
kwargs: dict[str, Any] = {}
172+
if self.active_object is not None:
173+
kwargs["active_object"] = self.active_object.as_api_model()
174+
if self.referenced_objects:
175+
kwargs["referenced_objects"] = [ro.as_api_model() for ro in self.referenced_objects]
176+
if self.view is not None:
177+
kwargs["view"] = self.view.as_api_model()
178+
return UserContext(_check_type=False, **kwargs)

packages/gooddata-sdk/src/gooddata_sdk/compute/service.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
ResultCacheMetadata,
2424
TableDimension,
2525
)
26+
from gooddata_sdk.compute.model.user_context import CatalogUserContext
2627
from gooddata_sdk.compute.visualization_to_sdk_converter import VisualizationToSdkConverter
2728

2829
logger = logging.getLogger(__name__)
@@ -135,17 +136,27 @@ def build_exec_def_from_chat_result(
135136
is_cancellable=is_cancellable,
136137
)
137138

138-
def ai_chat(self, workspace_id: str, question: str) -> ChatResult:
139+
def ai_chat(
140+
self,
141+
workspace_id: str,
142+
question: str,
143+
user_context: CatalogUserContext | None = None,
144+
) -> ChatResult:
139145
"""
140146
Chat with AI in GoodData workspace.
141147
142148
Args:
143149
workspace_id (str): workspace identifier
144150
question (str): question for the AI
151+
user_context (CatalogUserContext | None): optional user context providing ambient UI
152+
state and explicitly referenced objects to influence the AI response.
145153
Returns:
146154
ChatResult: Chat response
147155
"""
148-
chat_request = ChatRequest(question=question)
156+
kwargs: dict[str, Any] = {}
157+
if user_context is not None:
158+
kwargs["user_context"] = user_context.as_api_model()
159+
chat_request = ChatRequest(question=question, **kwargs)
149160
response = self._actions_api.ai_chat(workspace_id, chat_request, _check_return_type=False)
150161
return response
151162

@@ -160,17 +171,27 @@ def _parse_sse_events(self, raw: str) -> Iterator[Any]:
160171
except json.JSONDecodeError:
161172
continue
162173

163-
def ai_chat_stream(self, workspace_id: str, question: str) -> Iterator[Any]:
174+
def ai_chat_stream(
175+
self,
176+
workspace_id: str,
177+
question: str,
178+
user_context: CatalogUserContext | None = None,
179+
) -> Iterator[Any]:
164180
"""
165181
Chat Stream with AI in GoodData workspace.
166182
167183
Args:
168184
workspace_id (str): workspace identifier
169185
question (str): question for the AI
186+
user_context (CatalogUserContext | None): optional user context providing ambient UI
187+
state and explicitly referenced objects to influence the AI response.
170188
Returns:
171189
Iterator[Any]: Yields parsed JSON objects from each SSE event's data field
172190
"""
173-
chat_request = ChatRequest(question=question)
191+
kwargs: dict[str, Any] = {}
192+
if user_context is not None:
193+
kwargs["user_context"] = user_context.as_api_model()
194+
chat_request = ChatRequest(question=question, **kwargs)
174195
response = self._actions_api.ai_chat_stream(
175196
workspace_id, chat_request, _check_return_type=False, _preload_content=False
176197
)

0 commit comments

Comments
 (0)