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

FEAT: Unified Notifications #290

Open
wants to merge 66 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
0d7f44a
added logs component to layout
jagadeeswaran-zipstack Apr 25, 2024
9b94cc8
sonar fix
jagadeeswaran-zipstack Apr 25, 2024
4e36241
added border and fixed layout
jagadeeswaran-zipstack Apr 28, 2024
ef0d76a
Merge branch 'main' into feat/unified-notifications
jagadeeswaran-zipstack Apr 28, 2024
8d01c7a
added close and close all button
jagadeeswaran-zipstack Apr 30, 2024
a8e7e05
Merge branch 'main' into feat/unified-notifications
tahierhussain May 1, 2024
b92adf0
Merge branch 'main' of github.com:Zipstack/unstract into feat/unified…
tahierhussain May 9, 2024
a80b716
Backend related changes for unified notifications
tahierhussain May 13, 2024
2bde1d4
Changes in pubsub helper
tahierhussain May 13, 2024
bef8109
UI related changes in unified notifications
tahierhussain May 13, 2024
dce29af
Merge branch 'main' of github.com:Zipstack/unstract into feat/unified…
tahierhussain May 13, 2024
512222d
Minor fixes
tahierhussain May 13, 2024
c285e41
Merge branch 'main' into feat/unified-notifications
tahierhussain May 13, 2024
753b4df
Merge branch 'main' into feat/unified-notifications
tahierhussain May 14, 2024
8a9f8a4
Display log messages from the API response
tahierhussain May 21, 2024
7065e28
Merge branch 'feat/unified-notifications' of github.com:Zipstack/unst…
tahierhussain May 21, 2024
76d5ade
Merge branch 'main' into feat/unified-notifications
jagadeeswaran-zipstack May 29, 2024
0526d35
store notifications to redis logs
jagadeeswaran-zipstack May 29, 2024
4e3d00f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 29, 2024
4dd9b40
Merge branch 'main' into feat/unified-notifications
tahierhussain May 31, 2024
b30cc6d
updated sample.env
jagadeeswaran-zipstack May 31, 2024
71c4de7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 31, 2024
f4ae7fc
Merge branch 'main' of github.com:Zipstack/unstract into feat/unified…
tahierhussain Jun 3, 2024
0d91070
Removed exceptions as it is handled by default
tahierhussain Jun 3, 2024
b091bd7
changed to django redis
jagadeeswaran-zipstack Jun 3, 2024
c6e9f5d
Merge branch 'main' into feat/unified-notifications
jagadeeswaran-zipstack Jun 3, 2024
23318c2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
b3eaf66
build fix
jagadeeswaran-zipstack Jun 3, 2024
6806d51
remove logs on logout
jagadeeswaran-zipstack Jun 5, 2024
6b1949f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 5, 2024
c25f2ea
pre-commit fix
jagadeeswaran-zipstack Jun 5, 2024
6e188ef
Merge branch 'feat/unified-notifications' of github.com:Zipstack/unst…
jagadeeswaran-zipstack Jun 5, 2024
1e02027
Merge branch 'main' into feat/unified-notifications
tahierhussain Jun 6, 2024
91af677
reused CacheService
jagadeeswaran-zipstack Jun 10, 2024
b616fa0
Merge branch 'feat/unified-notifications' of github.com:Zipstack/unst…
jagadeeswaran-zipstack Jun 10, 2024
fb74901
Merge branch 'main' into feat/unified-notifications
jagadeeswaran-zipstack Jun 10, 2024
c451c51
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 10, 2024
879863f
Merge branch 'feat/unified-notifications' of github.com:Zipstack/unst…
jagadeeswaran-zipstack Jun 10, 2024
c643b02
merge fix
jagadeeswaran-zipstack Jun 10, 2024
68516da
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 10, 2024
b8f2a33
fixed return type
jagadeeswaran-zipstack Jun 10, 2024
4054631
Merge branch 'feat/unified-notifications' of github.com:Zipstack/unst…
jagadeeswaran-zipstack Jun 10, 2024
7e9511f
code cleanup
jagadeeswaran-zipstack Jun 20, 2024
c0fcfde
code cleanup
jagadeeswaran-zipstack Jun 20, 2024
2619260
encapsulated redis key creation
jagadeeswaran-zipstack Jun 24, 2024
08b9a9a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 24, 2024
e9badc3
code optimization
jagadeeswaran-zipstack Jun 24, 2024
5f43ca2
Merge branch 'feat/unified-notifications' of github.com:Zipstack/unst…
jagadeeswaran-zipstack Jun 24, 2024
abc8f71
Merge branch 'main' of github.com:Zipstack/unstract into feat/unified…
jagadeeswaran-zipstack Jul 15, 2024
30c7203
Merge branch 'main' of github.com:Zipstack/unstract into feat/unified…
jagadeeswaran-zipstack Jul 15, 2024
e467d8b
Merge branch 'main' of github.com:Zipstack/unstract into feat/unified…
jagadeeswaran-zipstack Jul 19, 2024
e898b18
added logs to redis
jagadeeswaran-zipstack Jul 29, 2024
cbe6116
Merge branch 'main' of github.com:Zipstack/unstract into feat/unified…
tahierhussain Jul 30, 2024
45a9bb1
UI/UX improvements in unified notifications
tahierhussain Jul 30, 2024
8ee5c59
Handled default duration of popup message
tahierhussain Jul 30, 2024
2de8a68
sonar issue fix
jagadeeswaran-zipstack Jul 30, 2024
fc42be6
Merge branch 'main' into feat/unified-notifications
tahierhussain Jul 31, 2024
a1be308
settings changes
jagadeeswaran-zipstack Jul 31, 2024
cfd496b
Merge branch 'feat/unified-notifications' of github.com:Zipstack/unst…
jagadeeswaran-zipstack Jul 31, 2024
808c31e
code refactor
jagadeeswaran-zipstack Jul 31, 2024
fa633cf
moved inline style to class
jagadeeswaran-zipstack Jul 31, 2024
5a2c170
Merge branch 'main' into feat/unified-notifications
tahierhussain Jul 31, 2024
0a0f8f2
Merge branch 'main' into feat/unified-notifications
Deepak-Kesavan Jul 31, 2024
b18261c
moved from django-redis to redis
jagadeeswaran-zipstack Jul 31, 2024
69e11df
Merge branch 'feat/unified-notifications' of github.com:Zipstack/unst…
jagadeeswaran-zipstack Jul 31, 2024
66ca748
Merge branch 'main' into feat/unified-notifications
jagadeeswaran-zipstack Aug 16, 2024
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
3 changes: 3 additions & 0 deletions backend/account/authentication_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from django.middleware import csrf
from django.shortcuts import redirect
from django_tenants.utils import tenant_context
from logs_helper.log_service import LogService
from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response
Expand Down Expand Up @@ -268,6 +269,8 @@ def make_user_organization_display_name(self, user_name: str) -> str:
return self.auth_service.make_user_organization_display_name(user_name)

