separate-stat-query
All checks were successful
Deploy on push / deploy (push) Successful in 22s

This commit is contained in:
Untone 2024-04-23 14:31:34 +03:00
parent 8e130027f0
commit 8436bc4305
12 changed files with 170 additions and 111 deletions

View File

@ -2,6 +2,7 @@
- feat: sentry integration enabled with glitchtip - feat: sentry integration enabled with glitchtip
- fix: reindex on update shout - fix: reindex on update shout
- packages upgrade, isort - packages upgrade, isort
- separated stats queries for author and topic
[0.3.2] [0.3.2]
- redis cache for what author follows - redis cache for what author follows

View File

@ -1,24 +1,52 @@
from resolvers.author import (get_author, get_author_followers, from resolvers.author import (
get_author_follows, get_author_follows_authors, get_author,
get_author_follows_topics, get_author_id, get_author_followers,
get_authors_all, load_authors_by, search_authors, get_author_follows,
update_author) get_author_follows_authors,
get_author_follows_topics,
get_author_id,
get_authors_all,
load_authors_by,
search_authors,
update_author,
)
from resolvers.community import get_communities_all, get_community from resolvers.community import get_communities_all, get_community
from resolvers.editor import create_shout, delete_shout, update_shout from resolvers.editor import create_shout, delete_shout, update_shout
from resolvers.follower import (follow, get_shout_followers, from resolvers.follower import (
get_topic_followers, unfollow) follow,
from resolvers.notifier import (load_notifications, notification_mark_seen, get_shout_followers,
get_topic_followers,
unfollow,
)
from resolvers.notifier import (
load_notifications,
notification_mark_seen,
notifications_seen_after, notifications_seen_after,
notifications_seen_thread) notifications_seen_thread,
)
from resolvers.rating import rate_author from resolvers.rating import rate_author
from resolvers.reaction import (create_reaction, delete_reaction, from resolvers.reaction import (
load_reactions_by, load_shouts_followed, create_reaction,
update_reaction) delete_reaction,
from resolvers.reader import (get_shout, load_shouts_by, load_shouts_feed, load_reactions_by,
load_shouts_random_top, load_shouts_random_topic, load_shouts_followed,
load_shouts_search, load_shouts_unrated) update_reaction,
from resolvers.topic import (get_topic, get_topics_all, get_topics_by_author, )
get_topics_by_community) from resolvers.reader import (
get_shout,
load_shouts_by,
load_shouts_feed,
load_shouts_random_top,
load_shouts_random_topic,
load_shouts_search,
load_shouts_unrated,
)
from resolvers.topic import (
get_topic,
get_topics_all,
get_topics_by_author,
get_topics_by_community,
)
from services.triggers import events_register from services.triggers import events_register
events_register() events_register()

View File

@ -8,8 +8,7 @@ from sqlalchemy_searchable import search
from orm.author import Author, AuthorFollower from orm.author import Author, AuthorFollower
from orm.shout import ShoutAuthor, ShoutTopic from orm.shout import ShoutAuthor, ShoutTopic
from orm.topic import Topic from orm.topic import Topic
from resolvers.stat import (author_follows_authors, author_follows_topics, from resolvers.stat import author_follows_authors, author_follows_topics, get_with_stat
get_with_stat)
from services.auth import login_required from services.auth import login_required
from services.cache import cache_author, cache_follower from services.cache import cache_author, cache_follower
from services.db import local_session from services.db import local_session

View File

@ -139,10 +139,9 @@ async def remove_invite(_, info, invite_id: int):
author_dict = info.context["author"] author_dict = info.context["author"]
author_id = author_dict.get("id") author_id = author_dict.get("id")
if author_id: if isinstance(author_id, int):
# Check if the user exists # Check if the user exists
with local_session() as session: with local_session() as session:
author_id == int(author_id)
# Check if the invite exists # Check if the invite exists
invite = session.query(Invite).filter(Invite.id == invite_id).first() invite = session.query(Invite).filter(Invite.id == invite_id).first()
if isinstance(invite, Invite): if isinstance(invite, Invite):

