diff --git a/docs/content/en/latest/workspace/workspaces/_index.md b/docs/content/en/latest/workspace/workspaces/_index.md index cc06080b3..3f8a72080 100644 --- a/docs/content/en/latest/workspace/workspaces/_index.md +++ b/docs/content/en/latest/workspace/workspaces/_index.md @@ -15,6 +15,20 @@ Manage workspaces. Entity and Declarative methods are supported. * [delete_workspace](./delete_workspace/) * [list_workspaces](./list_workspaces/) +### Workspace Setting Methods + +* [list_workspace_settings](./list_workspace_settings/) +* [get_workspace_setting](./get_workspace_setting/) +* [create_or_update_workspace_setting](./create_or_update_workspace_setting/) +* [delete_workspace_setting](./delete_workspace_setting/) + +### Custom Application Setting Methods + +* [list_custom_application_settings](./list_custom_application_settings/) +* [get_custom_application_setting](./get_custom_application_setting/) +* [create_or_update_custom_application_setting](./create_or_update_custom_application_setting/) +* [delete_custom_application_setting](./delete_custom_application_setting/) + ### Declarative Methods * [get_declarative_workspace](./get_declarative_workspace/) diff --git a/docs/content/en/latest/workspace/workspaces/create_or_update_custom_application_setting.md b/docs/content/en/latest/workspace/workspaces/create_or_update_custom_application_setting.md new file mode 100644 index 000000000..40a71969f --- /dev/null +++ b/docs/content/en/latest/workspace/workspaces/create_or_update_custom_application_setting.md @@ -0,0 +1,45 @@ +--- +title: "create_or_update_custom_application_setting" +linkTitle: "create_or_update_custom_application_setting" +weight: 320 +superheading: "catalog_workspace." +api_ref: "CatalogWorkspaceService.create_or_update_custom_application_setting" +--- + + + +``create_or_update_custom_application_setting(workspace_id: str, custom_application_setting: CatalogCustomApplicationSetting)`` + +Creates a new custom application setting or overwrites an existing one with the same id. + +If the setting has no id, the server generates one. The generated id is not returned and is not assigned to the setting object, so calling the method again with the same id-less setting creates another setting. To update or delete the setting later, either supply an explicit id or look the generated id up with [list_custom_application_settings](../list_custom_application_settings/). + +{{% parameters-block title="Parameters" %}} +{{< parameter p_name="workspace_id" p_type="string" >}} +Workspace identification string e.g. "demo" +{{< /parameter >}} +{{< parameter p_name="custom_application_setting" p_type="CatalogCustomApplicationSetting" >}} +Custom application setting object to be created or updated. `content` can be any JSON object. +{{< /parameter >}} +{{% /parameters-block %}} + +{{% parameters-block title="Returns" %}} +{{< parameter p_type="UpsertOutcome" >}} +CREATED if the setting did not exist yet, UPDATED if it did. +{{< /parameter >}} +{{% /parameters-block %}} + +## Example + +```python +from gooddata_sdk import CatalogCustomApplicationSetting + +setting = CatalogCustomApplicationSetting( + id="my_app_version", + application_name="my_app", + content={"version": "1.2.0"}, +) +sdk.catalog_workspace.create_or_update_custom_application_setting( + workspace_id="demo", custom_application_setting=setting +) +``` diff --git a/docs/content/en/latest/workspace/workspaces/create_or_update_workspace_setting.md b/docs/content/en/latest/workspace/workspaces/create_or_update_workspace_setting.md new file mode 100644 index 000000000..613045d37 --- /dev/null +++ b/docs/content/en/latest/workspace/workspaces/create_or_update_workspace_setting.md @@ -0,0 +1,39 @@ +--- +title: "create_or_update_workspace_setting" +linkTitle: "create_or_update_workspace_setting" +weight: 220 +superheading: "catalog_workspace." +api_ref: "CatalogWorkspaceService.create_or_update_workspace_setting" +--- + + + +``create_or_update_workspace_setting(workspace_id: str, workspace_setting: CatalogWorkspaceSetting)`` + +Creates a new workspace setting or overwrites an existing workspace setting with the same id. + +The setting must have an explicit id. Creating a setting whose id is left as the default `None` currently fails. + +{{% parameters-block title="Parameters" %}} +{{< parameter p_name="workspace_id" p_type="string" >}} +Workspace identification string e.g. "demo" +{{< /parameter >}} +{{< parameter p_name="workspace_setting" p_type="CatalogWorkspaceSetting" >}} +Workspace setting object to be created or updated. +{{< /parameter >}} +{{% /parameters-block %}} + +{{% parameters-block title="Returns" %}} +{{< parameter p_type="UpsertOutcome" >}} +CREATED if the setting did not exist yet, UPDATED if it did. +{{< /parameter >}} +{{% /parameters-block %}} + +## Example + +```python +from gooddata_sdk import CatalogWorkspaceSetting + +setting = CatalogWorkspaceSetting(id="locale", setting_type="LOCALE", content={"value": "fr-FR"}) +sdk.catalog_workspace.create_or_update_workspace_setting(workspace_id="demo", workspace_setting=setting) +``` diff --git a/docs/content/en/latest/workspace/workspaces/delete_custom_application_setting.md b/docs/content/en/latest/workspace/workspaces/delete_custom_application_setting.md new file mode 100644 index 000000000..1c78844f0 --- /dev/null +++ b/docs/content/en/latest/workspace/workspaces/delete_custom_application_setting.md @@ -0,0 +1,31 @@ +--- +title: "delete_custom_application_setting" +linkTitle: "delete_custom_application_setting" +weight: 330 +superheading: "catalog_workspace." +api_ref: "CatalogWorkspaceService.delete_custom_application_setting" +--- + + + +``delete_custom_application_setting(workspace_id: str, custom_application_setting_id: str)`` + +Deletes a custom application setting. Deleting a setting that does not exist does nothing. + +{{% parameters-block title="Parameters" %}} +{{< parameter p_name="workspace_id" p_type="string" >}} +Workspace identification string e.g. "demo" +{{< /parameter >}} +{{< parameter p_name="custom_application_setting_id" p_type="string" >}} +Custom application setting identification string e.g. "my_app_version" +{{< /parameter >}} +{{% /parameters-block %}} + +{{% parameters-block title="Returns" None="yes" %}} +{{% /parameters-block %}} + +## Example + +```python +sdk.catalog_workspace.delete_custom_application_setting(workspace_id="demo", custom_application_setting_id="my_app_version") +``` diff --git a/docs/content/en/latest/workspace/workspaces/delete_workspace_setting.md b/docs/content/en/latest/workspace/workspaces/delete_workspace_setting.md new file mode 100644 index 000000000..d9151d3af --- /dev/null +++ b/docs/content/en/latest/workspace/workspaces/delete_workspace_setting.md @@ -0,0 +1,31 @@ +--- +title: "delete_workspace_setting" +linkTitle: "delete_workspace_setting" +weight: 230 +superheading: "catalog_workspace." +api_ref: "CatalogWorkspaceService.delete_workspace_setting" +--- + + + +``delete_workspace_setting(workspace_id: str, workspace_setting_id: str)`` + +Deletes a workspace setting. Deleting a setting that does not exist does nothing. + +{{% parameters-block title="Parameters" %}} +{{< parameter p_name="workspace_id" p_type="string" >}} +Workspace identification string e.g. "demo" +{{< /parameter >}} +{{< parameter p_name="workspace_setting_id" p_type="string" >}} +Workspace setting identification string e.g. "locale" +{{< /parameter >}} +{{% /parameters-block %}} + +{{% parameters-block title="Returns" None="yes" %}} +{{% /parameters-block %}} + +## Example + +```python +sdk.catalog_workspace.delete_workspace_setting(workspace_id="demo", workspace_setting_id="locale") +``` diff --git a/docs/content/en/latest/workspace/workspaces/get_custom_application_setting.md b/docs/content/en/latest/workspace/workspaces/get_custom_application_setting.md new file mode 100644 index 000000000..c9b5241af --- /dev/null +++ b/docs/content/en/latest/workspace/workspaces/get_custom_application_setting.md @@ -0,0 +1,34 @@ +--- +title: "get_custom_application_setting" +linkTitle: "get_custom_application_setting" +weight: 310 +superheading: "catalog_workspace." +api_ref: "CatalogWorkspaceService.get_custom_application_setting" +--- + + + +``get_custom_application_setting(workspace_id: str, custom_application_setting_id: str)`` + +Gets an individual custom application setting. + +{{% parameters-block title="Parameters" %}} +{{< parameter p_name="workspace_id" p_type="string" >}} +Workspace identification string e.g. "demo" +{{< /parameter >}} +{{< parameter p_name="custom_application_setting_id" p_type="string" >}} +Custom application setting identification string e.g. "my_app_version" +{{< /parameter >}} +{{% /parameters-block %}} + +{{% parameters-block title="Returns" %}} +{{< parameter p_type="CatalogCustomApplicationSetting" >}} +Custom application setting object. +{{< /parameter >}} +{{% /parameters-block %}} + +## Example + +```python +setting = sdk.catalog_workspace.get_custom_application_setting(workspace_id="demo", custom_application_setting_id="my_app_version") +``` diff --git a/docs/content/en/latest/workspace/workspaces/get_workspace_setting.md b/docs/content/en/latest/workspace/workspaces/get_workspace_setting.md new file mode 100644 index 000000000..047b16340 --- /dev/null +++ b/docs/content/en/latest/workspace/workspaces/get_workspace_setting.md @@ -0,0 +1,34 @@ +--- +title: "get_workspace_setting" +linkTitle: "get_workspace_setting" +weight: 210 +superheading: "catalog_workspace." +api_ref: "CatalogWorkspaceService.get_workspace_setting" +--- + + + +``get_workspace_setting(workspace_id: str, workspace_setting_id: str)`` + +Gets an individual workspace setting. + +{{% parameters-block title="Parameters" %}} +{{< parameter p_name="workspace_id" p_type="string" >}} +Workspace identification string e.g. "demo" +{{< /parameter >}} +{{< parameter p_name="workspace_setting_id" p_type="string" >}} +Workspace setting identification string e.g. "locale" +{{< /parameter >}} +{{% /parameters-block %}} + +{{% parameters-block title="Returns" %}} +{{< parameter p_type="CatalogWorkspaceSetting" >}} +Workspace setting object. +{{< /parameter >}} +{{% /parameters-block %}} + +## Example + +```python +setting = sdk.catalog_workspace.get_workspace_setting(workspace_id="demo", workspace_setting_id="locale") +``` diff --git a/docs/content/en/latest/workspace/workspaces/list_custom_application_settings.md b/docs/content/en/latest/workspace/workspaces/list_custom_application_settings.md new file mode 100644 index 000000000..2986ed745 --- /dev/null +++ b/docs/content/en/latest/workspace/workspaces/list_custom_application_settings.md @@ -0,0 +1,34 @@ +--- +title: "list_custom_application_settings" +linkTitle: "list_custom_application_settings" +weight: 300 +superheading: "catalog_workspace." +api_ref: "CatalogWorkspaceService.list_custom_application_settings" +--- + + + +``list_custom_application_settings(workspace_id: str)`` + +Lists the settings of all custom applications in a workspace. + +{{% parameters-block title="Parameters" %}} +{{< parameter p_name="workspace_id" p_type="string" >}} +Workspace identification string e.g. "demo" +{{< /parameter >}} +{{% /parameters-block %}} + +{{% parameters-block title="Returns" %}} +{{< parameter p_type="list[CatalogCustomApplicationSetting]" >}} +List of custom application settings. +{{< /parameter >}} +{{% /parameters-block %}} + +## Example + +```python +settings = sdk.catalog_workspace.list_custom_application_settings(workspace_id="demo") + +# Keep only the settings of one application +my_app_settings = [s for s in settings if s.application_name == "my_app"] +``` diff --git a/docs/content/en/latest/workspace/workspaces/list_workspace_settings.md b/docs/content/en/latest/workspace/workspaces/list_workspace_settings.md new file mode 100644 index 000000000..5e65811b5 --- /dev/null +++ b/docs/content/en/latest/workspace/workspaces/list_workspace_settings.md @@ -0,0 +1,31 @@ +--- +title: "list_workspace_settings" +linkTitle: "list_workspace_settings" +weight: 200 +superheading: "catalog_workspace." +api_ref: "CatalogWorkspaceService.list_workspace_settings" +--- + + + +``list_workspace_settings(workspace_id: str)`` + +Lists all settings of a workspace. + +{{% parameters-block title="Parameters" %}} +{{< parameter p_name="workspace_id" p_type="string" >}} +Workspace identification string e.g. "demo" +{{< /parameter >}} +{{% /parameters-block %}} + +{{% parameters-block title="Returns" %}} +{{< parameter p_type="list[CatalogWorkspaceSetting]" >}} +List of workspace settings. +{{< /parameter >}} +{{% /parameters-block %}} + +## Example + +```python +settings = sdk.catalog_workspace.list_workspace_settings(workspace_id="demo") +``` diff --git a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py index 93567b214..fd9545ea1 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py @@ -287,6 +287,9 @@ CatalogDeclarativeWorkspaceModel, CatalogDeclarativeWorkspaces, ) + from gooddata_sdk.catalog.workspace.entity_model.content_objects.custom_application_setting import ( + CatalogCustomApplicationSetting, + ) from gooddata_sdk.catalog.workspace.entity_model.content_objects.dataset import ( CatalogAttribute, CatalogDataset, @@ -411,6 +414,7 @@ "CatalogContentSlideTemplate": "gooddata_sdk.catalog.organization.common.slide_template", "CatalogCoverSlideTemplate": "gooddata_sdk.catalog.organization.common.slide_template", "CatalogCspDirective": "gooddata_sdk.catalog.organization.entity_model.directive", + "CatalogCustomApplicationSetting": "gooddata_sdk.catalog.workspace.entity_model.content_objects.custom_application_setting", "CatalogDashboardAssigneeIdentifier": "gooddata_sdk.catalog.permission.declarative_model.manage_dashboard_permissions", "CatalogDashboardPermissions": "gooddata_sdk.catalog.permission.declarative_model.dashboard_permissions", "CatalogDashboardSlidesTemplate": "gooddata_sdk.catalog.organization.common.dashboard_slides_template", @@ -684,6 +688,7 @@ "CatalogContentSlideTemplate", "CatalogCoverSlideTemplate", "CatalogCspDirective", + "CatalogCustomApplicationSetting", "CatalogDashboardAssigneeIdentifier", "CatalogDashboardPermissions", "CatalogDashboardSlidesTemplate", diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/entity_model/content_objects/custom_application_setting.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/entity_model/content_objects/custom_application_setting.py new file mode 100644 index 000000000..0533e9de8 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/entity_model/content_objects/custom_application_setting.py @@ -0,0 +1,73 @@ +# (C) 2026 GoodData Corporation +from __future__ import annotations + +from typing import Any, Union + +from attrs import define, field +from gooddata_api_client.model.json_api_custom_application_setting_in import JsonApiCustomApplicationSettingIn +from gooddata_api_client.model.json_api_custom_application_setting_in_attributes import ( + JsonApiCustomApplicationSettingInAttributes, +) +from gooddata_api_client.model.json_api_custom_application_setting_in_document import ( + JsonApiCustomApplicationSettingInDocument, +) +from gooddata_api_client.model.json_api_custom_application_setting_out import JsonApiCustomApplicationSettingOut +from gooddata_api_client.model.json_api_custom_application_setting_post_optional_id import ( + JsonApiCustomApplicationSettingPostOptionalId, +) +from gooddata_api_client.model.json_api_custom_application_setting_post_optional_id_document import ( + JsonApiCustomApplicationSettingPostOptionalIdDocument, +) + +from gooddata_sdk.catalog.base import Base + + +@define(kw_only=True) +class CatalogCustomApplicationSetting(Base): + """A workspace setting owned by a custom application. + + Unlike CatalogWorkspaceSetting, the content is free-form and is scoped by + `application_name` rather than by a fixed setting type. + """ + + id: str | None = field(default=None) + application_name: str + content: dict[str, Any] = field(repr=False) + + @staticmethod + def client_class() -> type[JsonApiCustomApplicationSettingOut]: + return JsonApiCustomApplicationSettingOut + + def _attributes(self) -> JsonApiCustomApplicationSettingInAttributes: + return JsonApiCustomApplicationSettingInAttributes( + application_name=self.application_name, + content=self.content, + ) + + def to_api( + self, post: bool = False + ) -> Union[JsonApiCustomApplicationSettingInDocument, JsonApiCustomApplicationSettingPostOptionalIdDocument]: + if post: + # The generated model rejects id=None; "optional" means the key is omitted + # so that the backend generates the id. + kwargs: dict[str, Any] = {"id": self.id} if self.id is not None else {} + return JsonApiCustomApplicationSettingPostOptionalIdDocument( + data=JsonApiCustomApplicationSettingPostOptionalId(attributes=self._attributes(), **kwargs) + ) + if self.id is None: + raise ValueError( + f"The combination for {post=} and {self.id=} is not valid. Id can be None only for post=True." + ) + return JsonApiCustomApplicationSettingInDocument( + data=JsonApiCustomApplicationSettingIn(id=self.id, attributes=self._attributes()) + ) + + @classmethod + def from_api(cls, entity: dict[str, Any]) -> CatalogCustomApplicationSetting: + # Works on a plain dict: list responses are loaded with _check_return_type=False + # to avoid the composed-schema validation failure on *OutWithLinks items. + return cls( + id=entity["id"], + application_name=entity["attributes"]["applicationName"], + content=entity["attributes"]["content"], + ) diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py index 4faba1cd5..eb5fcda9e 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/service.py @@ -30,6 +30,9 @@ CatalogDeclarativeWorkspaces, get_workspace_folder, ) +from gooddata_sdk.catalog.workspace.entity_model.content_objects.custom_application_setting import ( + CatalogCustomApplicationSetting, +) from gooddata_sdk.catalog.workspace.entity_model.content_objects.workspace_setting import CatalogWorkspaceSetting from gooddata_sdk.catalog.workspace.entity_model.filter_view import ( CatalogFilterView, @@ -204,6 +207,96 @@ def list_workspace_settings(self, workspace_id: str) -> list[CatalogWorkspaceSet workspace_settings = load_all_entities(get_workspace_settings).data return [CatalogWorkspaceSetting.from_api(ws) for ws in workspace_settings] + def create_or_update_custom_application_setting( + self, workspace_id: str, custom_application_setting: CatalogCustomApplicationSetting + ) -> UpsertOutcome: + """Create a new custom application setting or overwrite an existing one with the same id. + + Args: + workspace_id (str): + ID of workspace where we create the setting + custom_application_setting (CatalogCustomApplicationSetting): + Catalog Custom Application Setting object to be created or updated. + If its id is None, the backend generates one. The generated id is not + returned or set on the object; use list_custom_application_settings to find it. + + Returns: + UpsertOutcome: + CREATED if the setting did not exist yet, UPDATED if it did. + """ + if custom_application_setting.id is None: + self._entities_api.create_entity_custom_application_settings( + workspace_id, custom_application_setting.to_api(True) + ) + return UpsertOutcome.CREATED + try: + self.get_custom_application_setting(workspace_id, custom_application_setting.id) + self._entities_api.update_entity_custom_application_settings( + workspace_id, + custom_application_setting.id, + custom_application_setting.to_api(), + ) + except NotFoundException: + self._entities_api.create_entity_custom_application_settings( + workspace_id, custom_application_setting.to_api(True) + ) + return UpsertOutcome.CREATED + return UpsertOutcome.UPDATED + + def delete_custom_application_setting(self, workspace_id: str, custom_application_setting_id: str) -> None: + """Delete a custom application setting. Deleting a setting that does not exist is a no-op. + + Args: + workspace_id (str): + ID of workspace containing the setting + custom_application_setting_id (str): + ID of the setting to delete + """ + try: + self._entities_api.delete_entity_custom_application_settings(workspace_id, custom_application_setting_id) + except NotFoundException: + pass + + def get_custom_application_setting( + self, workspace_id: str, custom_application_setting_id: str + ) -> CatalogCustomApplicationSetting: + """Get a single custom application setting. + + Args: + workspace_id (str): + ID of workspace containing the setting + custom_application_setting_id (str): + ID of the setting + + Returns: + CatalogCustomApplicationSetting: + The requested setting. + """ + return CatalogCustomApplicationSetting.from_api( + self._entities_api.get_entity_custom_application_settings( + workspace_id, custom_application_setting_id, _check_return_type=False + ).data + ) + + def list_custom_application_settings(self, workspace_id: str) -> list[CatalogCustomApplicationSetting]: + """List all custom application settings in a workspace. + + Args: + workspace_id (str): + ID of workspace + + Returns: + list[CatalogCustomApplicationSetting]: + Settings of all custom applications in the workspace. + """ + get_custom_application_settings = functools.partial( + self._entities_api.get_all_entities_custom_application_settings, + workspace_id, + _check_return_type=False, + ) + custom_application_settings = load_all_entities(get_custom_application_settings).data + return [CatalogCustomApplicationSetting.from_api(cas) for cas in custom_application_settings] + def resolve_all_workspace_settings(self, workspace_id: str) -> dict: """Resolve values for all settings in a workspace. diff --git a/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/create_custom_application_setting.yaml b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/create_custom_application_setting.yaml new file mode 100644 index 000000000..db54e2c2d --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/create_custom_application_setting.yaml @@ -0,0 +1,196 @@ +# (C) 2026 GoodData Corporation +interactions: + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: + detail: The requested endpoint does not exist or you do not have permission + to access it. + status: 404 + title: Not Found + traceId: NORMALIZED_TRACE_ID_000000000000 + type: about:blank + headers: + Content-Type: + - application/problem+json + DATE: &id001 + - PLACEHOLDER + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 404 + message: Not Found + - request: + body: + data: + attributes: + applicationName: test_app + content: + tags: + - a + - b + version: 1 + id: test_custom_app_setting + type: customApplicationSetting + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + Content-Type: + - application/json + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: POST + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings + response: + body: + string: + data: + attributes: + applicationName: test_app + content: + tags: + - a + - b + version: 1 + id: test_custom_app_setting + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 201 + message: Created + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: + data: + attributes: + applicationName: test_app + content: + tags: + - a + - b + version: 1 + id: test_custom_app_setting + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: DELETE + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: '' + headers: + Content-Type: + - application/vnd.gooddata.api+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 204 + message: No Content +version: 1 diff --git a/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/create_custom_application_setting_without_id.yaml b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/create_custom_application_setting_without_id.yaml new file mode 100644 index 000000000..219d88cac --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/create_custom_application_setting_without_id.yaml @@ -0,0 +1,199 @@ +# (C) 2026 GoodData Corporation +interactions: + - request: + body: + data: + attributes: + applicationName: test_app + content: + version: 1 + type: customApplicationSetting + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + Content-Type: + - application/json + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: POST + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings + response: + body: + string: + data: + attributes: + applicationName: test_app + content: + version: 1 + id: 93e09df2-109c-43d7-8877-87d037b11202 + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/93e09df2-109c-43d7-8877-87d037b11202 + headers: + Content-Type: + - application/json + DATE: &id001 + - PLACEHOLDER + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 201 + message: Created + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=0&size=500 + response: + body: + string: + data: + - attributes: + applicationName: test_app + content: + version: 1 + id: 93e09df2-109c-43d7-8877-87d037b11202 + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/93e09df2-109c-43d7-8877-87d037b11202 + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + next: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=1&size=500 + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=0&size=500 + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=0&size=500 + response: + body: + string: + data: + - attributes: + applicationName: test_app + content: + version: 1 + id: 93e09df2-109c-43d7-8877-87d037b11202 + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/93e09df2-109c-43d7-8877-87d037b11202 + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + next: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=1&size=500 + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=0&size=500 + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: DELETE + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/93e09df2-109c-43d7-8877-87d037b11202 + response: + body: + string: '' + headers: + Content-Type: + - application/vnd.gooddata.api+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 204 + message: No Content +version: 1 diff --git a/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/delete_custom_application_setting.yaml b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/delete_custom_application_setting.yaml new file mode 100644 index 000000000..5c5fcc845 --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/delete_custom_application_setting.yaml @@ -0,0 +1,246 @@ +# (C) 2026 GoodData Corporation +interactions: + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: + detail: The requested endpoint does not exist or you do not have permission + to access it. + status: 404 + title: Not Found + traceId: NORMALIZED_TRACE_ID_000000000000 + type: about:blank + headers: + Content-Type: + - application/problem+json + DATE: &id001 + - PLACEHOLDER + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 404 + message: Not Found + - request: + body: + data: + attributes: + applicationName: test_app + content: + version: 1 + id: test_custom_app_setting + type: customApplicationSetting + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + Content-Type: + - application/json + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: POST + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings + response: + body: + string: + data: + attributes: + applicationName: test_app + content: + version: 1 + id: test_custom_app_setting + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 201 + message: Created + - request: + body: null + headers: + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: DELETE + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: '' + headers: + Content-Type: + - application/vnd.gooddata.api+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 204 + message: No Content + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=0&size=500 + response: + body: + string: + data: [] + links: + next: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=1&size=500 + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=0&size=500 + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: DELETE + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: '' + headers: + Content-Type: + - application/vnd.gooddata.api+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 204 + message: No Content + - request: + body: null + headers: + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: DELETE + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: '' + headers: + Content-Type: + - application/vnd.gooddata.api+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 204 + message: No Content +version: 1 diff --git a/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/list_custom_application_settings.yaml b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/list_custom_application_settings.yaml new file mode 100644 index 000000000..ebd3a9950 --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/list_custom_application_settings.yaml @@ -0,0 +1,336 @@ +# (C) 2026 GoodData Corporation +interactions: + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting_1 + response: + body: + string: + detail: The requested endpoint does not exist or you do not have permission + to access it. + status: 404 + title: Not Found + traceId: NORMALIZED_TRACE_ID_000000000000 + type: about:blank + headers: + Content-Type: + - application/problem+json + DATE: &id001 + - PLACEHOLDER + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 404 + message: Not Found + - request: + body: + data: + attributes: + applicationName: test_app_a + content: + version: 1 + id: test_custom_app_setting_1 + type: customApplicationSetting + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + Content-Type: + - application/json + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: POST + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings + response: + body: + string: + data: + attributes: + applicationName: test_app_a + content: + version: 1 + id: test_custom_app_setting_1 + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting_1 + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 201 + message: Created + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting_2 + response: + body: + string: + detail: The requested endpoint does not exist or you do not have permission + to access it. + status: 404 + title: Not Found + traceId: NORMALIZED_TRACE_ID_000000000000 + type: about:blank + headers: + Content-Type: + - application/problem+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 404 + message: Not Found + - request: + body: + data: + attributes: + applicationName: test_app_b + content: + enabled: true + id: test_custom_app_setting_2 + type: customApplicationSetting + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + Content-Type: + - application/json + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: POST + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings + response: + body: + string: + data: + attributes: + applicationName: test_app_b + content: + enabled: true + id: test_custom_app_setting_2 + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting_2 + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 201 + message: Created + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=0&size=500 + response: + body: + string: + data: + - attributes: + applicationName: test_app_a + content: + version: 1 + id: test_custom_app_setting_1 + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting_1 + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + - attributes: + applicationName: test_app_b + content: + enabled: true + id: test_custom_app_setting_2 + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting_2 + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + next: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=1&size=500 + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings?page=0&size=500 + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: DELETE + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting_1 + response: + body: + string: '' + headers: + Content-Type: + - application/vnd.gooddata.api+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 204 + message: No Content + - request: + body: null + headers: + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: DELETE + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting_2 + response: + body: + string: '' + headers: + Content-Type: + - application/vnd.gooddata.api+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 204 + message: No Content +version: 1 diff --git a/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/update_custom_application_setting.yaml b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/update_custom_application_setting.yaml new file mode 100644 index 000000000..f904baa25 --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/fixtures/workspaces/update_custom_application_setting.yaml @@ -0,0 +1,294 @@ +# (C) 2026 GoodData Corporation +interactions: + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: + detail: The requested endpoint does not exist or you do not have permission + to access it. + status: 404 + title: Not Found + traceId: NORMALIZED_TRACE_ID_000000000000 + type: about:blank + headers: + Content-Type: + - application/problem+json + DATE: &id001 + - PLACEHOLDER + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 404 + message: Not Found + - request: + body: + data: + attributes: + applicationName: test_app + content: + version: 1 + id: test_custom_app_setting + type: customApplicationSetting + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + Content-Type: + - application/json + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: POST + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings + response: + body: + string: + data: + attributes: + applicationName: test_app + content: + version: 1 + id: test_custom_app_setting + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 201 + message: Created + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: + data: + attributes: + applicationName: test_app + content: + version: 1 + id: test_custom_app_setting + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 200 + message: OK + - request: + body: + data: + attributes: + applicationName: test_app + content: + version: 2 + id: test_custom_app_setting + type: customApplicationSetting + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + Content-Type: + - application/json + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: PUT + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: + data: + attributes: + applicationName: test_app + content: + version: 2 + id: test_custom_app_setting + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: GET + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: + data: + attributes: + applicationName: test_app + content: + version: 2 + id: test_custom_app_setting + meta: + origin: + originId: demo + originType: NATIVE + type: customApplicationSetting + links: + self: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + headers: + Content-Type: + - application/json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept-Encoding: + - br, gzip, deflate + X-GDC-VALIDATE-RELATIONS: + - 'true' + X-Requested-With: + - XMLHttpRequest + method: DELETE + uri: http://localhost:3000/api/v1/entities/workspaces/demo/customApplicationSettings/test_custom_app_setting + response: + body: + string: '' + headers: + Content-Type: + - application/vnd.gooddata.api+json + DATE: *id001 + Expires: + - '0' + Featurepolicy: + - geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr + 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope + 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'; + Pragma: + - no-cache + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-GDC-TRACE-ID: *id001 + status: + code: 204 + message: No Content +version: 1 diff --git a/packages/gooddata-sdk/tests/catalog/test_catalog_workspace.py b/packages/gooddata-sdk/tests/catalog/test_catalog_workspace.py index 91de1448b..ba0277981 100644 --- a/packages/gooddata-sdk/tests/catalog/test_catalog_workspace.py +++ b/packages/gooddata-sdk/tests/catalog/test_catalog_workspace.py @@ -10,6 +10,7 @@ from gooddata_sdk import ( BasicCredentials, CatalogAutomationSchedule, + CatalogCustomApplicationSetting, CatalogDataSourcePostgres, CatalogDeclarativeAutomation, CatalogDeclarativeFilterView, @@ -878,6 +879,102 @@ def test_update_workspace_setting(test_config): safe_delete(sdk.catalog_workspace.delete_workspace_setting, test_config["workspace"], setting_id) +@gd_vcr.use_cassette(str(_fixtures_dir / "create_custom_application_setting.yaml")) +def test_create_custom_application_setting(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + setting_id = "test_custom_app_setting" + setting = CatalogCustomApplicationSetting( + id=setting_id, application_name="test_app", content={"version": 1, "tags": ["a", "b"]} + ) + + try: + outcome = sdk.catalog_workspace.create_or_update_custom_application_setting(test_config["workspace"], setting) + assert outcome == UpsertOutcome.CREATED + setting_o = sdk.catalog_workspace.get_custom_application_setting(test_config["workspace"], setting_id) + assert setting_o == setting + finally: + safe_delete(sdk.catalog_workspace.delete_custom_application_setting, test_config["workspace"], setting_id) + + +@gd_vcr.use_cassette(str(_fixtures_dir / "create_custom_application_setting_without_id.yaml")) +def test_create_custom_application_setting_without_id(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + setting = CatalogCustomApplicationSetting(application_name="test_app", content={"version": 1}) + + def _delete_test_app_settings() -> None: + # The id is generated by the server, so find what to delete by application name. + for s in sdk.catalog_workspace.list_custom_application_settings(test_config["workspace"]): + if s.application_name == setting.application_name: + sdk.catalog_workspace.delete_custom_application_setting(test_config["workspace"], s.id) + + try: + outcome = sdk.catalog_workspace.create_or_update_custom_application_setting(test_config["workspace"], setting) + assert outcome == UpsertOutcome.CREATED + settings = sdk.catalog_workspace.list_custom_application_settings(test_config["workspace"]) + assert len(settings) == 1 + assert settings[0].id is not None + assert settings[0].application_name == setting.application_name + assert settings[0].content == setting.content + finally: + safe_delete(_delete_test_app_settings) + + +@gd_vcr.use_cassette(str(_fixtures_dir / "list_custom_application_settings.yaml")) +def test_list_custom_application_settings(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + new_setting_1 = CatalogCustomApplicationSetting( + id="test_custom_app_setting_1", application_name="test_app_a", content={"version": 1} + ) + new_setting_2 = CatalogCustomApplicationSetting( + id="test_custom_app_setting_2", application_name="test_app_b", content={"enabled": True} + ) + + try: + sdk.catalog_workspace.create_or_update_custom_application_setting(test_config["workspace"], new_setting_1) + sdk.catalog_workspace.create_or_update_custom_application_setting(test_config["workspace"], new_setting_2) + settings = sdk.catalog_workspace.list_custom_application_settings(test_config["workspace"]) + assert len(settings) == 2 + assert new_setting_1 in settings + assert new_setting_2 in settings + finally: + for setting in (new_setting_1, new_setting_2): + safe_delete(sdk.catalog_workspace.delete_custom_application_setting, test_config["workspace"], setting.id) + + +@gd_vcr.use_cassette(str(_fixtures_dir / "update_custom_application_setting.yaml")) +def test_update_custom_application_setting(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + setting_id = "test_custom_app_setting" + setting = CatalogCustomApplicationSetting(id=setting_id, application_name="test_app", content={"version": 1}) + + try: + outcome = sdk.catalog_workspace.create_or_update_custom_application_setting(test_config["workspace"], setting) + assert outcome == UpsertOutcome.CREATED + setting = CatalogCustomApplicationSetting(id=setting_id, application_name="test_app", content={"version": 2}) + outcome = sdk.catalog_workspace.create_or_update_custom_application_setting(test_config["workspace"], setting) + assert outcome == UpsertOutcome.UPDATED + setting_o = sdk.catalog_workspace.get_custom_application_setting(test_config["workspace"], setting_id) + assert setting_o == setting + finally: + safe_delete(sdk.catalog_workspace.delete_custom_application_setting, test_config["workspace"], setting_id) + + +@gd_vcr.use_cassette(str(_fixtures_dir / "delete_custom_application_setting.yaml")) +def test_delete_custom_application_setting(test_config): + sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) + setting_id = "test_custom_app_setting" + setting = CatalogCustomApplicationSetting(id=setting_id, application_name="test_app", content={"version": 1}) + + try: + sdk.catalog_workspace.create_or_update_custom_application_setting(test_config["workspace"], setting) + sdk.catalog_workspace.delete_custom_application_setting(test_config["workspace"], setting_id) + assert sdk.catalog_workspace.list_custom_application_settings(test_config["workspace"]) == [] + # Deleting a setting that no longer exists is a no-op. + sdk.catalog_workspace.delete_custom_application_setting(test_config["workspace"], setting_id) + finally: + safe_delete(sdk.catalog_workspace.delete_custom_application_setting, test_config["workspace"], setting_id) + + @gd_vcr.use_cassette(str(_fixtures_dir / "get_metadata_localization.yaml")) def test_get_metadata_localization(test_config): sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"]) diff --git a/packages/gooddata-sdk/tests/catalog/test_upsert_outcome.py b/packages/gooddata-sdk/tests/catalog/test_upsert_outcome.py index b2d81434b..bf96ed830 100644 --- a/packages/gooddata-sdk/tests/catalog/test_upsert_outcome.py +++ b/packages/gooddata-sdk/tests/catalog/test_upsert_outcome.py @@ -7,6 +7,7 @@ from attrs import evolve from gooddata_api_client.exceptions import ApiTypeError, NotFoundException from gooddata_sdk import ( + CatalogCustomApplicationSetting, CatalogExportTemplate, CatalogWorkspaceSetting, UpsertOutcome, @@ -93,6 +94,29 @@ def test_updated_when_present(self): service._entities_api.update_entity_workspace_settings.assert_called_once() +class TestCustomApplicationSettingOutcome: + def _setting(self, setting_id: str | None = "app_setting") -> CatalogCustomApplicationSetting: + return CatalogCustomApplicationSetting(id=setting_id, application_name="app", content={"version": 1}) + + def test_created_when_absent(self): + service = _service(CatalogWorkspaceService, "get_custom_application_setting", found=False) + assert service.create_or_update_custom_application_setting("demo", self._setting()) == UpsertOutcome.CREATED + service._entities_api.create_entity_custom_application_settings.assert_called_once() + + def test_updated_when_present(self): + service = _service(CatalogWorkspaceService, "get_custom_application_setting", found=True) + assert service.create_or_update_custom_application_setting("demo", self._setting()) == UpsertOutcome.UPDATED + service._entities_api.update_entity_custom_application_settings.assert_called_once() + service._entities_api.create_entity_custom_application_settings.assert_not_called() + + def test_created_without_id(self): + # Unlike the other entities, the id-less path works: to_api omits the id key. + service = _service(CatalogWorkspaceService, "get_custom_application_setting", found=False) + assert service.create_or_update_custom_application_setting("demo", self._setting(None)) == UpsertOutcome.CREATED + service.get_custom_application_setting.assert_not_called() + service._entities_api.create_entity_custom_application_settings.assert_called_once() + + class TestExportTemplateOutcome: def test_created_when_absent(self): service = _service(CatalogOrganizationService, "get_export_template", found=False)