Skip to content

Commit

Permalink
[BUILD-921] Update smart search upsell (#1073)
Browse files Browse the repository at this point in the history
* Add flashcard selector upsell dialog

* Add tests
  • Loading branch information
abdnh authored Jan 15, 2025
1 parent 6126ae5 commit 883a999
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 8 deletions.
58 changes: 51 additions & 7 deletions ankihub/gui/overview.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,29 @@
from concurrent.futures import Future
from functools import partial
from pathlib import Path
from typing import Any, Optional
from typing import Any, Callable, Optional
from uuid import UUID

import aqt
from aqt.gui_hooks import overview_did_refresh, webview_did_receive_js_message
from aqt.utils import tooltip
from aqt.qt import QDialogButtonBox
from aqt.utils import openLink, tooltip
from aqt.webview import AnkiWebView
from jinja2 import Template

from .. import LOGGER
from ..addon_ankihub_client import AddonAnkiHubClient as AnkiHubClient
from ..feature_flags import add_feature_flags_update_callback
from ..settings import config, url_flashcard_selector, url_flashcard_selector_embed
from ..settings import (
config,
url_flashcard_selector,
url_flashcard_selector_embed,
url_plans_page,
)
from .deck_updater import ah_deck_updater
from .js_message_handling import parse_js_message_kwargs
from .menu import AnkiHubLogin
from .utils import get_ah_did_of_deck_or_ancestor_deck
from .utils import get_ah_did_of_deck_or_ancestor_deck, show_dialog
from .webview import AnkiHubWebViewDialog

ADD_FLASHCARD_SELECTOR_BUTTON_JS_PATH = (
Expand Down Expand Up @@ -55,7 +62,6 @@ def _maybe_add_flashcard_selector_button() -> None:

if not aqt.mw.state == "overview":
return

ah_did = get_ah_did_of_deck_or_ancestor_deck(aqt.mw.col.decks.current()["id"])
if (
not config.deck_config(ah_did)
Expand All @@ -81,16 +87,54 @@ def _maybe_add_flashcard_selector_button() -> None:
overview_web.eval(js)


def _show_flashcard_selector_upsell_if_user_has_no_access(
on_done: Callable[[bool], None]
) -> None:
user_details = AnkiHubClient().get_user_details()
has_access = user_details["is_premium"] or user_details["is_trialing"]
if has_access:
on_done(True)
return
show_trial_ended_message = user_details["show_trial_ended_message"]
text = "Let AI do the heavy lifting! Find flashcards perfectly matched to your study materials and elevate your \
learning experience with Premium. 🌟"
if show_trial_ended_message:
title = "Your Trial Has Ended! 🎓✨"
else:
title = "📚 Unlock Your Potential with Premium"

def on_button_clicked(button_index: int) -> None:
if button_index == 1:
openLink(url_plans_page())
on_done(False)

show_dialog(
text,
title,
parent=aqt.mw,
buttons=[
("Not Now", QDialogButtonBox.ButtonRole.RejectRole),
("Learn More", QDialogButtonBox.ButtonRole.HelpRole),
],
default_button_idx=1,
callback=on_button_clicked,
)


def _handle_flashcard_selector_py_commands(
handled: tuple[bool, Any], message: str, context: Any
) -> tuple[bool, Any]:
if message.startswith(FLASHCARD_SELECTOR_OPEN_PYCMD):
kwargs = parse_js_message_kwargs(message)
ah_did = UUID(kwargs.get("deck_id"))

FlashCardSelectorDialog.display_for_ah_did(ah_did=ah_did, parent=aqt.mw)
def on_checked_for_access(has_access: bool) -> None:
if has_access:
FlashCardSelectorDialog.display_for_ah_did(ah_did=ah_did, parent=aqt.mw)
LOGGER.info("Opened flashcard selector dialog.")

_show_flashcard_selector_upsell_if_user_has_no_access(on_checked_for_access)

LOGGER.info("Opened flashcard selector dialog.")
return (True, None)
elif message.startswith(FLASHCARD_SELECTOR_SYNC_NOTES_ACTIONS_PYCMD):
kwargs = parse_js_message_kwargs(message)
Expand Down
80 changes: 79 additions & 1 deletion tests/addon/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
browser_will_show_context_menu,
)
from aqt.importing import AnkiPackageImporter
from aqt.qt import QAction, Qt, QUrl
from aqt.qt import QAction, Qt, QUrl, QWidget
from aqt.theme import theme_manager
from aqt.webview import AnkiWebView
from pytest import fixture
Expand Down Expand Up @@ -5674,6 +5674,16 @@ def test_clicking_button_opens_flashcard_selector_dialog(

mocker.patch.object(AnkiWebView, "load_url")

mocker.patch.object(
AnkiHubClient,
"get_user_details",
return_value={
"is_premium": True,
"is_trialing": False,
"show_trial_ended_message": False,
},
)

overview_web: AnkiWebView = aqt.mw.overview.web
overview_web.eval(
f"document.getElementById('{FLASHCARD_SELECTOR_OPEN_BUTTON_ID}').click()",
Expand Down Expand Up @@ -5711,6 +5721,16 @@ def test_clicking_button_twice_shows_existing_dialog_again(

mocker.patch.object(AnkiWebView, "load_url")

mocker.patch.object(
AnkiHubClient,
"get_user_details",
return_value={
"is_premium": True,
"is_trialing": False,
"show_trial_ended_message": False,
},
)

overview_web: AnkiWebView = aqt.mw.overview.web
overview_web.eval(
f"document.getElementById('{FLASHCARD_SELECTOR_OPEN_BUTTON_ID}').click()",
Expand Down Expand Up @@ -5738,6 +5758,64 @@ def flashcard_selector_opened():

assert FlashCardSelectorDialog.dialog == dialog

@pytest.mark.sequential
@pytest.mark.parametrize(
"show_trial_ended_message",
[False, True],
)
def test_shows_flashcard_selector_upsell_if_no_access(
self,
anki_session_with_addon_data: AnkiSession,
install_ah_deck: InstallAHDeck,
qtbot: QtBot,
set_feature_flag_state: SetFeatureFlagState,
mocker: MockerFixture,
show_trial_ended_message: bool,
):
set_feature_flag_state("show_flashcards_selector_button")

entry_point.run()
with anki_session_with_addon_data.profile_loaded():
mocker.patch.object(config, "token")

anki_did = DeckId(1)
install_ah_deck(
anki_did=anki_did,
has_note_embeddings=True,
)
aqt.mw.deckBrowser.set_current_deck(anki_did)

qtbot.wait(500)

mocker.patch.object(AnkiWebView, "load_url")

mocker.patch.object(
AnkiHubClient,
"get_user_details",
return_value={
"is_premium": False,
"is_trialing": False,
"show_trial_ended_message": show_trial_ended_message,
},
)

overview_web: AnkiWebView = aqt.mw.overview.web
overview_web.eval(
f"document.getElementById('{FLASHCARD_SELECTOR_OPEN_BUTTON_ID}').click()",
)

def upsell_dialog_opened():
dialog: QWidget = aqt.mw.app.activeWindow()
if not isinstance(dialog, utils._Dialog):
return False
return (
"Trial" in dialog.windowTitle()
if show_trial_ended_message
else True
)

qtbot.wait_until(upsell_dialog_opened)

def test_with_no_auth_token(
self,
anki_session_with_addon_data: AnkiSession,
Expand Down

0 comments on commit 883a999

Please sign in to comment.