Skip to content

Commit 442c688

Browse files
Merge pull request #1129 from hinashi/fixed/join_modal_slow
Fixed slow display of join_attr modal
2 parents 5d9fe22 + 5d27ae5 commit 442c688

13 files changed

+144
-171
lines changed

apiclient/typescript-fetch/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dmm-com/airone-apiclient-typescript-fetch",
3-
"version": "0.0.13",
3+
"version": "0.0.14",
44
"description": "AirOne APIv2 client in TypeScript",
55
"main": "src/autogenerated/index.ts",
66
"scripts": {

entity/api_v2/serializers.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class EntityAttrSerializer(serializers.ModelSerializer):
194194

195195
class Meta:
196196
model = EntityAttr
197-
fields = ("id", "name", "type", "referral")
197+
fields = ("id", "name", "type")
198198

199199

200200
class EntitySerializer(serializers.ModelSerializer):
@@ -649,4 +649,4 @@ def _do_import(resource, iter_data: Any):
649649

650650

651651
class EntityAttrNameSerializer(serializers.ListSerializer):
652-
child = EntityAttrSerializer()
652+
child = serializers.CharField()

entity/api_v2/views.py

+23-29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from distutils.util import strtobool
2-
from typing import Dict, List
32

4-
from django.db.models import Count, F
3+
from django.db.models import F
54
from django.http import Http404
65
from django.http.response import HttpResponse, JsonResponse
76
from django_filters.rest_framework import DjangoFilterBackend
@@ -13,7 +12,6 @@
1312
from rest_framework.permissions import BasePermission, IsAuthenticated
1413
from rest_framework.request import Request
1514
from rest_framework.response import Response
16-
from rest_framework.serializers import Serializer
1715

1816
from airone.lib.acl import ACLType, get_permitted_objects
1917
from airone.lib.drf import ObjectNotExistsError, YAMLParser, YAMLRenderer
@@ -263,44 +261,40 @@ def get_object(self) -> dict:
263261
@extend_schema(
264262
parameters=[
265263
OpenApiParameter("entity_ids", OpenApiTypes.STR, OpenApiParameter.QUERY),
264+
OpenApiParameter("referral_attr", OpenApiTypes.STR, OpenApiParameter.QUERY),
266265
],
267266
)
268267
class EntityAttrNameAPI(generics.GenericAPIView):
269268
serializer_class = EntityAttrNameSerializer
270269

271270
def get_queryset(self):
272271
entity_ids = list(filter(None, self.request.query_params.get("entity_ids", "").split(",")))
272+
referral_attr = self.request.query_params.get("referral_attr")
273273

274-
if len(entity_ids) == 0:
275-
return EntityAttr.objects.filter(is_active=True)
276-
277-
else:
274+
entity_attrs = EntityAttr.objects.filter(is_active=True)
275+
if len(entity_ids) != 0:
278276
entities = Entity.objects.filter(id__in=entity_ids, is_active=True)
279277
if len(entity_ids) != len(entities):
280278
# the case invalid entity-id was specified
281279
raise ValidationError("Target Entity doesn't exist")
282-
else:
283-
# filter only names appear in all specified entities
284-
return EntityAttr.objects.filter(parent_entity__in=entities, is_active=True)
280+
281+
# filter only names appear in all specified entities
282+
entity_attrs = entity_attrs.filter(parent_entity__in=entities)
283+
284+
if referral_attr:
285+
referral_entity_ids = (
286+
entity_attrs.filter(name=referral_attr)
287+
.exclude(referral__id__isnull=True)
288+
.values_list("referral", flat=True)
289+
.distinct()
290+
)
291+
entity_attrs = EntityAttr.objects.filter(
292+
parent_entity__in=referral_entity_ids, is_active=True
293+
)
294+
295+
return entity_attrs.values_list("name", flat=True).order_by("name").distinct()
285296

286297
def get(self, request: Request) -> Response:
287298
queryset = self.get_queryset()
288-
289-
entity_ids: List[int] = list(
290-
filter(None, self.request.query_params.get("entity_ids", "").split(","))
291-
)
292-
names_query = queryset.values("name")
293-
if len(entity_ids) > 0:
294-
names_query = names_query.annotate(count=Count("name")).filter(count=len(entity_ids))
295-
296-
# Compile each attribute of referrals by attribute name
297-
serializer: Serializer = self.get_serializer(queryset)
298-
results: Dict[str, Dict] = {}
299-
for attrname in names_query.values_list("name", flat=True):
300-
for attrinfo in [x for x in serializer.data if x["name"] == attrname]:
301-
if attrname in results:
302-
results[attrname]["referral"] += attrinfo["referral"]
303-
else:
304-
results[attrname] = attrinfo
305-
306-
return Response(results.values())
299+
serializer: serializers.Serializer = self.get_serializer(queryset)
300+
return Response(serializer.data)

entity/tests/test_api_v2.py

+25-10
Original file line numberDiff line numberDiff line change
@@ -3473,6 +3473,10 @@ def test_export(self):
34733473
def test_get_entity_attr_names(self):
34743474
user = self.admin_login()
34753475

3476+
self.ref_entity.delete()
3477+
self.entity.attrs.all().delete()
3478+
self.entity.delete()
3479+
34763480
entity_info = {
34773481
"test_entity1": ["foo", "bar", "fuga"],
34783482
"test_entity2": ["bar", "hoge", "fuga"],
@@ -3495,26 +3499,37 @@ def test_get_entity_attr_names(self):
34953499

34963500
entity.attrs.add(attr)
34973501

3498-
self.ref_entity.delete()
3499-
self.entity.attrs.all().delete()
3500-
self.entity.delete()
3502+
entities = Entity.objects.filter(name__contains="test_entity")
3503+
3504+
entity3 = self.create_entity(
3505+
user,
3506+
"test_entity3",
3507+
[
3508+
{
3509+
"name": "puyo",
3510+
"type": AttrTypeValue["object"],
3511+
"ref": entities.first(),
3512+
}
3513+
],
3514+
)
35013515

35023516
# get partially
3503-
entities = Entity.objects.filter(name__contains="test_entity")
35043517
resp = self.client.get(
3505-
"/entity/api/v2/attrs?entity_ids=%s" % ",".join([str(x.id) for x in entities])
3518+
"/entity/api/v2/attrs?entity_ids=%s" % ",".join([str(x.id) for x in entities[:2]])
35063519
)
35073520
self.assertEqual(resp.status_code, 200)
3508-
self.assertEqual([x["name"] for x in resp.json()], sorted(["bar", "fuga"]))
3509-
self.assertEqual(
3510-
[x["referral"] for x in resp.json() if x["type"] == AttrType.OBJECT][0],
3511-
list(entities.values_list("id", flat=True)),
3521+
self.assertEqual(resp.json(), ["bar", "foo", "fuga", "hoge"])
3522+
3523+
resp = self.client.get(
3524+
"/entity/api/v2/attrs?entity_ids=%s&referral_attr=%s" % (entity3.id, "puyo")
35123525
)
3526+
self.assertEqual(resp.status_code, 200)
3527+
self.assertEqual(resp.json(), ["bar", "foo", "fuga"])
35133528

35143529
# get all attribute infomations are returned collectly
35153530
resp = self.client.get("/entity/api/v2/attrs")
35163531
self.assertEqual(resp.status_code, 200)
3517-
self.assertEqual([x["name"] for x in resp.json()], ["foo", "bar", "fuga", "hoge"])
3532+
self.assertEqual(resp.json(), ["bar", "foo", "fuga", "hoge", "puyo"])
35183533

35193534
# invalid entity_id(s)
35203535
resp = self.client.get("/entity/api/v2/attrs?entity_ids=9999")

frontend/src/components/entry/AdvancedSearchJoinModal.tsx

+20-20
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,36 @@ import { aironeApiClient } from "repository/AironeApiClient";
1212
import { formatAdvancedSearchParams } from "services/entry/AdvancedSearch";
1313

1414
interface Props {
15+
targetEntityIds: number[];
16+
searchAllEntities: boolean;
1517
targetAttrname: string;
16-
referralIds: number[] | undefined;
1718
joinAttrs: AdvancedSearchJoinAttrInfo[];
18-
setJoinAttrname: (name: string) => void;
19+
handleClose: () => void;
1920
}
2021

2122
export const AdvancedSearchJoinModal: FC<Props> = ({
23+
targetEntityIds,
24+
searchAllEntities,
2225
targetAttrname,
23-
referralIds,
2426
joinAttrs,
25-
setJoinAttrname,
27+
handleClose,
2628
}) => {
2729
const history = useHistory();
2830
// This is join attributes that have been already been selected before.
2931
const currentAttrInfo: AdvancedSearchJoinAttrInfo | undefined =
3032
joinAttrs.find((attr) => attr.name === targetAttrname);
3133

32-
const [selectedAttrNames, setSelectedAttrNames] = useState<string[]>([]);
34+
const [selectedAttrNames, setSelectedAttrNames] = useState<Array<string>>(
35+
currentAttrInfo?.attrinfo.map((attr) => attr.name) ?? []
36+
);
3337

3438
const referralAttrs = useAsyncWithThrow(async () => {
35-
if (referralIds !== undefined && referralIds.length > 0) {
36-
return await aironeApiClient.getEntityAttrs(referralIds);
37-
}
38-
return [];
39-
}, [referralIds]);
40-
41-
const closeModal = () => {
42-
setJoinAttrname("");
43-
};
39+
return await aironeApiClient.getEntityAttrs(
40+
targetEntityIds,
41+
searchAllEntities,
42+
targetAttrname
43+
);
44+
}, [targetEntityIds, searchAllEntities, targetAttrname]);
4445

4546
const handleUpdatePageURL = () => {
4647
// to prevent duplication of same name parameter
@@ -71,19 +72,18 @@ export const AdvancedSearchJoinModal: FC<Props> = ({
7172
pathname: location.pathname,
7273
search: "?" + params.toString(),
7374
});
74-
history.go(0);
7575
};
7676

7777
return (
7878
<AironeModal
7979
title={"結合するアイテムの属性名"}
8080
open={targetAttrname !== ""}
81-
onClose={() => closeModal()}
81+
onClose={handleClose}
8282
>
8383
<Autocomplete
84-
options={referralAttrs.value?.map((x) => x.name) || []}
85-
defaultValue={currentAttrInfo?.attrinfo.map((x) => x.name) || []}
86-
onChange={(_, value: string[]) => {
84+
options={referralAttrs.value ?? []}
85+
value={selectedAttrNames}
86+
onChange={(_, value: Array<string>) => {
8787
setSelectedAttrNames(value);
8888
}}
8989
renderInput={(params) => (
@@ -105,7 +105,7 @@ export const AdvancedSearchJoinModal: FC<Props> = ({
105105
variant="outlined"
106106
color="primary"
107107
sx={{ mx: "4px" }}
108-
onClick={() => closeModal()}
108+
onClick={handleClose}
109109
>
110110
キャンセル
111111
</Button>

frontend/src/components/entry/SearchResults.test.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ test("should render a component with essential props", function () {
2525
/* do nothing */
2626
}}
2727
hasReferral={false}
28-
setJoinAttrname={() => {
29-
/* do nothing */
30-
}}
28+
entityIds={[]}
29+
joinAttrs={[]}
30+
searchAllEntities={false}
3131
/>,
3232
{ wrapper: TestWrapper }
3333
)

frontend/src/components/entry/SearchResults.tsx

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { AdvancedSearchResult } from "@dmm-com/airone-apiclient-typescript-fetch";
1+
import {
2+
AdvancedSearchJoinAttrInfo,
3+
AdvancedSearchResult,
4+
} from "@dmm-com/airone-apiclient-typescript-fetch";
25
import {
36
Box,
47
Checkbox,
@@ -13,7 +16,7 @@ import {
1316
Typography,
1417
} from "@mui/material";
1518
import { styled } from "@mui/material/styles";
16-
import React, { Dispatch, FC, SetStateAction, useMemo } from "react";
19+
import React, { FC, useMemo } from "react";
1720
import { Link } from "react-router-dom";
1821

1922
import { SearchResultsTableHead } from "./SearchResultsTableHead";
@@ -55,7 +58,9 @@ interface Props {
5558
defaultAttrsFilter?: AttrsFilter;
5659
bulkOperationEntryIds: Array<number>;
5760
handleChangeBulkOperationEntryId: (id: number, checked: boolean) => void;
58-
setJoinAttrname: Dispatch<SetStateAction<string>>;
61+
entityIds: number[];
62+
searchAllEntities: boolean;
63+
joinAttrs: AdvancedSearchJoinAttrInfo[];
5964
}
6065

6166
export const SearchResults: FC<Props> = ({
@@ -68,7 +73,9 @@ export const SearchResults: FC<Props> = ({
6873
defaultAttrsFilter = {},
6974
bulkOperationEntryIds,
7075
handleChangeBulkOperationEntryId,
71-
setJoinAttrname,
76+
entityIds,
77+
searchAllEntities,
78+
joinAttrs,
7279
}) => {
7380
// NOTE attrTypes are guessed by the first element on the results. So if it has no appropriate attr,
7481
// the type guess doesn't work well. We should improve attr type API if more accurate type is needed.
@@ -94,7 +101,9 @@ export const SearchResults: FC<Props> = ({
94101
defaultEntryFilter={defaultEntryFilter}
95102
defaultReferralFilter={defaultReferralFilter}
96103
defaultAttrsFilter={defaultAttrsFilter}
97-
setJoinAttrname={setJoinAttrname}
104+
entityIds={entityIds}
105+
searchAllEntities={searchAllEntities}
106+
joinAttrs={joinAttrs}
98107
/>
99108
<TableBody>
100109
{results.values?.map((result) => (

0 commit comments

Comments
 (0)