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

Added Analytics App in backend #339

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
14 changes: 14 additions & 0 deletions backend/apps/analytics/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Analytics app admin."""

from django.contrib import admin

from apps.analytics.models.usersearchquery import UserSearchQuery


@admin.register(UserSearchQuery)
class AnalyticsAdmin(admin.ModelAdmin):
list_display = ("query", "source", "category", "timestamp")
list_filter = ("source", "category", "timestamp")
search_fields = ("query", "user_id", "session_id")
ordering = ("-timestamp",)
readonly_fields = ("timestamp",)
Empty file.
9 changes: 9 additions & 0 deletions backend/apps/analytics/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Analytics API URLs."""

from rest_framework import routers

from apps.analytics.api.usersearchquery import UserSearchQueryViewSet

router = routers.SimpleRouter()

router.register(r"analytics/search", UserSearchQueryViewSet)
24 changes: 24 additions & 0 deletions backend/apps/analytics/api/usersearchquery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""UserSearcQuery API."""

from rest_framework import serializers, viewsets
from rest_framework.permissions import AllowAny

from apps.analytics.models.usersearchquery import UserSearchQuery


# Serializers define the API representation.
class UserSearchQuerySerializer(serializers.HyperlinkedModelSerializer):
"""UserSearchQuery serializer."""

class Meta:
model = UserSearchQuery
fields = "__all__"


# ViewSets define the view behavior.
class UserSearchQueryViewSet(viewsets.ModelViewSet):
"""UserSearchQuery view set."""

queryset = UserSearchQuery.objects.all()
serializer_class = UserSearchQuerySerializer
permission_classes = [AllowAny]
5 changes: 5 additions & 0 deletions backend/apps/analytics/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class AnalyticsConfig(AppConfig):
name = "apps.analytics"
48 changes: 48 additions & 0 deletions backend/apps/analytics/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Generated by Django 5.1.4 on 2024-12-31 14:46

from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="UserSearchQuery",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("query", models.TextField()),
(
"source",
models.CharField(
choices=[("slackbot", "Slackbot"), ("frontend", "Frontend")],
default="frontend",
max_length=50,
),
),
("user_id", models.CharField(blank=True, max_length=255, null=True)),
("session_id", models.CharField(blank=True, max_length=255, null=True)),
("location", models.CharField(blank=True, max_length=255, null=True)),
("device_info", models.CharField(blank=True, max_length=255, null=True)),
("timestamp", models.DateTimeField(auto_now_add=True)),
],
options={
"verbose_name": "User Search Query",
"verbose_name_plural": "User Search Queries",
"db_table": "analytics_usersearchquery",
"indexes": [
models.Index(fields=["query"], name="analytics_u_query_dfad69_idx"),
models.Index(fields=["source"], name="analytics_u_source_b44b2c_idx"),
models.Index(fields=["timestamp"], name="analytics_u_timesta_b42e3f_idx"),
models.Index(fields=["user_id"], name="analytics_u_user_id_906cb6_idx"),
],
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1.4 on 2024-12-31 19:03

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("analytics", "0001_initial"),
]

operations = [
migrations.AlterField(
model_name="usersearchquery",
name="source",
field=models.CharField(
choices=[("nextbot", "NestBot"), ("frontend", "Frontend")],
default="frontend",
max_length=50,
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 5.1.4 on 2025-01-02 08:41

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("analytics", "0002_alter_usersearchquery_source"),
]

operations = [
migrations.AddField(
model_name="usersearchquery",
name="category",
field=models.CharField(
choices=[
("project", "Project"),
("chapter", "Chapter"),
("committee", "Committee"),
("others", "Others"),
],
default="others",
max_length=20,
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 5.1.4 on 2025-01-02 13:56

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("analytics", "0003_usersearchquery_category"),
]

operations = [
migrations.RemoveIndex(
model_name="usersearchquery",
name="analytics_u_user_id_906cb6_idx",
),
migrations.RemoveField(
model_name="usersearchquery",
name="device_info",
),
migrations.RemoveField(
model_name="usersearchquery",
name="location",
),
migrations.RemoveField(
model_name="usersearchquery",
name="session_id",
),
migrations.RemoveField(
model_name="usersearchquery",
name="user_id",
),
migrations.AddIndex(
model_name="usersearchquery",
index=models.Index(fields=["category"], name="analytics_u_categor_2a3ae6_idx"),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 5.1.4 on 2025-01-02 14:34

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("analytics", "0004_remove_usersearchquery_analytics_u_user_id_906cb6_idx_and_more"),
]

operations = [
migrations.AlterField(
model_name="usersearchquery",
name="category",
field=models.CharField(
choices=[
("projects", "Projects"),
("chapters", "Chapters"),
("committees", "Committees"),
("others", "Others"),
],
default="others",
max_length=20,
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1.4 on 2025-01-03 19:31

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("analytics", "0005_alter_usersearchquery_category"),
]

operations = [
migrations.AlterField(
model_name="usersearchquery",
name="source",
field=models.CharField(
choices=[("nestbot", "NestBot"), ("frontend", "Frontend")],
default="frontend",
max_length=50,
),
),
]
Empty file.
Empty file.
49 changes: 49 additions & 0 deletions backend/apps/analytics/models/usersearchquery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Analytics app UserSearchQuery model."""

