Skip to content

Commit 1d24d79

Browse files
committed
feat: add api to gooddata-sdk for agg fact
risk: low
1 parent 01a7d8d commit 1d24d79

File tree

8 files changed

+191
-50
lines changed

8 files changed

+191
-50
lines changed

docker-compose.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ services:
2424
GDC_FEATURES_VALUES_ENABLE_SCHEDULING: "true"
2525
GDC_FEATURES_VALUES_ENABLE_ALERTING: "true"
2626
GDC_FEATURES_VALUES_ENABLE_SMTP: "true"
27+
GDC_FEATURES_VALUES_ENABLE_PRE_AGGREGATION_DATASETS: "true"
2728
# In the case of failing tests (HTTP 500), you can increase the memory for the metadata API
2829
# METADATA_API_JAVA_OPTS: "-Xmx1024m -Xms512m"
2930
gooddata-fdw:

gooddata-sdk/gooddata_sdk/catalog/identifier.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
)
1717
from gooddata_api_client.model.declarative_user_group_identifier import DeclarativeUserGroupIdentifier
1818
from gooddata_api_client.model.declarative_user_identifier import DeclarativeUserIdentifier
19+
from gooddata_api_client.model.fact_identifier import FactIdentifier
1920
from gooddata_api_client.model.grain_identifier import GrainIdentifier
2021
from gooddata_api_client.model.label_identifier import LabelIdentifier
2122
from gooddata_api_client.model.reference_identifier import ReferenceIdentifier
@@ -82,6 +83,16 @@ def client_class() -> builtins.type[DeclarativeUserIdentifier]:
8283
return DeclarativeUserIdentifier
8384

8485

86+
@attr.s(auto_attribs=True, kw_only=True)
87+
class CatalogFactIdentifier(Base):
88+
id: str
89+
type: str = attr.field(validator=value_in_allowed)
90+
91+
@staticmethod
92+
def client_class() -> builtins.type[FactIdentifier]:
93+
return FactIdentifier
94+
95+
8596
@attr.s(auto_attribs=True, kw_only=True)
8697
class CatalogLabelIdentifier(Base):
8798
id: str