def user_logout(self, request: Request) -> Response:
session_id: str = UserSessionUtils.get_session_id(request=request)
LogService.remove_logs_on_logout(session_id=session_id)
response = self.auth_service.user_logout(request=request)
organization_id = UserSessionUtils.get_organization_id(request)
user_id = UserSessionUtils.get_user_id(request)
Expand Down
3 changes: 3 additions & 0 deletions backend/backend/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ def get_required_setting(
get_required_setting("LOG_HISTORY_CONSUMER_INTERVAL", "60")
)
LOGS_BATCH_LIMIT = int(get_required_setting("LOGS_BATCH_LIMIT", "30"))
LOGS_EXPIRATION_TIME_IN_SECOND = int(
get_required_setting("LOGS_EXPIRATION_TIME_IN_SECOND")
)
CELERY_BROKER_URL = get_required_setting(
"CELERY_BROKER_URL", f"redis://{REDIS_HOST}:{REDIS_PORT}"
)
Expand Down
1 change: 1 addition & 0 deletions backend/backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
path("", include("connector_processor.urls")),
path("", include("adapter_processor.urls")),
path("", include("file_management.urls")),
path("", include("logs_helper.urls")),
path("", include("tool_instance.urls")),
path("", include("pipeline.urls")),
path("", include("feature_flag.urls")),
Expand Down
Empty file added backend/logs_helper/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions backend/logs_helper/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class LogsHelperConfig(AppConfig):
name = "logs_helper"
3 changes: 3 additions & 0 deletions backend/logs_helper/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class LogsHelperKeys:
LOG = "LOG"
LOG_EVENTS_ID = "log_events_id"
24 changes: 24 additions & 0 deletions backend/logs_helper/log_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from utils.cache_service import CacheService


