diff --git a/cognite_toolkit/_cdf_tk/resource_classes/infield_cdmv1.py b/cognite_toolkit/_cdf_tk/resource_classes/infield_cdmv1.py new file mode 100644 index 0000000000..36f99b4152 --- /dev/null +++ b/cognite_toolkit/_cdf_tk/resource_classes/infield_cdmv1.py @@ -0,0 +1,92 @@ +from typing import Any + +from .base import BaseModelResource, ToolkitResource + + +class ObservationFeatureToggles(BaseModelResource): + """Feature toggles for observations.""" + + is_enabled: bool | None = None + is_write_back_enabled: bool | None = None + notifications_endpoint_external_id: str | None = None + attachments_endpoint_external_id: str | None = None + + +class FeatureToggles(BaseModelResource): + """Feature toggles for InField location configuration.""" + + three_d: bool | None = None + trends: bool | None = None + documents: bool | None = None + workorders: bool | None = None + notifications: bool | None = None + media: bool | None = None + template_checklist_flow: bool | None = None + workorder_checklist_flow: bool | None = None + observations: ObservationFeatureToggles | None = None + + +class AccessManagement(BaseModelResource): + """Access management configuration.""" + + template_admins: list[str] | None = None # list of CDF group external IDs + checklist_admins: list[str] | None = None # list of CDF group external IDs + + +class ResourceFilters(BaseModelResource): + """Resource filters.""" + + spaces: list[str] | None = None + + +class RootLocationDataFilters(BaseModelResource): + """Data filters for root location.""" + + general: ResourceFilters | None = None + assets: ResourceFilters | None = None + files: ResourceFilters | None = None + timeseries: ResourceFilters | None = None + + +class DataExplorationConfig(BaseModelResource): + """Properties for DataExplorationConfig node. + + Contains configuration for data exploration features: + - observations: Observations feature configuration + - activities: Activities configuration + - documents: Document configuration + - notifications: Notifications configuration + - assets: Asset page configuration + """ + + external_id: str + + observations: dict[str, Any] | None = None # ObservationsConfigFeature + activities: dict[str, Any] | None = None # ActivitiesConfiguration + documents: dict[str, Any] | None = None # DocumentConfiguration + notifications: dict[str, Any] | None = None # NotificationsConfiguration + assets: dict[str, Any] | None = None # AssetPageConfiguration + + +class InfieldLocationConfigYAML(ToolkitResource): + """Properties for InFieldLocationConfig node. + + Currently migrated fields: + - root_location_external_id: Reference to the LocationFilterDTO external ID + - feature_toggles: Feature toggles migrated from old configuration + - rootAsset: Direct relation to the root asset (space and externalId) + - app_instance_space: Application instance space from appDataInstanceSpace + - access_management: Template and checklist admin groups (from templateAdmins and checklistAdmins) + - disciplines: List of disciplines (from disciplines in FeatureConfiguration) + - data_filters: Data filters for general, assets, files, and timeseries (from dataFilters in old configuration) + - data_exploration_config: Direct relation to the DataExplorationConfig node (shared across all locations) + """ + + external_id: str + + root_location_external_id: str | None = None + feature_toggles: FeatureToggles | None = None + app_instance_space: str | None = None + access_management: AccessManagement | None = None + data_filters: RootLocationDataFilters | None = None + data_exploration_config: DataExplorationConfig | None = None diff --git a/tests/data/complete_org_alpha_flags/modules/my_example_module/cdf_applications/my_location.InfieldCDMv1.yaml b/tests/data/complete_org_alpha_flags/modules/my_example_module/cdf_applications/my_location.InfieldCDMv1.yaml new file mode 100644 index 0000000000..05356a0532 --- /dev/null +++ b/tests/data/complete_org_alpha_flags/modules/my_example_module/cdf_applications/my_location.InfieldCDMv1.yaml @@ -0,0 +1,49 @@ +externalId: my_location_config_001 + +rootLocationExternalId: location_filter_main_facility + +featureToggles: + threeD: true + trends: true + documents: true + workorders: true + notifications: true + media: true + templateChecklistFlow: true + workorderChecklistFlow: false + +appInstanceSpace: my_app_instance_space + +accessManagement: + templateAdmins: + - cdf_group_template_admins + - cdf_group_superusers + checklistAdmins: + - cdf_group_checklist_admins + - cdf_group_superusers + +dataFilters: + general: + spaces: + - my_space_001 + - my_space_002 + +dataExplorationConfig: + externalId: data_exploration_config_shared + observations: + enabled: true + defaultView: list + activities: + enabled: true + showCompleted: true + documents: + enabled: true + supportedFormats: + - pdf + - docx + notifications: + enabled: true + pushEnabled: false + assets: + enabled: true + showHierarchy: true diff --git a/tests/test_unit/test_cdf_tk/test_resource_classes/test_infield_cdmv1.py b/tests/test_unit/test_cdf_tk/test_resource_classes/test_infield_cdmv1.py new file mode 100644 index 0000000000..36fb8a48a8 --- /dev/null +++ b/tests/test_unit/test_cdf_tk/test_resource_classes/test_infield_cdmv1.py @@ -0,0 +1,84 @@ +from collections.abc import Iterable +from pathlib import Path +from typing import Any + +import pytest + +from cognite_toolkit._cdf_tk.resource_classes.infield_cdmv1 import InfieldLocationConfigYAML +from cognite_toolkit._cdf_tk.tk_warnings.fileread import ResourceFormatWarning +from cognite_toolkit._cdf_tk.validation import validate_resource_yaml_pydantic +from tests.data import COMPLETE_ORG_ALPHA_FLAGS +from tests.test_unit.utils import find_resources + + +def invalid_test_cases() -> Iterable: + yield pytest.param( + {"rootLocationExternalId": "myLocation"}, + {"Missing required field: 'externalId'"}, + id="Missing required field: externalId", + ) + yield pytest.param( + { + "externalId": "my_config", + "unknownField": "invalid_value", + "anotherUnknownField": 123, + "featureToggles": { + "threeD": True, + "invalidToggle": "bad_value", + }, + }, + { + "In featureToggles unused field: 'invalidToggle'", + "Unused field: 'anotherUnknownField'", + "Unused field: 'unknownField'", + }, + id="Multiple extra fields at different levels", + ) + yield pytest.param( + { + "externalId": "my_config", + "featureToggles": { + "threeD": "not_a_boolean", + "trends": 123, + "observations": { + "isEnabled": "not_a_boolean", + "isWriteBackEnabled": ["invalid_type"], + }, + }, + "accessManagement": { + "templateAdmins": "should_be_a_list", + "checklistAdmins": 456, + }, + }, + { + "In accessManagement.checklistAdmins input should be a valid list. Got 456.", + "In accessManagement.templateAdmins input should be a valid list. Got 'should_be_a_list'.", + "In featureToggles.observations.isEnabled input should be a valid boolean. " + "Got 'not_a_boolean' of type str.", + "In featureToggles.observations.isWriteBackEnabled input should be a valid " + "boolean. Got ['invalid_type'] of type list.", + "In featureToggles.threeD input should be a valid boolean. Got 'not_a_boolean' of type str.", + "In featureToggles.trends input should be a valid boolean. Got 123 of type int.", + }, + id="Multiple type mismatches across nested structures", + ) + + +class TestInfieldCDMv1YAML: + @pytest.mark.parametrize( + "data", list(find_resources("InfieldCDMv1", resource_dir="cdf_applications", base=COMPLETE_ORG_ALPHA_FLAGS)) + ) + def test_load_valid(self, data: dict[str, Any]) -> None: + loaded = InfieldLocationConfigYAML.model_validate(data) + + dumped = loaded.model_dump(exclude_unset=True, by_alias=True) + assert dumped == data + + @pytest.mark.parametrize("data, expected_errors", list(invalid_test_cases())) + def test_invalid_error_messages(self, data: dict[str, Any], expected_errors: set[str]) -> None: + warning_list = validate_resource_yaml_pydantic(data, InfieldLocationConfigYAML, Path("some_file.yaml")) + assert len(warning_list) == 1 + format_warning = warning_list[0] + assert isinstance(format_warning, ResourceFormatWarning) + + assert set(format_warning.errors) == expected_errors