gooddata-sdk/gooddata_sdk/catalog/workspace/content_service.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from gooddata_sdk.catalog.workspace.declarative_model.workspace.logical_model.ldm import CatalogDeclarativeModel
2222
from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import LAYOUT_WORKSPACES_DIR
2323
from gooddata_sdk.catalog.workspace.entity_model.content_objects.dataset import (
24+
CatalogAggregatedFact,
2425
CatalogAttribute,
2526
CatalogFact,
2627
CatalogLabel,
@@ -191,6 +192,24 @@ def get_facts_catalog(self, workspace_id: str) -> list[CatalogFact]:
191192
catalog_facts = [CatalogFact.from_api(fact) for fact in facts.data]
192193
return catalog_facts
193194

195+
def get_aggregated_facts_catalog(self, workspace_id: str) -> list[CatalogAggregatedFact]:
196+
"""Retrieve all aggregated facts in a given workspace.
197+
198+
Args:
199+
workspace_id (str):
200+
Workspace identification string e.g. "demo"
201+
202+
Returns:
203+
list[CatalogAggregatedFact]:
204+
List of all aggregated facts in a given workspace.
205+
"""
206+
get_agg_facts = functools.partial(
207+
self._entities_api.get_all_entities_aggregated_facts, workspace_id, _check_return_type=False
208+
)
209+
agg_facts = load_all_entities(get_agg_facts)
210+
catalog_agg_facts = [CatalogAggregatedFact.from_api(agg_fact) for agg_fact in agg_facts.data]
211+
return catalog_agg_facts
212+
194213
def get_dependent_entities_graph(self, workspace_id: str) -> CatalogDependentEntitiesResponse:
195214
"""There are dependencies among all catalog objects, the chain is the following:
196215
`fact/attribute/label → dataset → metric → visualization → dashboard`

gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,24 @@
66

77
import attr
88
from gooddata_api_client.model.data_source_table_identifier import DataSourceTableIdentifier
9+
from gooddata_api_client.model.declarative_aggregated_fact import DeclarativeAggregatedFact
910
from gooddata_api_client.model.declarative_attribute import DeclarativeAttribute
1011
from gooddata_api_client.model.declarative_dataset import DeclarativeDataset
1112
from gooddata_api_client.model.declarative_dataset_sql import DeclarativeDatasetSql
1213
from gooddata_api_client.model.declarative_fact import DeclarativeFact
1314
from gooddata_api_client.model.declarative_label import DeclarativeLabel
1415
from gooddata_api_client.model.declarative_reference import DeclarativeReference
1516
from gooddata_api_client.model.declarative_reference_source import DeclarativeReferenceSource
17+
from gooddata_api_client.model.declarative_source_fact_reference import DeclarativeSourceFactReference
1618
from gooddata_api_client.model.declarative_workspace_data_filter_column import DeclarativeWorkspaceDataFilterColumn
1719

1820
from gooddata_sdk.catalog.base import Base
19-
from gooddata_sdk.catalog.identifier import CatalogGrainIdentifier, CatalogLabelIdentifier, CatalogReferenceIdentifier
21+
from gooddata_sdk.catalog.identifier import (
22+
CatalogFactIdentifier,
23+
CatalogGrainIdentifier,
24+
CatalogLabelIdentifier,
25+
CatalogReferenceIdentifier,
26+
)
2027
from gooddata_sdk.catalog.workspace.declarative_model.workspace.logical_model.data_filter_references import (
2128
CatalogDeclarativeWorkspaceDataFilterReferences,
2229
)
@@ -34,6 +41,7 @@ class CatalogDeclarativeDataset(Base):
3441
description: Optional[str] = None
3542
attributes: Optional[list[CatalogDeclarativeAttribute]] = None
3643
facts: Optional[list[CatalogDeclarativeFact]] = None
44+
aggregated_facts: Optional[list[CatalogDeclarativeAggregatedFact]] = None
3745
data_source_table_id: Optional[CatalogDataSourceTableIdentifier] = None
3846
sql: Optional[CatalogDeclarativeDatasetSql] = None
3947
tags: Optional[list[str]] = None
@@ -86,6 +94,30 @@ def client_class() -> type[DeclarativeFact]:
8694
return DeclarativeFact
8795

8896

97+
@attr.s(auto_attribs=True, kw_only=True)
98+
class CatalogDeclarativeSourceFactReference(Base):
99+
operation: str
100+
reference: CatalogFactIdentifier
101+
102+
@staticmethod
103+
def client_class() -> type[DeclarativeFact]:
104+
return DeclarativeSourceFactReference
105+
106+
107+
@attr.s(auto_attribs=True, kw_only=True)
108+
class CatalogDeclarativeAggregatedFact(Base):
109+
id: str
110+
source_column: str
111+
source_fact_reference: Optional[CatalogDeclarativeSourceFactReference] = None
112+
source_column_data_type: Optional[str] = None
113+
description: Optional[str] = None
114+
tags: Optional[list[str]] = None
115+
116+
@staticmethod
117+
def client_class() -> type[DeclarativeAggregatedFact]:
118+
return DeclarativeAggregatedFact
119+
120+
89121
@attr.s(auto_attribs=True, kw_only=True)
90122
class CatalogDataSourceTableIdentifier(Base):
91123
id: str

gooddata-sdk/gooddata_sdk/catalog/workspace/entity_model/content_objects/dataset.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import attr
77
import attrs
8+
from gooddata_api_client.model.json_api_aggregated_fact_out import JsonApiAggregatedFactOut
89
from gooddata_api_client.model.json_api_attribute_out import JsonApiAttributeOut
910
from gooddata_api_client.model.json_api_dataset_out import JsonApiDatasetOut
1011
from gooddata_api_client.model.json_api_fact_out import JsonApiFactOut
@@ -99,6 +100,18 @@ def as_computable(self) -> Metric:
99100
# TODO - dataset?
100101

101102

103+
@attr.s(auto_attribs=True, kw_only=True)
104+
class CatalogAggregatedFact(AttrCatalogEntity):
105+
@staticmethod
106+
def client_class() -> Any:
107+
return JsonApiAggregatedFactOut
108+
109+
def as_computable(self) -> Metric:
110+
return SimpleMetric(local_id=self.id, item=self.obj_id)
111+
112+
# TODO - dataset?
113+
114+
102115
@attr.s(auto_attribs=True, kw_only=True)
103116
class CatalogDataset(AttrCatalogEntity):
104117
@property
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# (C) 2024 GoodData Corporation
2+
version: 1
3+
interactions:
4+
- request:
5+
method: GET
6+
uri: http://localhost:3000/api/v1/entities/workspaces/demo/aggregatedFacts?page=0&size=500
7+
body: null
8+
headers:
9+
Accept:
10+
- application/vnd.gooddata.api+json
11+
Accept-Encoding:
12+
- br, gzip, deflate
13+
X-GDC-VALIDATE-RELATIONS:
14+
- 'true'
15+
X-Requested-With:
16+
- XMLHttpRequest
17+
response:
18+
status:
19+
code: 200
20+
message: OK
21+
headers:
22+
Access-Control-Allow-Credentials:
23+
- 'true'
24+
Access-Control-Expose-Headers:
25+
- Content-Disposition, Content-Length, Content-Range, Set-Cookie
26+
Cache-Control:
27+
- no-cache, no-store, max-age=0, must-revalidate
28+
Connection:
29+
- keep-alive
30+
Content-Length:
31+
- '1546'
32+
Content-Security-Policy:
33+
- 'default-src ''self'' *.wistia.com *.wistia.net; script-src ''self'' ''unsafe-inline''
34+
''unsafe-eval'' *.wistia.com *.wistia.net *.hsforms.net *.hsforms.com
35+
src.litix.io matomo.anywhere.gooddata.com *.jquery.com unpkg.com cdnjs.cloudflare.com;
36+
img-src * data: blob:; style-src ''self'' ''unsafe-inline'' fonts.googleapis.com
37+
cdn.jsdelivr.net fast.fonts.net; font-src ''self'' data: fonts.gstatic.com
38+
*.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src
39+
''self'' *.hsforms.net *.hsforms.com; object-src ''none''; worker-src
40+
''self'' blob:; child-src blob:; connect-src ''self'' *.tiles.mapbox.com
41+
*.mapbox.com *.litix.io *.wistia.com *.hsforms.net *.hsforms.com embedwistia-a.akamaihd.net
42+
matomo.anywhere.gooddata.com; media-src ''self'' blob: data: *.wistia.com
43+
*.wistia.net embedwistia-a.akamaihd.net'
44+
Content-Type:
45+
- application/vnd.gooddata.api+json
46+
DATE: &id001
47+
- PLACEHOLDER
48+
Expires:
49+
- '0'
50+
GoodData-Deployment:
51+
- aio
52+
Permission-Policy:
53+
- geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera
54+
'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment
55+
'none';
56+
Pragma:
57+
- no-cache
58+
Referrer-Policy:
59+
- no-referrer
60+
Server:
61+
- nginx
62+
Vary:
63+
- Origin
64+
- Access-Control-Request-Method
65+
- Access-Control-Request-Headers
66+
X-Content-Type-Options:
67+
- nosniff
68+
X-GDC-TRACE-ID: *id001
69+
X-XSS-Protection:
70+
- '0'
71+
set-cookie:
72+
- SPRING_REDIRECT_URI=; Max-Age=0; Expires=Mon, 07 Oct 2024 11:17:34 GMT;
73+
Path=/; HTTPOnly; SameSite=Lax
74+
body:
75+
string:
76+
data:
77+
links:
78+
self: http://localhost:3000/api/v1/entities/workspaces/demo/aggregatedFacts?page=0&size=500
79+
next: http://localhost:3000/api/v1/entities/workspaces/demo/aggregatedFacts?page=1&size=500

gooddata-sdk/tests/catalog/fixtures/workspace_content/demo_catalog_list_facts.yaml

Lines changed: 26 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) 2024 GoodData Corporation
1+
# (C) 2025 GoodData Corporation
22
version: 1
33
interactions:
44
- request:
@@ -19,58 +19,35 @@ interactions:
1919
code: 200
2020
message: OK
2121
headers:
22-
Access-Control-Allow-Credentials:
23-
- 'true'
24-
Access-Control-Expose-Headers:
25-
- Content-Disposition, Content-Length, Content-Range, Set-Cookie
2622
Cache-Control:
2723
- no-cache, no-store, max-age=0, must-revalidate
28-
Connection:
29-
- keep-alive
3024
Content-Length:
3125
- '1546'
32-
Content-Security-Policy:
33-
- 'default-src ''self'' *.wistia.com *.wistia.net; script-src ''self'' ''unsafe-inline''
34-
''unsafe-eval'' *.wistia.com *.wistia.net *.hsforms.net *.hsforms.com
35-
src.litix.io matomo.anywhere.gooddata.com *.jquery.com unpkg.com cdnjs.cloudflare.com;
36-
img-src * data: blob:; style-src ''self'' ''unsafe-inline'' fonts.googleapis.com
37-
cdn.jsdelivr.net fast.fonts.net; font-src ''self'' data: fonts.gstatic.com
38-
*.alicdn.com *.wistia.com cdn.jsdelivr.net info.gooddata.com; frame-src
39-
''self'' *.hsforms.net *.hsforms.com; object-src ''none''; worker-src
40-
''self'' blob:; child-src blob:; connect-src ''self'' *.tiles.mapbox.com
41-
*.mapbox.com *.litix.io *.wistia.com *.hsforms.net *.hsforms.com embedwistia-a.akamaihd.net
42-
matomo.anywhere.gooddata.com; media-src ''self'' blob: data: *.wistia.com
43-
*.wistia.net embedwistia-a.akamaihd.net'
4426
Content-Type:
4527
- application/vnd.gooddata.api+json
4628
DATE: &id001
4729
- PLACEHOLDER
4830
Expires:
4931
- '0'
50-
GoodData-Deployment:
51-
- aio
52-
Permission-Policy:
53-
- geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera
54-
'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'none'; payment
55-
'none';
32+
Featurepolicy:
33+
- geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr
34+
'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope
35+
'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none';
5636
Pragma:
5737
- no-cache
5838
Referrer-Policy:
59-
- no-referrer
60-
Server:
61-
- nginx
39+
- same-origin
6240
Vary:
6341
- Origin
6442
- Access-Control-Request-Method
6543
- Access-Control-Request-Headers
6644
X-Content-Type-Options:
6745
- nosniff
46+
X-Frame-Options:
47+
- SAMEORIGIN
6848
X-GDC-TRACE-ID: *id001
69-
X-XSS-Protection:
70-
- '0'
71-
set-cookie:
72-
- SPRING_REDIRECT_URI=; Max-Age=0; Expires=Mon, 07 Oct 2024 11:17:34 GMT;
73-
Path=/; HTTPOnly; SameSite=Lax
49+
X-Xss-Protection:
50+
- 1; mode=block
7451
body:
7552
string:
7653
data:
@@ -90,22 +67,6 @@ interactions:
9067
origin:
9168
originType: NATIVE
9269
originId: demo
93-
- id: spend
94-
type: fact
95-
attributes:
96-
title: Spend
97-
description: Spend
98-
tags:
99-
- Campaign channels
100-
sourceColumn: spend
101-
sourceColumnDataType: NUMERIC
102-
areRelationsValid: true
103-
links:
104-
self: http://localhost:3000/api/v1/entities/workspaces/demo/facts/spend
105-
meta:
106-
origin:
107-
originType: NATIVE
108-
originId: demo
10970
- id: price
11071
type: fact
11172
attributes:
@@ -138,6 +99,22 @@ interactions:
13899
origin:
139100
originType: NATIVE
140101
originId: demo
102+
- id: spend
103+
type: fact
104+
attributes:
105+
title: Spend
106+
description: Spend
107+
tags:
108+
- Campaign channels
109+
sourceColumn: spend
110+
sourceColumnDataType: NUMERIC
111+
areRelationsValid: true
112+
links:
113+
self: http://localhost:3000/api/v1/entities/workspaces/demo/facts/spend
114+
meta:
115+
origin:
116+
originType: NATIVE
117+
originId: demo
141118
links:
142119
self: http://localhost:3000/api/v1/entities/workspaces/demo/facts?page=0&size=500
143120
next: http://localhost:3000/api/v1/entities/workspaces/demo/facts?page=1&size=500

gooddata-sdk/tests/catalog/test_catalog_workspace_content.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ def test_catalog_list_facts(test_config):
5656
sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"])
5757
facts_list = sdk.catalog_workspace_content.get_facts_catalog(test_config["workspace"])
5858
assert len(facts_list) == 4
59+
# TODO: Add to the same workspace aggregated facts and make sure they are not there via keeping the same cassette
60+
61+
62+
@gd_vcr.use_cassette(str(_fixtures_dir / "demo_catalog_list_aggregated_facts.yaml"))
63+
def test_catalog_list_aggregated_facts(test_config):
64+
sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"])
65+
agg_facts_list = sdk.catalog_workspace_content.get_aggregated_facts_catalog(test_config["workspace"])
66+
# TODO: Add aggregated facts to make a non-trivial test
67+
assert len(agg_facts_list) == 0
5968

6069

6170
@gd_vcr.use_cassette(str(_fixtures_dir / "demo_catalog_list_attributes.yaml"))

0 commit comments

Comments
 (0)