class LogService:
@staticmethod
def remove_logs_on_logout(session_id: str) -> None:

if session_id:
key_pattern = f"{LogService.generate_redis_key(session_id=session_id)}*"

# Delete keys matching the pattern
CacheService.clear_cache(key_pattern=key_pattern)

@staticmethod
def generate_redis_key(session_id):
"""Generate a Redis key for logs based on the provided session_id.

Parameters:
session_id (str): The session identifier to include in the Redis key.

Returns:
str: The constructed Redis key.
"""
return f"logs:{session_id}"
5 changes: 5 additions & 0 deletions backend/logs_helper/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from rest_framework import serializers


class StoreLogMessagesSerializer(serializers.Serializer):
log = serializers.CharField()
16 changes: 16 additions & 0 deletions backend/logs_helper/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns

from .views import LogsHelperViewSet

logs_helper = LogsHelperViewSet.as_view({"get": "get_logs", "post": "store_log"})

urlpatterns = format_suffix_patterns(
[
path(
"logs/",
logs_helper,
name="logs-helper",
),
]
)
66 changes: 66 additions & 0 deletions backend/logs_helper/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import json
import logging
from datetime import datetime, timezone

from django.conf import settings
from django.http import HttpRequest
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from utils.cache_service import CacheService
from utils.user_session import UserSessionUtils

from .log_service import LogService
from .serializers import StoreLogMessagesSerializer

logger = logging.getLogger(__name__)


class LogsHelperViewSet(viewsets.ModelViewSet):
"""Viewset to handle all Tool Studio prompt related API logics."""

@action(detail=False, methods=["get"])
def get_logs(self, request: HttpRequest) -> Response:
# Extract the session ID
session_id: str = UserSessionUtils.get_session_id(request=request)

# Construct the Redis key pattern to match keys
# associated with the session ID
redis_key = LogService.generate_redis_key(session_id=session_id)

# Retrieve keys matching the pattern
keys = CacheService.get_all_keys(f"{redis_key}*")

# Retrieve values corresponding to the keys and sort them by timestamp
logs = []
jagadeeswaran-zipstack marked this conversation as resolved.
Show resolved Hide resolved
for key in keys:
log_data = CacheService.get_key(key)
logs.append(log_data)

# Sort logs based on timestamp
sorted_logs = sorted(logs, key=lambda x: x["timestamp"])

return Response({"data": sorted_logs}, status=status.HTTP_200_OK)
jagadeeswaran-zipstack marked this conversation as resolved.
Show resolved Hide resolved

@action(detail=False, methods=["post"])
def store_log(self, request: HttpRequest) -> Response:
jagadeeswaran-zipstack marked this conversation as resolved.
Show resolved Hide resolved
"""Store log message in Redis."""
# Extract the session ID
logs_expiry = settings.LOGS_EXPIRATION_TIME_IN_SECOND
session_id: str = UserSessionUtils.get_session_id(request=request)

