Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize App type resolvers #12804

Merged
merged 2 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions saleor/graphql/app/dataloaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ def batch_load(self, keys):
return [extensions_map.get(app_id, []) for app_id in keys]


class AppTokensByAppIdLoader(DataLoader):
context_key = "app_tokens_by_app_id"

def batch_load(self, keys):
tokens = AppToken.objects.using(self.database_connection_name).filter(
app_id__in=keys
)
tokens_by_app_map = defaultdict(list)
for token in tokens:
tokens_by_app_map[token.app_id].append(token)
return [tokens_by_app_map.get(app_id, []) for app_id in keys]


class AppByTokenLoader(DataLoader):
context_key = "app_by_token"

Expand Down
68 changes: 67 additions & 1 deletion saleor/graphql/app/tests/benchmarks/test_apps.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import graphene
import pytest

from .....app.models import App
from .....app.models import App, AppToken
from .....webhook.models import Webhook
from ....tests.utils import get_graphql_content


Expand Down Expand Up @@ -71,3 +72,68 @@ def test_apps_for_federation_query_count(
)
content = get_graphql_content(response)
assert len(content["data"]["_entities"]) == 3


@pytest.mark.django_db
@pytest.mark.count_queries(autouse=False)
def test_apps_with_tokens_and_webhooks(
staff_api_client,
permission_manage_apps,
django_assert_num_queries,
count_queries,
):
apps = App.objects.bulk_create(
[
App(name="app 1"),
App(name="app 2"),
App(name="app 3"),
]
)

webhooks = []
app_tokens = []
for app in apps:
for i in range(3):
webhooks.append(
Webhook(
app=app,
target_url=f"http://www.example.com/{i}",
name=f"webhook{i}",
)
)
app_tokens.append(
AppToken(
app=app, name=f"token{i}", auth_token=f"auth_token-{app.name}-{i}"
)
)
Webhook.objects.bulk_create(webhooks)
AppToken.objects.bulk_create(app_tokens)

query = """
query apps {
apps(first:100) {
edges {
node {
id
tokens {
name
authToken
}
webhooks {
id
targetUrl
}
}
}
}
}
"""

response = staff_api_client.post_graphql(
query,
{},
permissions=[permission_manage_apps],
check_no_permissions=False,
)
content = get_graphql_content(response)
assert len(content["data"]["apps"]["edges"]) == 3
12 changes: 9 additions & 3 deletions saleor/graphql/app/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@
from ..core.utils import from_global_id_or_error
from ..meta.types import ObjectWithMetadata
from ..utils import format_permissions_for_display, get_user_or_app_from_context
from ..webhook.dataloaders import WebhooksByAppIdLoader
from ..webhook.enums import WebhookEventTypeAsyncEnum, WebhookEventTypeSyncEnum
from ..webhook.types import Webhook
from .dataloaders import AppByIdLoader, AppExtensionByAppIdLoader, app_promise_callback
from .dataloaders import (
AppByIdLoader,
AppExtensionByAppIdLoader,
AppTokensByAppIdLoader,
app_promise_callback,
)
from .enums import AppExtensionMountEnum, AppExtensionTargetEnum, AppTypeEnum
from .resolvers import (
resolve_access_token_for_app,
Expand Down Expand Up @@ -334,12 +340,12 @@ def resolve_permissions(root: models.App, _info):
@staticmethod
def resolve_tokens(root: models.App, info):
has_required_permission(root, info.context)
return root.tokens.all()
return AppTokensByAppIdLoader(info.context).load(root.id)

@staticmethod
def resolve_webhooks(root: models.App, info):
has_required_permission(root, info.context)
return root.webhooks.all()
return WebhooksByAppIdLoader(info.context).load(root.id)

@staticmethod
def resolve_access_token(root: models.App, info):
Expand Down
15 changes: 14 additions & 1 deletion saleor/graphql/webhook/dataloaders.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections import defaultdict

from ...core.models import EventPayload
from ...webhook.models import WebhookEvent
from ...webhook.models import Webhook, WebhookEvent
from ..core.dataloaders import DataLoader


Expand All @@ -28,3 +28,16 @@ def batch_load(self, keys):
webhook_events_map[event.webhook_id].append(event)

return [webhook_events_map.get(webhook_id, []) for webhook_id in keys]


class WebhooksByAppIdLoader(DataLoader):
context_key = "webhooks_by_app_id"

def batch_load(self, keys):
webhooks = Webhook.objects.using(self.database_connection_name).filter(
app_id__in=keys
)
webhooks_by_app_map = defaultdict(list)
for webhook in webhooks:
webhooks_by_app_map[webhook.app_id].append(webhook)
return [webhooks_by_app_map.get(app_id, []) for app_id in keys]