from django.db import models


class UserSearchQuery(models.Model):
"""UserSearchQuery model."""

class Meta:
db_table = "analytics_usersearchquery"
verbose_name = "User Search Query"
verbose_name_plural = "User Search Queries"
indexes = [
models.Index(fields=["query"]),
models.Index(fields=["category"]),
models.Index(fields=["source"]),
models.Index(fields=["timestamp"]),
]

query = models.TextField()
source = models.CharField(
max_length=50,
choices=[
("nestbot", "NestBot"),
("frontend", "Frontend"),
],
default="frontend",
)
category = models.CharField(
max_length=20,
choices=[
("projects", "Projects"),
("chapters", "Chapters"),
("committees", "Committees"),
("others", "Others"),
],
default="others",
)
timestamp = models.DateTimeField(auto_now_add=True)

def save(self, *args, **kwargs):
"""Save user search queries."""
if self.query:
self.query_length = len(self.query)
super(__class__, self).save(*args, **kwargs)

def __str__(self):
"""UserSearchQuery human readable representation."""
return f"Query: {self.query} | Source: {self.source} | Time: {self.timestamp}"
Empty file.
3 changes: 2 additions & 1 deletion backend/apps/slack/commands/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from apps.slack.apps import SlackConfig
from apps.slack.blocks import markdown
from apps.slack.constants import FEEDBACK_CHANNEL_MESSAGE, NL
from apps.slack.utils import escape
from apps.slack.utils import escape, send_to_analytics

COMMAND = "/projects"
NAME_TRUNCATION_LIMIT = 80
Expand All @@ -25,6 +25,7 @@ def handler(ack, command, client):

search_query = command["text"]
search_query_escaped = escape(command["text"])
send_to_analytics(search_query, COMMAND)
blocks = [
markdown(f"*No results found for `{COMMAND} {search_query_escaped}`*{NL}"),
]
Expand Down
17 changes: 17 additions & 0 deletions backend/apps/slack/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
"""Slack app utils."""

import html
import logging

import requests
from django.conf import settings

logger = logging.getLogger(__name__)


def escape(content):
"""Escape HTML content."""
return html.escape(content, quote=False)


def send_to_analytics(query, command):
"""Send the search query to the analytics endpoint."""
analytics_url = f"{settings.SITE_URL}/api/v1/analytics/search/"
payload = {"query": query, "source": "nestbot", "category": command.strip("/")}
try:
response = requests.post(analytics_url, json=payload, timeout=10)
response.raise_for_status()
except requests.exceptions.RequestException:
logger.exception("Failed to send analytics data")
1 change: 1 addition & 0 deletions backend/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Base(Configuration):
"apps.github",
"apps.owasp",
"apps.slack",
"apps.analytics",
)

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
Expand Down
2 changes: 2 additions & 0 deletions backend/settings/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
from django.urls import include, path
from rest_framework import routers

from apps.analytics.api.urls import router as analytics_router
from apps.github.api.urls import router as github_router
from apps.owasp.api.urls import router as owasp_router
from apps.slack.apps import SlackConfig

router = routers.DefaultRouter()
router.registry.extend(github_router.registry)
router.registry.extend(owasp_router.registry)
router.registry.extend(analytics_router.registry)

urlpatterns = [
path("api/v1/", include(router.urls)),
Expand Down
Loading
Loading