serializer = StoreLogMessagesSerializer(data=request.data)
serializer.is_valid(raise_exception=True)

# Extract the log message from the validated data
log: str = serializer.validated_data.get("log")
log_data = json.loads(log)
timestamp = datetime.now(timezone.utc).timestamp()

redis_key = (
f"{LogService.generate_redis_key(session_id=session_id)}:{timestamp}"
)

CacheService.set_key(redis_key, log_data, logs_expiry)

return Response({"message": "Successfully stored the message in redis"})
3 changes: 2 additions & 1 deletion backend/sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,9 @@ ENABLE_LOG_HISTORY=True
LOG_HISTORY_CONSUMER_INTERVAL=30
# Maximum number of logs to insert in a single batch.
LOGS_BATCH_LIMIT=30
# Logs Expiry
LOGS_EXPIRATION_TIME_IN_SECOND=3600
jagadeeswaran-zipstack marked this conversation as resolved.
Show resolved Hide resolved

# Celery Configuration
CELERY_BROKER_URL = "redis://unstract-redis:6379"

# Indexing flag to prevent re-index
Expand Down
6 changes: 6 additions & 0 deletions backend/utils/cache_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def set_key(
expire,
)

@staticmethod
def get_all_keys(key_pattern: str) -> Any:
keys = redis_cache.keys(key_pattern)
# Ensure all keys are strings
return [key.decode("utf-8") if isinstance(key, bytes) else key for key in keys]

jagadeeswaran-zipstack marked this conversation as resolved.
Show resolved Hide resolved
@staticmethod
def clear_cache(key_pattern: str) -> Any:
"""Delete keys in bulk based on the key pattern."""
Expand Down
4 changes: 4 additions & 0 deletions backend/utils/user_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def set_organization_id(request: HttpRequest, organization_id: str) -> None:
def get_user_id(request: HttpRequest) -> Optional[str]:
return request.session.get("user_id")

@staticmethod
def get_session_id(request: HttpRequest) -> Optional[str]:
return request.session.session_key

@staticmethod
def set_organization_member_role(
request: HttpRequest, member: OrganizationMember
Expand Down
9 changes: 3 additions & 6 deletions frontend/src/components/agency/agency/Agency.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
height: 100%;
display: flex;
flex-direction: column;
padding-bottom: 10px;
}

.agency-sider-layout {
Expand Down Expand Up @@ -40,20 +41,16 @@
padding: 12px;
}

.agency-ide-logs {
height: 10vh;
}

.agency-ide-collapse-panel {
background-color: var(--white);
border: none;
border-radius: 0px;
}

