Anyone from your organisation can join this channel and view its message history.
-
If you make this channel private again, it willbe visible to anyone who has joined the channel up until that point.
+
If you make this channel private, it will be visible to anyone who has joined the channel up until that point.
}
- {channelData?.type === 'Public' &&
+ {newChannelType === 'Private' && Please understand that when you make {channelData?.channel_name} a private channel:
No changes will be made to the channel's history or members
All files shared in this channel will become private and will be accessible only to the channel members
}
+ {newChannelType === 'Open' &&
+ Please understand that when you make {channelData?.channel_name} a open channel:
+
+
Everyone from your organisation will become a channel member and will be able to view its message history.
+
If you later intend to make this private you will have to manually remove members that should not have access to this channel.
+
+ }
-
>
diff --git a/raven-app/src/components/feature/chat/ChatInput/FileInput/useFileUpload.ts b/raven-app/src/components/feature/chat/ChatInput/FileInput/useFileUpload.ts
index e12d91486..3c1e9199f 100644
--- a/raven-app/src/components/feature/chat/ChatInput/FileInput/useFileUpload.ts
+++ b/raven-app/src/components/feature/chat/ChatInput/FileInput/useFileUpload.ts
@@ -16,6 +16,10 @@ export default function useFileUpload(channelID: string, selectedMessage?: Messa
const [files, setFiles] = useState([])
+ const filesStateRef = useRef([])
+
+ filesStateRef.current = files
+
const [fileUploadProgress, setFileUploadProgress] = useState>({})
const addFile = (file: File) => {
@@ -38,7 +42,7 @@ export default function useFileUpload(channelID: string, selectedMessage?: Messa
}
const uploadFiles = async () => {
- const newFiles = [...files]
+ const newFiles = [...filesStateRef.current]
if (newFiles.length > 0) {
const promises = newFiles.map(async (f: CustomFile) => {
return file.uploadFile(f,
diff --git a/raven-app/src/components/feature/chat/ChatMessage/MessageActions/MessageActions.tsx b/raven-app/src/components/feature/chat/ChatMessage/MessageActions/MessageActions.tsx
index ede5eea68..4646495f9 100644
--- a/raven-app/src/components/feature/chat/ChatMessage/MessageActions/MessageActions.tsx
+++ b/raven-app/src/components/feature/chat/ChatMessage/MessageActions/MessageActions.tsx
@@ -7,6 +7,7 @@ import { HiReply } from 'react-icons/hi'
import { FrappeConfig, FrappeContext } from 'frappe-react-sdk'
import { useMessageCopy } from './useMessageCopy'
import { useToast } from '@/hooks/useToast'
+import { RetractVote } from './RetractVote'
export interface MessageContextMenuProps {
message?: Message | null,
@@ -25,6 +26,9 @@ export const MessageContextMenu = ({ message, onDelete, onEdit, onReply }: Messa
return (
{message ? <>
+
+ {message && message.message_type === 'Poll' && }
+
diff --git a/raven-app/src/components/feature/chat/ChatMessage/MessageActions/RetractVote.tsx b/raven-app/src/components/feature/chat/ChatMessage/MessageActions/RetractVote.tsx
new file mode 100644
index 000000000..b6f09629c
--- /dev/null
+++ b/raven-app/src/components/feature/chat/ChatMessage/MessageActions/RetractVote.tsx
@@ -0,0 +1,56 @@
+import { ContextMenu, Flex } from '@radix-ui/themes'
+import { useFrappeGetCall, useFrappePostCall } from 'frappe-react-sdk'
+import { TiArrowBackOutline } from 'react-icons/ti'
+import { Poll } from '../Renderers/PollMessage'
+import { Message } from '../../../../../../../types/Messaging/Message'
+import { toast } from '@/hooks/useToast'
+
+interface RetractVoteProps {
+ message: Message
+}
+
+export const RetractVote = ({ message }: RetractVoteProps) => {
+
+ // fetch poll data using message_id
+ const { data } = useFrappeGetCall<{ message: Poll }>('raven.api.raven_poll.get_poll', {
+ 'message_id': message?.name,
+ }, `poll_data_${message?.poll_id}`, {
+ revalidateOnFocus: false,
+ revalidateIfStale: false,
+ revalidateOnReconnect: false
+ })
+
+ const { call } = useFrappePostCall('raven.api.raven_poll.retract_vote')
+ const onRetractVote = () => {
+ return call({
+ poll_id: message?.poll_id,
+ }).then(() => {
+ toast({
+ title: 'Vote retracted',
+ variant: 'accent',
+ duration: 800,
+ })
+ }).catch(() => {
+ toast({
+ title: 'Could not retract vote',
+ variant: 'destructive',
+ duration: 800,
+ })
+ })
+ }
+
+ if (data && data.message?.current_user_votes.length > 0)
+ return (
+ <>
+
+
+
+ Retract vote
+
+
+
+ >
+ )
+
+ return null
+}
\ No newline at end of file
diff --git a/raven-app/src/components/feature/chat/ChatMessage/Renderers/PollMessage.tsx b/raven-app/src/components/feature/chat/ChatMessage/Renderers/PollMessage.tsx
index 638b83b6b..1da40427a 100644
--- a/raven-app/src/components/feature/chat/ChatMessage/Renderers/PollMessage.tsx
+++ b/raven-app/src/components/feature/chat/ChatMessage/Renderers/PollMessage.tsx
@@ -1,9 +1,9 @@
import { Box, Checkbox, Flex, Text, RadioGroup, Button, Badge } from "@radix-ui/themes"
import { BoxProps } from "@radix-ui/themes/dist/cjs/components/box"
-import { memo, useEffect, useMemo, useState } from "react"
+import { useEffect, useMemo, useState } from "react"
import { UserFields } from "../../../../../utils/users/UserListProvider"
import { PollMessage } from "../../../../../../../types/Messaging/Message"
-import { useFrappeDocumentEventListener, useFrappeGetCall, useFrappePostCall, useSWRConfig } from "frappe-react-sdk"
+import { useFrappeDocumentEventListener, useFrappeGetCall, useFrappePostCall } from "frappe-react-sdk"
import { RavenPoll } from "@/types/RavenMessaging/RavenPoll"
import { ErrorBanner } from "@/components/layout/AlertBanner"
import { RavenPollOption } from "@/types/RavenMessaging/RavenPollOption"
@@ -14,12 +14,12 @@ interface PollMessageBlockProps extends BoxProps {
user?: UserFields,
}
-interface Poll {
+export interface Poll {
'poll': RavenPoll,
'current_user_votes': { 'option': string }[]
}
-export const PollMessageBlock = memo(({ message, user, ...props }: PollMessageBlockProps) => {
+export const PollMessageBlock = ({ message, user, ...props }: PollMessageBlockProps) => {
// fetch poll data using message_id
const { data, error, mutate } = useFrappeGetCall<{ message: Poll }>('raven.api.raven_poll.get_poll', {
@@ -40,7 +40,7 @@ export const PollMessageBlock = memo(({ message, user, ...props }: PollMessageBl
{data && }
)
-})
+}
const PollMessageBox = ({ data, messageID }: { data: Poll, messageID: string }) => {
return (
@@ -132,7 +132,6 @@ const PollOption = ({ data, option }: { data: Poll, option: RavenPollOption }) =
const SingleChoicePoll = ({ data, messageID }: { data: Poll, messageID: string }) => {
- const { mutate } = useSWRConfig()
const { call } = useFrappePostCall('raven.api.raven_poll.add_vote')
const { toast } = useToast()
const onVoteSubmit = async (option: RavenPollOption) => {
@@ -140,7 +139,6 @@ const SingleChoicePoll = ({ data, messageID }: { data: Poll, messageID: string }
'message_id': messageID,
'option_id': option.name
}).then(() => {
- mutate(`poll_data_${data.poll.name}`)
toast({
title: "Your vote has been submitted!",
variant: 'success',
@@ -168,7 +166,6 @@ const SingleChoicePoll = ({ data, messageID }: { data: Poll, messageID: string }
const MultiChoicePoll = ({ data, messageID }: { data: Poll, messageID: string }) => {
const [selectedOptions, setSelectedOptions] = useState([])
- const { mutate } = useSWRConfig()
const { toast } = useToast()
const handleCheckboxChange = (name: string, value: boolean | string) => {
@@ -185,7 +182,6 @@ const MultiChoicePoll = ({ data, messageID }: { data: Poll, messageID: string })
'message_id': messageID,
'option_id': selectedOptions
}).then(() => {
- mutate(`poll_data_${data.poll.name}`)
toast({
title: "Your vote has been submitted!",
variant: 'success',
diff --git a/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/Italic.tsx b/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/Italic.tsx
deleted file mode 100644
index cf00cde08..000000000
--- a/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/Italic.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { mergeAttributes } from "@tiptap/react";
-import Italic from '@tiptap/extension-italic';
-export const CustomItalic = Italic.extend({
- renderHTML({ HTMLAttributes }) {
- return [
- "em",
- mergeAttributes(HTMLAttributes, {
- class: 'rt-Em'
- }), // mergeAttributes is a exported function from @tiptap/core
- 0,
- ];
- },
-})
\ No newline at end of file
diff --git a/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/TiptapRenderer.tsx b/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/TiptapRenderer.tsx
index 9ab0e1f2e..ded1b1498 100644
--- a/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/TiptapRenderer.tsx
+++ b/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/TiptapRenderer.tsx
@@ -2,7 +2,7 @@ import { EditorContent, EditorContext, useEditor } from '@tiptap/react'
import { TextMessage } from '../../../../../../../../types/Messaging/Message'
import { UserFields } from '@/utils/users/UserListProvider'
import { BoxProps } from '@radix-ui/themes/dist/cjs/components/box'
-import { Box, Text } from '@radix-ui/themes'
+import { Box } from '@radix-ui/themes'
import Highlight from '@tiptap/extension-highlight'
import StarterKit from '@tiptap/starter-kit'
import css from 'highlight.js/lib/languages/css'
@@ -17,10 +17,10 @@ import { CustomBlockquote } from './Blockquote'
import { CustomBold } from './Bold'
import { CustomUserMention } from './Mention'
import { CustomLink, LinkPreview } from './Link'
-import { CustomItalic } from './Italic'
import { CustomUnderline } from './Underline'
import { Image } from '@tiptap/extension-image'
import { clsx } from 'clsx'
+import Italic from '@tiptap/extension-italic';
const lowlight = createLowlight(common)
lowlight.register('html', html)
@@ -80,7 +80,7 @@ export const TiptapRenderer = ({ message, user, isScrolling = false, isTruncated
CustomBold,
CustomUserMention,
CustomLink,
- CustomItalic,
+ Italic,
Image.configure({
HTMLAttributes: {
class: 'w-full h-auto'
diff --git a/raven-app/src/pages/auth/ForgotPassword.tsx b/raven-app/src/pages/auth/ForgotPassword.tsx
new file mode 100644
index 000000000..e27a30b35
--- /dev/null
+++ b/raven-app/src/pages/auth/ForgotPassword.tsx
@@ -0,0 +1,107 @@
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { Link } from "react-router-dom";
+import { FrappeError, useFrappePostCall } from "frappe-react-sdk";
+import {
+ Box,
+ Button,
+ Flex,
+ TextField,
+ Link as LinkButton,
+} from "@radix-ui/themes";
+import { ErrorText, Label } from "@/components/common/Form";
+import { Loader } from "@/components/common/Loader";
+import { CalloutObject } from "@/components/common/Callouts/CustomCallout";
+import { ErrorCallout } from "@/components/common/Callouts/ErrorCallouts";
+import { SuccessCallout } from "@/components/common/Callouts/SuccessCallout";
+import { isEmailValid } from "@/utils/validations";
+import { ForgotPasswordInput } from "@/types/Auth/Login";
+import AuthContainer from "@/components/layout/AuthContainer";
+
+
+export const Component = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm();
+ const [callout, setCallout] = useState(null)
+
+ // POST Call to send reset password instructions on email
+ const { call, error } = useFrappePostCall('frappe.core.doctype.user.user.reset_password')
+
+ async function resetPassword(values: ForgotPasswordInput) {
+ return call({
+ user: values.user,
+ })
+ .then((res) => {
+ setCallout({
+ state: true,
+ message: "Password reset instructions have been sent to your email.",
+ });
+ }).catch((err)=>{
+ setCallout(null)
+ })
+ }
+
+ // TO-DO: To be removed once ErrorBanner/ ErrorCallout is fixed.
+ const generateErrorMessage = (error: FrappeError) =>{
+ if (error.exc_type === "ValidationError") return 'Too many requests. Please try after some time.'
+ return 'User does not exist. Please Sign Up.'
+ }
+
+ return (
+
+
+ {error && }
+ {callout && }
+
+
+
+
+
+ );
+};
+
+Component.displayName = "ForgotPassword";
\ No newline at end of file
diff --git a/raven-app/src/pages/auth/Login.tsx b/raven-app/src/pages/auth/Login.tsx
index 9d7cae3f0..8dd5721a1 100644
--- a/raven-app/src/pages/auth/Login.tsx
+++ b/raven-app/src/pages/auth/Login.tsx
@@ -131,6 +131,16 @@ export const Component = () => {
{isSubmitting ? : 'Login'}
+
+
+
+ Forgot Password?
+
+
+
diff --git a/raven-app/src/pages/auth/LoginWithEmail.tsx b/raven-app/src/pages/auth/LoginWithEmail.tsx
index 3820627cc..1a7de1a4b 100644
--- a/raven-app/src/pages/auth/LoginWithEmail.tsx
+++ b/raven-app/src/pages/auth/LoginWithEmail.tsx
@@ -39,6 +39,8 @@ export const Component = () => {
state: true,
message: "Login Link sent on Email",
});
+ }).catch((err)=>{
+ setCallout(null)
})
}
@@ -65,7 +67,6 @@ export const Component = () => {
})}
name="email"
type="email"
- required
placeholder="jane@example.com"
tabIndex={0}
/>
diff --git a/raven-app/src/types/Auth/Login.ts b/raven-app/src/types/Auth/Login.ts
index a4c30eaff..e9412916b 100644
--- a/raven-app/src/types/Auth/Login.ts
+++ b/raven-app/src/types/Auth/Login.ts
@@ -22,4 +22,8 @@ export type VerificationType = {
prompt?: string | undefined,
token_delivery?: boolean,
setup?: boolean
+}
+
+export type ForgotPasswordInput = {
+ user: LoginInputs["email"]
}
\ No newline at end of file
diff --git a/raven-app/yarn.lock b/raven-app/yarn.lock
index 9f63812e8..1fb9e09eb 100644
--- a/raven-app/yarn.lock
+++ b/raven-app/yarn.lock
@@ -4800,6 +4800,7 @@ sourcemap-codec@^1.4.8:
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
+ name string-width-cjs
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -4873,6 +4874,7 @@ stringify-object@^3.3.0:
is-regexp "^1.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ name strip-ansi-cjs
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
diff --git a/raven/__init__.py b/raven/__init__.py
index c0f285b05..5b6018861 100644
--- a/raven/__init__.py
+++ b/raven/__init__.py
@@ -1 +1 @@
-__version__ = "1.4.4"
+__version__ = "1.5.0"
diff --git a/raven/api/chat.py b/raven/api/chat.py
index 31a3a8b57..68ea867fc 100644
--- a/raven/api/chat.py
+++ b/raven/api/chat.py
@@ -17,7 +17,21 @@ def get_channel_members(channel_id):
channel_member = frappe.qb.DocType("Raven Channel Member")
user = frappe.qb.DocType("Raven User")
if frappe.get_cached_value("Raven Channel", channel_id, "type") == "Open":
- member_array = get_list()
+ # select all users, if channel member exists, get is_admin
+ member_query = (
+ frappe.qb.from_(user)
+ .join(channel_member, JoinType.left)
+ .on((user.name == channel_member.user_id) & (channel_member.channel_id == channel_id))
+ .select(
+ user.name,
+ user.full_name,
+ user.user_image,
+ user.first_name,
+ user.type,
+ channel_member.is_admin,
+ )
+ .orderby(channel_member.creation, order=Order.desc)
+ )
else:
member_query = (
frappe.qb.from_(channel_member)
@@ -35,7 +49,7 @@ def get_channel_members(channel_id):
.orderby(channel_member.creation, order=Order.desc)
)
- member_array = member_query.run(as_dict=True)
+ member_array = member_query.run(as_dict=True)
member_object = {}
for member in member_array:
diff --git a/raven/api/raven_poll.py b/raven/api/raven_poll.py
index d59fb6871..7863b7890 100644
--- a/raven/api/raven_poll.py
+++ b/raven/api/raven_poll.py
@@ -113,3 +113,62 @@ def add_vote(message_id, option_id):
).insert()
return "Vote added successfully."
+
+
+@frappe.whitelist(methods=["POST"])
+def retract_vote(poll_id):
+ # delete all votes by the user for the poll (this takes care of the case where the user has voted for multiple options in the same poll)
+ user = frappe.session.user
+ votes = frappe.get_all(
+ "Raven Poll Vote", filters={"poll_id": poll_id, "user_id": user}, fields=["name"]
+ )
+ if not votes:
+ frappe.throw(_("You have not voted for any option in this poll."))
+ else:
+ for vote in votes:
+ frappe.delete_doc("Raven Poll Vote", vote.name)
+
+
+@frappe.whitelist()
+def get_all_votes(poll_id):
+
+ # Check if the current user has access to the poll
+ if not frappe.has_permission(doctype="Raven Poll", doc=poll_id, ptype="read"):
+ frappe.throw(_("You do not have permission to access this poll"), frappe.PermissionError)
+
+ # Check if the poll is anonymous
+ is_anonymous = frappe.get_cached_value("Raven Poll", poll_id, "is_anonymous")
+
+ if is_anonymous:
+ frappe.throw(_("This poll is anonymous. You do not have permission to access the votes."), frappe.PermissionError)
+ else:
+ # Get all votes for this poll
+ votes = frappe.get_all(
+ "Raven Poll Vote",
+ filters={"poll_id": poll_id},
+ fields=["name", "option", "user_id"]
+ )
+
+ # Initialize results dictionary
+ results = {}
+
+ # Process votes
+ for vote in votes:
+ option = vote['option']
+ if option not in results:
+ results[option] = {
+ 'users': [vote['user_id']],
+ 'count': 1
+ }
+ else:
+ results[option]['users'].append(vote['user_id'])
+ results[option]['count'] += 1
+
+ # Calculate total votes
+ total_votes = sum(result['count'] for result in results.values())
+
+ # Calculate percentages
+ for result in results.values():
+ result['percentage'] = (result['count'] / total_votes) * 100
+
+ return results
\ No newline at end of file
diff --git a/raven/package.json b/raven/package.json
index 53022cb4e..e1e2b57f0 100644
--- a/raven/package.json
+++ b/raven/package.json
@@ -1,6 +1,6 @@
{
"name": "raven",
- "version": "1.4.4",
+ "version": "1.5.0",
"description": "",
"main": "index.js",
"scripts": {
diff --git a/raven/permissions.py b/raven/permissions.py
index 04227d5ea..36af8aec8 100644
--- a/raven/permissions.py
+++ b/raven/permissions.py
@@ -76,7 +76,7 @@ def raven_poll_vote_has_permission(doc, user=None, ptype=None):
Allowed users can add a vote to a poll and read votes (if the poll is not anonymous)
"""
- if ptype in ["read", "create"]:
+ if ptype in ["read", "create", "delete"]:
if doc.owner == user:
return True
elif user == "Administrator":
diff --git a/raven/raven_integrations/doctype/raven_scheduler_event/raven_scheduler_event.py b/raven/raven_integrations/doctype/raven_scheduler_event/raven_scheduler_event.py
index 127fc204f..a204e9ae7 100644
--- a/raven/raven_integrations/doctype/raven_scheduler_event/raven_scheduler_event.py
+++ b/raven/raven_integrations/doctype/raven_scheduler_event/raven_scheduler_event.py
@@ -6,83 +6,82 @@
class RavenSchedulerEvent(Document):
- # begin: auto-generated types
- # This code is auto-generated. Do not modify anything in this block.
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
- from typing import TYPE_CHECKING
+ from typing import TYPE_CHECKING
- if TYPE_CHECKING:
- from frappe.types import DF
+ if TYPE_CHECKING:
+ from frappe.types import DF
- bot: DF.Link
- channel: DF.Link
- content: DF.SmallText
- cron_expression: DF.Data | None
- disabled: DF.Check
- dm: DF.Link | None
- event_frequency: DF.Literal["Every Day",
- "Every Day of the week", "Date of the month", "Cron"]
- event_name: DF.Data
- scheduler_event_id: DF.Link | None
- send_to: DF.Literal["Channel", "DM"]
- # end: auto-generated types
+ bot: DF.Link
+ channel: DF.Link
+ content: DF.SmallText
+ cron_expression: DF.Data | None
+ disabled: DF.Check
+ dm: DF.Link | None
+ event_frequency: DF.Literal["Every Day", "Every Day of the week", "Date of the month", "Cron"]
+ event_name: DF.Data
+ scheduler_event_id: DF.Link | None
+ send_to: DF.Literal["Channel", "DM"]
+ # end: auto-generated types
- def before_save(self):
- '''
- 1. If the 'scheduler_event_id' is not set, create a Server Script of type 'Scheduler Event' and set the 'scheduler_event_id' to the name of the Server Script.
- '''
- if not self.scheduler_event_id:
- self.scheduler_event_id = self.create_scheduler_event()
- else:
- server_script = frappe.get_doc(
- 'Server Script', self.scheduler_event_id)
- server_script.cron_format = self.cron_expression
- server_script.script = self.get_scheduler_event_script()
- server_script.save()
+ def before_save(self):
+ """
+ 1. If the 'scheduler_event_id' is not set, create a Server Script of type 'Scheduler Event' and set the 'scheduler_event_id' to the name of the Server Script.
+ """
+ if not self.scheduler_event_id:
+ self.scheduler_event_id = self.create_scheduler_event()
+ else:
+ server_script = frappe.get_doc("Server Script", self.scheduler_event_id)
+ server_script.cron_format = self.cron_expression
+ server_script.script = self.get_scheduler_event_script()
+ server_script.save()
- def on_update(self):
- '''
- 1. If the 'scheduler_event_id' is set, and the 'disabled' field is updated, update the 'disabled' field of the Server Script of type 'Scheduler Event' with the name 'scheduler_event_id'.
- '''
- if self.scheduler_event_id:
- server_script = frappe.get_doc(
- 'Server Script', self.scheduler_event_id)
- server_script.disabled = self.disabled
- server_script.save()
+ def on_update(self):
+ """
+ 1. If the 'scheduler_event_id' is set, and the 'disabled' field is updated, update the 'disabled' field of the Server Script of type 'Scheduler Event' with the name 'scheduler_event_id'.
+ """
+ if self.scheduler_event_id:
+ server_script = frappe.get_doc("Server Script", self.scheduler_event_id)
+ server_script.disabled = self.disabled
+ server_script.save()
- def on_trash(self):
- '''
- 1. If the 'scheduler_event_id' is set, delete the Server Script of type 'Scheduler Event' with the name 'scheduler_event_id'.
- '''
- if self.scheduler_event_id:
- frappe.delete_doc('Server Script', self.scheduler_event_id)
+ def on_trash(self):
+ """
+ 1. If the 'scheduler_event_id' is set, delete the Server Script of type 'Scheduler Event' with the name 'scheduler_event_id'.
+ """
+ if self.scheduler_event_id:
+ frappe.delete_doc("Server Script", self.scheduler_event_id)
- def create_scheduler_event(self):
- '''
- Create a Server Script of type 'Scheduler Event' and set the 'scheduler_event_id' to the name of the Server Script.
- '''
- server_script = frappe.get_doc({
- 'doctype': 'Server Script',
- 'script_type': 'Scheduler Event',
- 'name': self.event_name,
- 'disabled': 0,
- 'event_frequency': 'Cron',
- 'cron_format': self.cron_expression,
- 'script': self.get_scheduler_event_script()
- })
- server_script.insert()
- return server_script.name
+ def create_scheduler_event(self):
+ """
+ Create a Server Script of type 'Scheduler Event' and set the 'scheduler_event_id' to the name of the Server Script.
+ """
+ server_script = frappe.get_doc(
+ {
+ "doctype": "Server Script",
+ "script_type": "Scheduler Event",
+ "name": self.event_name,
+ "disabled": 0,
+ "event_frequency": "Cron",
+ "cron_format": self.cron_expression,
+ "script": self.get_scheduler_event_script(),
+ }
+ )
+ server_script.insert()
+ return server_script.name
- def get_scheduler_event_script(self):
- '''
- Get the script for the Scheduler Event
- '''
- # bot = frappe.get_doc('Raven Bot', self.bot)
- # bot.send_message(self.channel, {'text': self.content})
- # return code snippet with bot & content as values
- message = {'text': self.content}
- script = f'''
+ def get_scheduler_event_script(self):
+ """
+ Get the script for the Scheduler Event
+ """
+ # bot = frappe.get_doc('Raven Bot', self.bot)
+ # bot.send_message(self.channel, {'text': self.content})
+ # return code snippet with bot & content as values
+ message = {"text": self.content}
+ script = f"""
bot = frappe.get_doc('Raven Bot', '{self.bot}')\n
bot.send_message('{self.channel}', {message})
-'''
- return script
+"""
+ return script
diff --git a/raven/raven_integrations/doctype/raven_webhook/raven_webhook.py b/raven/raven_integrations/doctype/raven_webhook/raven_webhook.py
index 862a5c49a..28d453f4e 100644
--- a/raven/raven_integrations/doctype/raven_webhook/raven_webhook.py
+++ b/raven/raven_integrations/doctype/raven_webhook/raven_webhook.py
@@ -17,8 +17,7 @@ class RavenWebhook(Document):
from frappe.types import DF
channel_id: DF.Link | None
- channel_type: DF.Literal["", "Public",
- "Private", "Open", "DM", "Self Message"]
+ channel_type: DF.Literal["", "Public", "Private", "Open", "DM", "Self Message"]
condition: DF.SmallText | None
conditions_on: DF.Literal["", "Channel", "User", "Channel Type", "Custom"]
enable_security: DF.Check
@@ -32,29 +31,36 @@ class RavenWebhook(Document):
webhook_data: DF.Table[WebhookData]
webhook_headers: DF.Table[WebhookHeader]
webhook_secret: DF.Password | None
- webhook_trigger: DF.Literal["Message Sent", "Message Edited", "Message Deleted", "Message Reacted On",
- "Channel Created", "Channel Deleted", "Channel Member Added", "Channel Member Deleted", "User Added", "User Deleted"]
+ webhook_trigger: DF.Literal[
+ "Message Sent",
+ "Message Edited",
+ "Message Deleted",
+ "Message Reacted On",
+ "Channel Created",
+ "Channel Deleted",
+ "Channel Member Added",
+ "Channel Member Deleted",
+ "User Added",
+ "User Deleted",
+ ]
# end: auto-generated types
def validate(self):
# 1. Check if webhook name is unique
# 2. Check if webhook_data and webhook_headers are unique
-
-
# 1. Check if webhook name is unique
- webhook = frappe.get_all('Raven Webhook', filters={
- 'webhook_name': self.name})
+ webhook = frappe.get_all("Raven Webhook", filters={"webhook_name": self.name})
if webhook:
- frappe.throw('Webhook name already exists')
+ frappe.throw("Webhook name already exists")
# 2. Check if webhook_data and webhook_headers are unique
webhook_data_keys = [data.key for data in self.webhook_data]
webhook_header_keys = [data.key for data in self.webhook_headers]
if len(webhook_data_keys) != len(set(webhook_data_keys)):
- frappe.throw('Webhook Data keys should be unique')
+ frappe.throw("Webhook Data keys should be unique")
if len(webhook_header_keys) != len(set(webhook_header_keys)):
- frappe.throw('Webhook Headers keys should be unique')
+ frappe.throw("Webhook Headers keys should be unique")
def before_save(self):
# 1. Check if webhook ID is exists
@@ -70,26 +76,25 @@ def before_save(self):
# 3. Create the webhook
self.create_webhook()
-
def on_trash(self):
# Delete the webhook
if self.webhook:
- frappe.db.delete('Webhook', self.webhook)
+ frappe.db.delete("Webhook", self.webhook)
def create_webhook(self):
# Create a new webhook
doctype, event = self.get_doctype_and_event()
conditions = self.get_conditions()
- webhook_doc = frappe.new_doc('Webhook')
+ webhook_doc = frappe.new_doc("Webhook")
webhook_doc.name = self.name
webhook_doc.request_url = self.request_url
webhook_doc.is_dynamic_url = self.is_dynamic_url
webhook_doc.timeout = self.timeout
webhook_doc.enable_security = self.enable_security
webhook_doc.webhook_secret = self.webhook_secret
- webhook_doc.request_method = 'POST'
- webhook_doc.request_structure = 'Form URL-Encoded'
+ webhook_doc.request_method = "POST"
+ webhook_doc.request_structure = "Form URL-Encoded"
self.set_webhook_data_and_headers(webhook_doc)
webhook_doc.webhook_doctype = doctype
webhook_doc.webhook_docevent = event
@@ -101,7 +106,7 @@ def update_webhook(self):
# Update the webhook
conditions = self.get_conditions()
- webhook_doc = frappe.get_doc('Webhook', self.webhook)
+ webhook_doc = frappe.get_doc("Webhook", self.webhook)
webhook_doc.request_url = self.request_url
webhook_doc.is_dynamic_url = self.is_dynamic_url
webhook_doc.timeout = self.timeout
@@ -115,8 +120,8 @@ def set_webhook_data_and_headers(self, webhook_doc):
# Set the webhook data and headers
# get the existing webhook data and headers
- webhook_header = webhook_doc.get('webhook_headers', [])
- webhook_data = webhook_doc.get('webhook_data', [])
+ webhook_header = webhook_doc.get("webhook_headers", [])
+ webhook_data = webhook_doc.get("webhook_data", [])
# get the existing webhook data and headers keys
webhook_data_keys = [data.key for data in self.webhook_data]
@@ -127,81 +132,54 @@ def set_webhook_data_and_headers(self, webhook_doc):
# and append the new webhook data and headers
for data in self.webhook_data:
if data.key not in webhook_data_keys:
- webhook_doc.append('webhook_data', {
- 'key': data.key,
- 'fieldname': data.fieldname,
- })
+ webhook_doc.append(
+ "webhook_data",
+ {
+ "key": data.key,
+ "fieldname": data.fieldname,
+ },
+ )
for data in self.webhook_headers:
if data.key not in webhook_header_keys:
- webhook_doc.append('webhook_headers', {
- 'key': data.key,
- 'value': data.value,
- })
+ webhook_doc.append(
+ "webhook_headers",
+ {
+ "key": data.key,
+ "value": data.value,
+ },
+ )
def get_doctype_and_event(self):
doctypes_and_events = [
{
- 'label': 'Message Sent',
- 'doctype': 'Raven Message',
- 'event': 'after_insert',
- },
- {
-
- 'label': 'Message Edited',
- 'doctype': 'Raven Message',
- 'event': 'on_update'
- },
- {
-
- 'label': 'Message Deleted',
- 'doctype': 'Raven Message',
- 'event': 'on_trash'
- },
- {
-
- 'label': 'Message Reacted On',
- 'doctype': 'Raven Message Reaction',
- 'event': 'after_insert'
- },
- {
-
- 'label': 'Channel Created',
- 'doctype': 'Raven Channel',
- 'event': 'after_insert'
+ "label": "Message Sent",
+ "doctype": "Raven Message",
+ "event": "after_insert",
},
+ {"label": "Message Edited", "doctype": "Raven Message", "event": "on_update"},
+ {"label": "Message Deleted", "doctype": "Raven Message", "event": "on_trash"},
+ {"label": "Message Reacted On", "doctype": "Raven Message Reaction", "event": "after_insert"},
+ {"label": "Channel Created", "doctype": "Raven Channel", "event": "after_insert"},
+ {"label": "Channel Deleted", "doctype": "Raven Channel", "event": "on_trash"},
{
-
- 'label': 'Channel Deleted',
- 'doctype': 'Raven Channel',
- 'event': 'on_trash'
+ "label": "Member Added to the Channel",
+ "doctype": "Raven Channel Member",
+ "event": "after_insert",
},
{
- 'label': 'Member Added to the Channel',
- 'doctype': 'Raven Channel Member',
- 'event': 'after_insert'
+ "label": "Member Deleted from the Channel",
+ "doctype": "Raven Channel Member",
+ "event": "on_trash",
},
- {
- 'label': 'Member Deleted from the Channel',
- 'doctype': 'Raven Channel Member',
- 'event': 'on_trash'
- },
- {
- 'label': 'User Added',
- 'doctype': 'Raven User',
- 'event': 'after_insert'
- },
- {
- 'label': 'User Deleted',
- 'doctype': 'Raven User',
- 'event': 'on_trash'
- }
+ {"label": "User Added", "doctype": "Raven User", "event": "after_insert"},
+ {"label": "User Deleted", "doctype": "Raven User", "event": "on_trash"},
]
doctype, event = None, None
for doctype_and_event in doctypes_and_events:
- if self.webhook_trigger == doctype_and_event['label']:
- doctype = doctype_and_event['doctype']
- event = doctype_and_event['event']
+ if self.webhook_trigger == doctype_and_event["label"]:
+ doctype = doctype_and_event["doctype"]
+ event = doctype_and_event["event"]
break
return doctype, event
@@ -209,42 +187,42 @@ def get_conditions(self):
# Get the conditions for the webhook
doctype, event = self.get_doctype_and_event()
if self.trigger_webhook_on_condition:
- if self.conditions_on == 'Channel':
- if doctype == 'Raven Channel':
+ if self.conditions_on == "Channel":
+ if doctype == "Raven Channel":
# return 'doc.name == self.channel_id'
return f'doc.name == "{self.channel_id}"'
- elif doctype == 'Raven Channel Member':
+ elif doctype == "Raven Channel Member":
return f'doc.channel_id == "{self.channel_id}"'
- elif doctype == 'Raven Message':
+ elif doctype == "Raven Message":
return f'doc.channel_id == "{self.channel_id}"'
- elif doctype == 'Raven Message Reaction':
- frappe.throw('Message Reaction cannot be triggered on Channel')
- elif doctype == 'Raven User':
- frappe.throw('Raven User cannot be triggered on Channel')
-
- elif self.conditions_on == 'User':
- if doctype == 'Raven Channel':
- frappe.throw('Channel cannot be triggered on User')
- elif doctype == 'Raven Channel Member':
+ elif doctype == "Raven Message Reaction":
+ frappe.throw("Message Reaction cannot be triggered on Channel")
+ elif doctype == "Raven User":
+ frappe.throw("Raven User cannot be triggered on Channel")
+
+ elif self.conditions_on == "User":
+ if doctype == "Raven Channel":
+ frappe.throw("Channel cannot be triggered on User")
+ elif doctype == "Raven Channel Member":
return f'doc.user_id == "{self.user}"'
- elif doctype == 'Raven Message':
+ elif doctype == "Raven Message":
return f'doc.owner == "{self.user}"'
- elif doctype == 'Raven Message Reaction':
+ elif doctype == "Raven Message Reaction":
return f'doc.owner == "{self.user}"'
- elif self.conditions_on == 'Channel Type':
- if doctype == 'Raven Channel':
- if self.channel_type in ['Public', 'Private', 'Open']:
+ elif self.conditions_on == "Channel Type":
+ if doctype == "Raven Channel":
+ if self.channel_type in ["Public", "Private", "Open"]:
return f'doc.type == "{self.channel_type}"'
- elif self.channel_type == 'DM':
- return f'doc.is_direct_message == 1'
- elif self.channel_type == 'Self Message':
- return f'doc.is_self_message == 1'
+ elif self.channel_type == "DM":
+ return f"doc.is_direct_message == 1"
+ elif self.channel_type == "Self Message":
+ return f"doc.is_self_message == 1"
else:
- frappe.throw('Invalid Channel Type')
+ frappe.throw("Invalid Channel Type")
else:
- frappe.throw('Channel Type cannot be triggered on other doctypes')
- elif self.conditions_on == 'Custom':
+ frappe.throw("Channel Type cannot be triggered on other doctypes")
+ elif self.conditions_on == "Custom":
return self.condition
return None
diff --git a/raven/raven_messaging/doctype/raven_poll_vote/raven_poll_vote.json b/raven/raven_messaging/doctype/raven_poll_vote/raven_poll_vote.json
index dea96348a..751c9f845 100644
--- a/raven/raven_messaging/doctype/raven_poll_vote/raven_poll_vote.json
+++ b/raven/raven_messaging/doctype/raven_poll_vote/raven_poll_vote.json
@@ -53,7 +53,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-03-22 20:08:19.982449",
+ "modified": "2024-04-08 13:32:30.594728",
"modified_by": "Administrator",
"module": "Raven Messaging",
"name": "Raven Poll Vote",
@@ -73,6 +73,7 @@
},
{
"create": 1,
+ "delete": 1,
"email": 1,
"export": 1,
"if_owner": 1,