diff --git a/entry/api_v2/serializers.py b/entry/api_v2/serializers.py index 24076a6cc..7ce7fab15 100644 --- a/entry/api_v2/serializers.py +++ b/entry/api_v2/serializers.py @@ -16,7 +16,7 @@ ) from airone.lib.types import AttrDefaultValue, AttrTypeValue from entity.api_v2.serializers import EntitySerializer -from entity.models import Entity +from entity.models import Entity, EntityAttr from entry.models import Attribute, AttributeValue, Entry from entry.settings import CONFIG as CONFIG_ENTRY from group.models import Group @@ -1031,6 +1031,7 @@ class AdvancedSearchResultExportSerializer(serializers.Serializer): entry_name = serializers.CharField( required=False, allow_blank=True, max_length=CONFIG_ENTRY.MAX_QUERY_SIZE ) + is_all_entities = serializers.BooleanField(default=False) export_style = serializers.CharField() def validate_entities(self, entities: List[int]): @@ -1043,6 +1044,22 @@ def validate_export_style(self, export_style: str): raise ValidationError("format must be yaml or csv") return export_style + def validate(self, params): + if params["is_all_entities"]: + attr_names = [x["name"] for x in params["attrinfo"]] + params["entities"] = list( + EntityAttr.objects.filter( + name__in=attr_names, is_active=True, parent_entity__is_active=True + ) + .order_by("parent_entity__name") + .values_list("parent_entity__id", flat=True) + .distinct() + ) + if not params["entities"]: + raise ValidationError("Invalid value for attribute parameter") + + return params + def save(self, **kwargs): user: User = self.context["request"].user diff --git a/entry/tests/test_api_v2.py b/entry/tests/test_api_v2.py index e7d041391..2f7d76403 100644 --- a/entry/tests/test_api_v2.py +++ b/entry/tests/test_api_v2.py @@ -3838,6 +3838,7 @@ def test_duplicate_export(self): export_params = { "entities": [entity.id], "attrinfo": [{"name": "attr", "keyword": "data-5"}], + "is_all_entities": False, "export_style": "csv", } @@ -4030,6 +4031,55 @@ def test_export_advanced_search_result_with_no_value(self): yaml_contents["test-entity"][0]["attrs"], {x["column"]: x["yaml"] for x in results} ) + @patch( + "dashboard.tasks.export_search_result.delay", + Mock(side_effect=dashboard_tasks.export_search_result), + ) + def test_export_with_all_entities(self): + self.add_entry(self.user, "Entry", self.entity, values={"val": "hoge"}) + + resp = self.client.post( + "/entry/api/v2/advanced_search_result_export/", + json.dumps( + { + "entities": [], + "attrinfo": [{"name": "hoge"}], + "is_all_entities": "true", + "export_style": "yaml", + } + ), + "application/json", + ) + self.assertEqual(resp.status_code, 400) + self.assertEqual( + resp.json(), + { + "non_field_errors": [ + { + "code": "AE-121000", + "message": "Invalid value for attribute parameter", + } + ] + }, + ) + + resp = self.client.post( + "/entry/api/v2/advanced_search_result_export/", + json.dumps( + { + "entities": [], + "attrinfo": [{"name": "val"}], + "is_all_entities": "true", + "export_style": "yaml", + } + ), + "application/json", + ) + + self.assertEqual(resp.status_code, 200) + resp_data = yaml.load(Job.objects.last().get_cache(), Loader=yaml.FullLoader) + self.assertEqual(resp_data, {"test-entity": [{"attrs": {"val": "hoge"}, "name": "Entry"}]}) + def test_entry_history(self): values = { "val": {"value": "hoge", "result": {"as_string": "hoge"}}, diff --git a/frontend/src/apiclient/autogenerated/models/AdvancedSearchResultExport.ts b/frontend/src/apiclient/autogenerated/models/AdvancedSearchResultExport.ts index 90bcf27ee..970609cba 100644 --- a/frontend/src/apiclient/autogenerated/models/AdvancedSearchResultExport.ts +++ b/frontend/src/apiclient/autogenerated/models/AdvancedSearchResultExport.ts @@ -56,6 +56,12 @@ export interface AdvancedSearchResultExport { * @memberof AdvancedSearchResultExport */ entryName?: string; + /** + * + * @type {boolean} + * @memberof AdvancedSearchResultExport + */ + isAllEntities?: boolean; /** * * @type {string} @@ -89,6 +95,9 @@ export function AdvancedSearchResultExportFromJSONTyped( ? undefined : json["referral_name"], entryName: !exists(json, "entry_name") ? undefined : json["entry_name"], + isAllEntities: !exists(json, "is_all_entities") + ? undefined + : json["is_all_entities"], exportStyle: json["export_style"], }; } @@ -110,6 +119,7 @@ export function AdvancedSearchResultExportToJSON( has_referral: value.hasReferral, referral_name: value.referralName, entry_name: value.entryName, + is_all_entities: value.isAllEntities, export_style: value.exportStyle, }; } diff --git a/frontend/src/pages/AdvancedSearchResultsPage.tsx b/frontend/src/pages/AdvancedSearchResultsPage.tsx index f33cc5282..fb4c12005 100644 --- a/frontend/src/pages/AdvancedSearchResultsPage.tsx +++ b/frontend/src/pages/AdvancedSearchResultsPage.tsx @@ -60,7 +60,7 @@ export const AdvancedSearchResultsPage: FC = () => { return 0; } return Math.ceil( - results.value?.count ?? 0 / AdvancedSerarchResultList.MAX_ROW_COUNT + (results.value?.count ?? 0) / AdvancedSerarchResultList.MAX_ROW_COUNT ); }, [results.loading, results.value?.count]); @@ -71,6 +71,7 @@ export const AdvancedSearchResultsPage: FC = () => { attrInfo, entryName, hasReferral, + searchAllEntities, exportStyle ); enqueueSnackbar("エクスポートジョブの登録に成功しました", { diff --git a/frontend/src/repository/AironeApiClientV2.ts b/frontend/src/repository/AironeApiClientV2.ts index 8e804ea33..de01faac0 100644 --- a/frontend/src/repository/AironeApiClientV2.ts +++ b/frontend/src/repository/AironeApiClientV2.ts @@ -687,6 +687,7 @@ class AironeApiClientV2 { attrinfo: Array, entryName: string, hasReferral: boolean, + isAllEntities: boolean, format: "yaml" | "csv" ): Promise { await this.entry.entryApiV2AdvancedSearchResultExportCreate( @@ -696,6 +697,7 @@ class AironeApiClientV2 { attrinfo: attrinfo, entryName: entryName, hasReferral: hasReferral, + isAllEntities: isAllEntities, exportStyle: format, }, },