.agency-ide-log-modal .ant-modal-content{
.agency-ide-log-modal .ant-modal-content {
height: 80vh;
}

.agency-ide-log-modal .agency-ide-logs{
.agency-ide-log-modal .agency-ide-logs {
height: 70vh !important;
}
78 changes: 2 additions & 76 deletions frontend/src/components/agency/agency/Agency.jsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,32 @@
import { Button, Collapse, Layout, Modal } from "antd";
import {
FullscreenExitOutlined,
FullscreenOutlined,
LeftOutlined,
RightOutlined,
} from "@ant-design/icons";
import { Button, Layout } from "antd";
import { LeftOutlined, RightOutlined } from "@ant-design/icons";
import Sider from "antd/es/layout/Sider";
import { useEffect, useState } from "react";

import { IslandLayout } from "../../../layouts/island-layout/IslandLayout";
import { Actions } from "../actions/Actions";
import { WorkflowExecution } from "../workflow-execution/WorkflowExecution";
import "./Agency.css";
import { useSocketLogsStore } from "../../../store/socket-logs-store";
import { useSocketMessagesStore } from "../../../store/socket-messages-store";
import { useWorkflowStore } from "../../../store/workflow-store";
import { LogsLabel } from "../logs-label/LogsLabel";
import { SidePanel } from "../side-panel/SidePanel";
import { DisplayLogs } from "../display-logs/DisplayLogs";

function Agency() {
const [isCollapsed, setIsCollapsed] = useState(false);
const [activeKey, setActiveKey] = useState([]);
const [steps, setSteps] = useState([]);
const [inputMd, setInputMd] = useState("");
const [outputMd, setOutputMd] = useState("");
const [statusBarMsg, setStatusBarMsg] = useState("");
const [sourceMsg, setSourceMsg] = useState("");
const [destinationMsg, setDestinationMsg] = useState("");
const { message, setDefault } = useSocketMessagesStore();
const { emptyLogs } = useSocketLogsStore();
const workflowStore = useWorkflowStore();
const { details, loadingType } = workflowStore;
const prompt = details?.prompt_text;
const [activeToolId, setActiveToolId] = useState("");
const [prevLoadingType, setPrevLoadingType] = useState("");
const [isUpdateSteps, setIsUpdateSteps] = useState(false);
const [stepLoader, setStepLoader] = useState(false);
const [showLogsModal, setShowLogsModal] = useState(false);

const openLogsModal = () => {
setShowLogsModal(true);
};

const closeLogsModal = () => {
setShowLogsModal(false);
};

const genExtra = () => (
<FullscreenOutlined
onClick={(event) => {
// If you don't want click extra trigger collapse, you can prevent this:
openLogsModal();
event.stopPropagation();
}}
/>
);

const getItems = () => [
{
key: "1",
label: activeKey?.length > 0 ? <LogsLabel /> : "Logs",
children: (
<div className="agency-ide-logs">
<DisplayLogs />
</div>
),
extra: genExtra(),
},
];

const handleCollapse = (keys) => {
setActiveKey(keys);
};

useEffect(() => {
if (prevLoadingType !== "EXECUTE") {
Expand Down Expand Up @@ -102,7 +56,6 @@ function Agency() {
setOutputMd("");
setStatusBarMsg("");
setDefault();
emptyLogs();
setSourceMsg("");
setDestinationMsg("");
};
Expand All @@ -111,7 +64,6 @@ function Agency() {
// Clean up function to clear all the socket messages
return () => {
setDefault();
emptyLogs();
};
}, []);

Expand All @@ -138,7 +90,6 @@ function Agency() {
}

if (msgComp === "SOURCE" && state === "RUNNING") {
setActiveKey("");
setSourceMsg("");
setDestinationMsg("");
const newSteps = [...steps].map((step) => {
Expand Down Expand Up @@ -225,31 +176,6 @@ function Agency() {
stepLoader={stepLoader}
/>
</div>
<div className="agency-footer">
<Collapse
className="agency-ide-collapse-panel"
size="small"
activeKey={activeKey}
items={getItems()}
expandIconPosition="end"
onChange={handleCollapse}
bordered={false}
/>
<Modal
title="Logs"
open={showLogsModal}
onCancel={closeLogsModal}
className="agency-ide-log-modal"
footer={null}
width={1000}
closeIcon={<FullscreenExitOutlined />}
>
<LogsLabel />
<div className="agency-ide-logs">
<DisplayLogs />
</div>
</Modal>
</div>
</div>
);
}
Expand Down
20 changes: 15 additions & 5 deletions frontend/src/components/agency/display-logs/DisplayLogs.css
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
/* Styles for DisplayLogs */

.tool-logs {
height: 100%;
overflow-y: auto;
height: 100%;
overflow-y: auto;
}

.display-logs-col-first {
font-size: 12px;
font-size: 12px;
}

.display-logs-col {
font-size: 12px;
padding-left: 5px;
font-size: 12px;
padding-left: 5px;
}

.display-logs-container {
padding-right: 25px;
padding-top: 2px;
border-top: 1px rgba(0, 0, 0, 0.15) solid;
}

.display-logs-error-bg {
background-color: #fad4d4;
}
Loading
Loading