functional notifications

This commit is contained in:
pablodanswer 2024-10-21 17:49:13 -07:00
parent 8f67f1715c
commit 8d66bdd061
17 changed files with 74 additions and 52 deletions

View File

@ -140,7 +140,7 @@ POSTGRES_PASSWORD = urllib.parse.quote_plus(
os.environ.get("POSTGRES_PASSWORD") or "password" os.environ.get("POSTGRES_PASSWORD") or "password"
) )
POSTGRES_HOST = os.environ.get("POSTGRES_HOST") or "localhost" POSTGRES_HOST = os.environ.get("POSTGRES_HOST") or "localhost"
POSTGRES_PORT = os.environ.get("POSTGRES_PORT") or "5432" POSTGRES_PORT = os.environ.get("POSTGRES_PORT") or "5433"
POSTGRES_DB = os.environ.get("POSTGRES_DB") or "postgres" POSTGRES_DB = os.environ.get("POSTGRES_DB") or "postgres"
POSTGRES_API_SERVER_POOL_SIZE = int( POSTGRES_API_SERVER_POOL_SIZE = int(

View File

@ -136,6 +136,7 @@ DocumentSourceRequiringTenantContext: list[DocumentSource] = [DocumentSource.FIL
class NotificationType(str, Enum): class NotificationType(str, Enum):
REINDEX = "reindex" REINDEX = "reindex"
PERSONA_SHARED = "persona_shared" PERSONA_SHARED = "persona_shared"
TRIAL_ENDS_TWO_DAYS = "two_day_trial_ending" # 2 days left in trial
class BlobType(str, Enum): class BlobType(str, Enum):

View File

@ -4,6 +4,7 @@ from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.sql import func from sqlalchemy.sql import func
from danswer.auth.schemas import UserRole
from danswer.configs.constants import NotificationType from danswer.configs.constants import NotificationType
from danswer.db.models import Notification from danswer.db.models import Notification
from danswer.db.models import User from danswer.db.models import User
@ -54,7 +55,9 @@ def get_notification_by_id(
notif = db_session.get(Notification, notification_id) notif = db_session.get(Notification, notification_id)
if not notif: if not notif:
raise ValueError(f"No notification found with id {notification_id}") raise ValueError(f"No notification found with id {notification_id}")
if notif.user_id != user_id: if notif.user_id != user_id and not (
notif.user_id is None and user.role == UserRole.ADMIN
):
raise PermissionError( raise PermissionError(
f"User {user_id} is not authorized to access notification {notification_id}" f"User {user_id} is not authorized to access notification {notification_id}"
) )

View File

@ -79,6 +79,8 @@ def _get_answer_stream_processor(
doc_id_to_rank_map: DocumentIdOrderMapping, doc_id_to_rank_map: DocumentIdOrderMapping,
answer_style_configs: AnswerStyleConfig, answer_style_configs: AnswerStyleConfig,
) -> StreamProcessor: ) -> StreamProcessor:
print("ANSWERR STYES")
print(answer_style_configs.__dict__)
if answer_style_configs.citation_config: if answer_style_configs.citation_config:
return build_citation_processor( return build_citation_processor(
context_docs=context_docs, doc_id_to_rank_map=doc_id_to_rank_map context_docs=context_docs, doc_id_to_rank_map=doc_id_to_rank_map

View File

@ -226,6 +226,7 @@ def process_model_tokens(
hold_quote = "" hold_quote = ""
for token in tokens: for token in tokens:
print(f"Token: {token}")
model_previous = model_output model_previous = model_output
model_output += token model_output += token

View File

@ -54,6 +54,7 @@ def fetch_settings(
Postgres calls""" Postgres calls"""
general_settings = load_settings() general_settings = load_settings()
user_notifications = get_reindex_notification(user, db_session) user_notifications = get_reindex_notification(user, db_session)
product_gating_notification = get_product_gating_notification(db_session)
try: try:
kv_store = get_kv_store() kv_store = get_kv_store()
@ -61,11 +62,27 @@ def fetch_settings(
except KvKeyNotFoundError: except KvKeyNotFoundError:
needs_reindexing = False needs_reindexing = False
return UserSettings( print("product_gating_notification", product_gating_notification)
# TODO: Clean up
print("response is ", [product_gating_notification])
response = UserSettings(
**general_settings.model_dump(), **general_settings.model_dump(),
notifications=user_notifications, notifications=[product_gating_notification]
if product_gating_notification
else user_notifications,
needs_reindexing=needs_reindexing, needs_reindexing=needs_reindexing,
) )
print("act is ", response)
return response
def get_product_gating_notification(db_session: Session) -> Notification | None:
notification = get_notifications(
user=None,
notif_type=NotificationType.TRIAL_ENDS_TWO_DAYS,
db_session=db_session,
)
return Notification.from_model(notification[0]) if notification else None
def get_reindex_notification( def get_reindex_notification(

View File

@ -8,6 +8,7 @@ from danswer.auth.users import User
from danswer.configs.app_configs import MULTI_TENANT from danswer.configs.app_configs import MULTI_TENANT
from danswer.configs.app_configs import WEB_DOMAIN from danswer.configs.app_configs import WEB_DOMAIN
from danswer.db.engine import get_session_with_tenant from danswer.db.engine import get_session_with_tenant
from danswer.db.notification import create_notification
from danswer.server.settings.store import load_settings from danswer.server.settings.store import load_settings
from danswer.server.settings.store import store_settings from danswer.server.settings.store import store_settings
from danswer.setup import setup_danswer from danswer.setup import setup_danswer
@ -87,12 +88,17 @@ def gate_product(
1) User has ended free trial without adding payment method 1) User has ended free trial without adding payment method
2) User's card has declined 2) User's card has declined
""" """
token = current_tenant_id.set(current_tenant_id.get()) tenant_id = product_gating_request.tenant_id
token = current_tenant_id.set(tenant_id)
settings = load_settings() settings = load_settings()
settings.product_gating = product_gating_request.product_gating settings.product_gating = product_gating_request.product_gating
store_settings(settings) store_settings(settings)
if product_gating_request.notification:
with get_session_with_tenant(tenant_id) as db_session:
create_notification(None, product_gating_request.notification, db_session)
if token is not None: if token is not None:
current_tenant_id.reset(token) current_tenant_id.reset(token)

View File

@ -1,5 +1,6 @@
from pydantic import BaseModel from pydantic import BaseModel
from danswer.configs.constants import NotificationType
from danswer.server.settings.models import GatingType from danswer.server.settings.models import GatingType
@ -15,6 +16,7 @@ class CreateTenantRequest(BaseModel):
class ProductGatingRequest(BaseModel): class ProductGatingRequest(BaseModel):
tenant_id: str tenant_id: str
product_gating: GatingType product_gating: GatingType
notification: NotificationType | None = None
class BillingInformation(BaseModel): class BillingInformation(BaseModel):

View File

@ -313,7 +313,7 @@ services:
- POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
ports: ports:
- "5432:5432" - "5433:5432"
volumes: volumes:
- db_volume:/var/lib/postgresql/data - db_volume:/var/lib/postgresql/data

View File

@ -313,7 +313,7 @@ services:
- POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
ports: ports:
- "5432:5432" - "5433:5432"
volumes: volumes:
- db_volume:/var/lib/postgresql/data - db_volume:/var/lib/postgresql/data

View File

@ -157,7 +157,7 @@ services:
- POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
ports: ports:
- "5432" - "5433"
volumes: volumes:
- db_volume:/var/lib/postgresql/data - db_volume:/var/lib/postgresql/data

View File

@ -18,6 +18,7 @@ export interface Settings {
export enum NotificationType { export enum NotificationType {
PERSONA_SHARED = "persona_shared", PERSONA_SHARED = "persona_shared",
REINDEX_NEEDED = "reindex_needed", REINDEX_NEEDED = "reindex_needed",
TRIAL_ENDS_TWO_DAYS = "two_day_trial_ending",
} }
export interface Notification { export interface Notification {

View File

@ -1,4 +1,4 @@
import { CLOUD_ENABLED } from "@/lib/constants"; import { NEXT_PUBLIC_CLOUD_ENABLED } from "@/lib/constants";
import { getAuthTypeMetadataSS, logoutSS } from "@/lib/userSS"; import { getAuthTypeMetadataSS, logoutSS } from "@/lib/userSS";
import { NextRequest } from "next/server"; import { NextRequest } from "next/server";
@ -13,7 +13,7 @@ export const POST = async (request: NextRequest) => {
} }
// Delete cookies only if cloud is enabled (jwt auth) // Delete cookies only if cloud is enabled (jwt auth)
if (CLOUD_ENABLED) { if (NEXT_PUBLIC_CLOUD_ENABLED) {
const cookiesToDelete = ["fastapiusersauth", "tenant_details"]; const cookiesToDelete = ["fastapiusersauth", "tenant_details"];
const cookieOptions = { const cookieOptions = {
path: "/", path: "/",

View File

@ -8,10 +8,8 @@ import {
} from "@/lib/userSS"; } from "@/lib/userSS";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { EmailPasswordForm } from "../login/EmailPasswordForm"; import { EmailPasswordForm } from "../login/EmailPasswordForm";
import { Card, Title, Text } from "@tremor/react"; import { Text } from "@tremor/react";
import Link from "next/link"; import Link from "next/link";
import { Logo } from "@/components/Logo";
import { CLOUD_ENABLED } from "@/lib/constants";
import { SignInButton } from "../login/SignInButton"; import { SignInButton } from "../login/SignInButton";
import AuthFlowContainer from "@/components/auth/AuthFlowContainer"; import AuthFlowContainer from "@/components/auth/AuthFlowContainer";

View File

@ -174,20 +174,6 @@ export default async function RootLayout({
process.env.THEME_IS_DARK?.toLowerCase() === "true" ? "dark" : "" process.env.THEME_IS_DARK?.toLowerCase() === "true" ? "dark" : ""
}`} }`}
> >
{productGating === GatingType.PARTIAL && (
<div className="fixed top-0 left-0 right-0 z-50 bg-warning-100 text-warning-900 p-2 text-center">
<p className="text-sm font-medium">
Your account is pending payment!{" "}
<a
href="/admin/cloud-settings"
className="font-bold underline hover:text-warning-700 transition-colors"
>
Update your billing information
</a>{" "}
or access will be suspended soon.
</p>
</div>
)}
<UserProvider> <UserProvider>
<ProviderContextProvider> <ProviderContextProvider>
<SettingsProvider settings={combinedSettings}> <SettingsProvider settings={combinedSettings}>

View File

@ -27,7 +27,6 @@ export async function Layout({ children }: { children: React.ReactNode }) {
const authTypeMetadata = results[0] as AuthTypeMetadata | null; const authTypeMetadata = results[0] as AuthTypeMetadata | null;
const user = results[1] as User | null; const user = results[1] as User | null;
console.log("authTypeMetadata", authTypeMetadata);
const authDisabled = authTypeMetadata?.authType === "disabled"; const authDisabled = authTypeMetadata?.authType === "disabled";
const requiresVerification = authTypeMetadata?.requiresVerification; const requiresVerification = authTypeMetadata?.requiresVerification;

View File

@ -15,6 +15,7 @@ export function AnnouncementBanner() {
settings?.settings.notifications || [] settings?.settings.notifications || []
); );
console.log("notifications", localNotifications);
useEffect(() => { useEffect(() => {
const filteredNotifications = ( const filteredNotifications = (
settings?.settings.notifications || [] settings?.settings.notifications || []
@ -32,7 +33,7 @@ export function AnnouncementBanner() {
const handleDismiss = async (notificationId: number) => { const handleDismiss = async (notificationId: number) => {
try { try {
const response = await fetch( const response = await fetch(
`/api/settings/notifications/${notificationId}/dismiss`, `/api/notifications/${notificationId}/dismiss`,
{ {
method: "POST", method: "POST",
} }
@ -61,12 +62,12 @@ export function AnnouncementBanner() {
{localNotifications {localNotifications
.filter((notification) => !notification.dismissed) .filter((notification) => !notification.dismissed)
.map((notification) => { .map((notification) => {
if (notification.notif_type == "reindex") {
return ( return (
<div <div
key={notification.id} key={notification.id}
className="absolute top-0 left-1/2 transform -translate-x-1/2 bg-blue-600 rounded-sm text-white px-4 pr-8 py-3 mx-auto" className="absolute top-0 left-1/2 transform -translate-x-1/2 bg-blue-600 rounded-sm text-white px-4 pr-8 py-3 mx-auto"
> >
{notification.notif_type == "reindex" ? (
<p className="text-center"> <p className="text-center">
Your index is out of date - we strongly recommend updating Your index is out of date - we strongly recommend updating
your search settings.{" "} your search settings.{" "}
@ -77,24 +78,29 @@ export function AnnouncementBanner() {
Update here Update here
</Link> </Link>
</p> </p>
) : notification.notif_type == "two_day_trial_ending" ? (
<p className="text-center">
Your trial is ending soon - submit your billing information to
continue using Danswer.{" "}
<Link
href="/admin/cloud-settings"
className="ml-2 underline cursor-pointer"
>
Update here
</Link>
</p>
) : null}
<button <button
onClick={() => handleDismiss(notification.id)} onClick={() => handleDismiss(notification.id)}
className="absolute top-0 right-0 mt-2 mr-2" className="absolute top-0 right-0 mt-2 mr-2"
aria-label="Dismiss" aria-label="Dismiss"
> >
<CustomTooltip <CustomTooltip showTick citation delay={100} content="Dismiss">
showTick
citation
delay={100}
content="Dismiss"
>
<XIcon className="h-5 w-5" /> <XIcon className="h-5 w-5" />
</CustomTooltip> </CustomTooltip>
</button> </button>
</div> </div>
); );
}
return null;
})} })}
</> </>
); );