View File

@ -11,8 +11,7 @@ from orm.community import Community
from orm.reaction import Reaction from orm.reaction import Reaction
from orm.shout import Shout, ShoutReactionsFollower from orm.shout import Shout, ShoutReactionsFollower
from orm.topic import Topic, TopicFollower from orm.topic import Topic, TopicFollower
from resolvers.stat import (author_follows_authors, author_follows_topics, from resolvers.stat import author_follows_authors, author_follows_topics, get_with_stat
get_with_stat)
from services.auth import login_required from services.auth import login_required
from services.cache import DEFAULT_FOLLOWS, cache_follower from services.cache import DEFAULT_FOLLOWS, cache_follower
from services.db import local_session from services.db import local_session

View File

@ -8,8 +8,12 @@ from sqlalchemy.orm import aliased
from sqlalchemy.sql import not_ from sqlalchemy.sql import not_
from orm.author import Author from orm.author import Author
from orm.notification import (Notification, NotificationAction, from orm.notification import (
NotificationEntity, NotificationSeen) Notification,
NotificationAction,
NotificationEntity,
NotificationSeen,
)
from orm.shout import Shout from orm.shout import Shout
from services.auth import login_required from services.auth import login_required
from services.db import local_session from services.db import local_session
@ -142,10 +146,10 @@ def get_notifications_grouped(
elif str(notification.entity) == NotificationEntity.REACTION.value: elif str(notification.entity) == NotificationEntity.REACTION.value:
reaction = payload reaction = payload
if not isinstance(shout, dict): if not isinstance(reaction, dict):
raise ValueError("reaction data is not consistent") raise ValueError("reaction data is not consistent")
shout_id = shout.get("shout") shout_id = reaction.get("shout")
author_id = shout.get("created_by", 0) author_id = reaction.get("created_by", 0)
if shout_id and author_id: if shout_id and author_id:
with local_session() as session: with local_session() as session:
author = ( author = (

View File

@ -6,8 +6,7 @@ from sqlalchemy.orm import aliased, joinedload
from sqlalchemy.sql import union from sqlalchemy.sql import union
from orm.author import Author from orm.author import Author
from orm.rating import (PROPOSAL_REACTIONS, RATING_REACTIONS, is_negative, from orm.rating import PROPOSAL_REACTIONS, RATING_REACTIONS, is_negative, is_positive
is_positive)
from orm.reaction import Reaction, ReactionKind from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout from orm.shout import Shout
from resolvers.editor import handle_proposing from resolvers.editor import handle_proposing

View File

@ -1,7 +1,6 @@
from sqlalchemy import bindparam, distinct, or_, text from sqlalchemy import bindparam, distinct, or_, text
from sqlalchemy.orm import aliased, joinedload from sqlalchemy.orm import aliased, joinedload
from sqlalchemy.sql.expression import (and_, asc, case, desc, func, nulls_last, from sqlalchemy.sql.expression import and_, asc, case, desc, func, nulls_last, select
select)
from orm.author import Author, AuthorFollower from orm.author import Author, AuthorFollower
from orm.reaction import Reaction, ReactionKind from orm.reaction import Reaction, ReactionKind

View File

@ -10,39 +10,51 @@ from services.db import local_session
from services.logger import root_logger as logger from services.logger import root_logger as logger
def add_topic_stat_columns(q): def get_topic_shouts_stat(topic_id: int):
aliased_shout_topic = aliased(ShoutTopic) q = (
aliased_authors = aliased(ShoutAuthor) select(func.count(distinct(ShoutTopic.shout)))
aliased_followers = aliased(TopicFollower) .select_from(join(ShoutTopic, Shout, ShoutTopic.shout == Shout.id))
aliased_shout = aliased(Shout) .filter(
# shouts
q = q.outerjoin(aliased_shout_topic, aliased_shout_topic.topic == Topic.id)
q = q.add_columns(
func.count(distinct(aliased_shout_topic.shout)).label("shouts_stat")
)
# authors
q = q.outerjoin(
aliased_shout,
and_( and_(
aliased_shout.id == aliased_shout_topic.shout, ShoutTopic.topic == topic_id,
aliased_shout.published_at.is_not(None), Shout.published_at.is_not(None),
aliased_shout.deleted_at.is_(None), Shout.deleted_at.is_(None),
),
) )
q = q.outerjoin(aliased_authors, aliased_shout.authors.any(id=aliased_authors.id))
q = q.add_columns(
func.count(distinct(aliased_authors.author)).label("authors_stat")
) )
)
[shouts_stat] = local_session().execute(q)
return shouts_stat or 0
# followers
q = q.outerjoin(aliased_followers, aliased_followers.topic == Topic.id)
q = q.add_columns(
func.count(distinct(aliased_followers.follower)).label("followers_stat")
)
# comments def get_topic_authors_stat(topic_id: int):
# authors
q = (
select(func.count(distinct(ShoutAuthor.author)))
.select_from(join(ShoutTopic, Shout, ShoutTopic.shout == Shout.id))
.join(ShoutAuthor, ShoutAuthor.shout == Shout.id)
.filter(
and_(
ShoutTopic.topic == topic_id,
Shout.published_at.is_not(None),
Shout.deleted_at.is_(None),
)
)
)
[authors_stat] = local_session().execute(q)
return authors_stat or 0
def get_topic_followers_stat(topic_id: int):
aliased_followers = aliased(TopicFollower)
q = select(func.count(distinct(aliased_followers.follower))).filter(
aliased_followers.topic == topic_id
)
with local_session() as session:
[followers_stat] = session.execute(q)
return followers_stat or 0
def get_topic_comments_stat(topic_id: int):
sub_comments = ( sub_comments = (
select( select(
Shout.id.label("shout_id"), Shout.id.label("shout_id"),
@ -61,39 +73,48 @@ def add_topic_stat_columns(q):
.group_by(Shout.id) .group_by(Shout.id)
.subquery() .subquery()
) )
q = q.outerjoin(sub_comments, aliased_shout_topic.shout == sub_comments.c.shout_id) q = select(func.coalesce(func.sum(sub_comments.c.comments_count), 0)).filter(
q = q.add_columns( ShoutTopic.topic == topic_id
func.coalesce(sub_comments.c.comments_count, 0).label("comments_stat")
) )
q = q.outerjoin(sub_comments, ShoutTopic.shout == sub_comments.c.shout_id)
group_list = [Topic.id, sub_comments.c.comments_count] [comments_stat] = local_session().execute(q)
return comments_stat or 0
q = q.group_by(*group_list)
logger.debug(q)
return q
def add_author_stat_columns(q): def get_author_shouts_stat(author_id: int):
aliased_shout_author = aliased(ShoutAuthor) aliased_shout_author = aliased(ShoutAuthor)
q = select(func.count(distinct(aliased_shout_author.shout))).filter(
aliased_shout_author.author == author_id
)
with local_session() as session:
[shouts_stat] = session.execute(q)
return shouts_stat or 0
def get_author_authors_stat(author_id: int):
aliased_authors = aliased(AuthorFollower) aliased_authors = aliased(AuthorFollower)
q = select(func.count(distinct(aliased_authors.author))).filter(
and_(
aliased_authors.follower == author_id,
aliased_authors.author != author_id,
)
)
with local_session() as session:
[authors_stat] = session.execute(q)
return authors_stat or 0
def get_author_followers_stat(author_id: int):
aliased_followers = aliased(AuthorFollower) aliased_followers = aliased(AuthorFollower)
q = select(func.count(distinct(aliased_followers.follower))).filter(
q = q.outerjoin(aliased_shout_author, aliased_shout_author.author == Author.id) aliased_followers.author == author_id
q = q.add_columns(
func.count(distinct(aliased_shout_author.shout)).label("shouts_stat")
) )
with local_session() as session:
[followers_stat] = session.execute(q)
return followers_stat or 0
q = q.outerjoin(aliased_authors, aliased_authors.follower == Author.id)
q = q.add_columns(
func.count(distinct(aliased_authors.author)).label("authors_stat")
)
q = q.outerjoin(aliased_followers, aliased_followers.author == Author.id) def get_author_comments_stat(author_id: int):
q = q.add_columns(
func.count(distinct(aliased_followers.follower)).label("followers_stat")
)
# Create a subquery for comments count
sub_comments = ( sub_comments = (
select( select(
Author.id, func.coalesce(func.count(Reaction.id)).label("comments_count") Author.id, func.coalesce(func.count(Reaction.id)).label("comments_count")
@ -109,35 +130,41 @@ def add_author_stat_columns(q):
.group_by(Author.id) .group_by(Author.id)
.subquery() .subquery()
) )
q = select(sub_comments.c.comments_count).filter(sub_comments.c.id == author_id)
q = q.outerjoin(sub_comments, Author.id == sub_comments.c.id) with local_session() as session:
q = q.add_columns(sub_comments.c.comments_count) [comments_stat] = session.execute(q)
group_list = [Author.id, sub_comments.c.comments_count] return comments_stat or 0
q = q.group_by(*group_list)
return q
def get_with_stat(q): def get_with_stat(q):
records = [] records = []
try: try:
is_author = f"{q}".lower().startswith("select author") is_author = f"{q}".lower().startswith("select author")
is_topic = f"{q}".lower().startswith("select topic") f"{q}".lower().startswith("select topic")
if is_author: result = []
q = add_author_stat_columns(q)
elif is_topic:
q = add_topic_stat_columns(q)
with local_session() as session: with local_session() as session:
result = session.execute(q) result = session.execute(q)
for cols in result: for cols in result:
entity = cols[0] entity = cols[0]
stat = dict() stat = dict()
stat["shouts"] = cols[1] stat["shouts"] = (
stat["authors"] = cols[2] get_author_shouts_stat(entity.id)
stat["followers"] = cols[3] if is_author
else get_topic_shouts_stat(entity.id)
)
stat["authors"] = (
get_author_authors_stat(entity.id)
if is_author
else get_topic_authors_stat(entity.id)
)
stat["followers"] = (
get_author_followers_stat(entity.id)
if is_author
else get_topic_followers_stat(entity.id)
)
if is_author: if is_author:
stat["comments"] = cols[4] stat["comments"] = get_author_comments_stat(entity.id)
entity.stat = stat entity.stat = stat
records.append(entity) records.append(entity)
except Exception as exc: except Exception as exc:

View File

@ -35,6 +35,7 @@ async def request_data(gql, headers=None):
except Exception as e: except Exception as e:
# Handling and logging exceptions during authentication check # Handling and logging exceptions during authentication check
import traceback import traceback
logger.error(f"request_data error: {e}") logger.error(f"request_data error: {e}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return None return None

View File

@ -5,8 +5,7 @@ import traceback
import warnings import warnings
from typing import Any, Callable, Dict, TypeVar from typing import Any, Callable, Dict, TypeVar
from sqlalchemy import (JSON, Column, Engine, Integer, create_engine, event, from sqlalchemy import JSON, Column, Engine, Integer, create_engine, event, exc, inspect
exc, inspect)
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, configure_mappers from sqlalchemy.orm import Session, configure_mappers
from sqlalchemy.sql.schema import Table from sqlalchemy.sql.schema import Table

View File

@ -7,8 +7,12 @@ from typing import Dict
# ga # ga
from google.analytics.data_v1beta import BetaAnalyticsDataClient from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import (DateRange, Dimension, Metric, from google.analytics.data_v1beta.types import (
RunReportRequest) DateRange,
Dimension,
Metric,
RunReportRequest,
)
from orm.author import Author from orm.author import Author
from orm.shout import Shout, ShoutAuthor, ShoutTopic from orm.shout import Shout, ShoutAuthor, ShoutTopic