Skip to content

Commit

Permalink
Added filters in the webhooks (#1140)
Browse files Browse the repository at this point in the history
* webhooks frontend + web hooks with filters

---------

Co-authored-by: namansleeps <mandhani12@gmail.com>
Co-authored-by: Fluder-Paradyne <121793617+Fluder-Paradyne@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 31, 2023
1 parent 5fc2526 commit 946a4a0
Show file tree
Hide file tree
Showing 13 changed files with 387 additions and 28 deletions.
21 changes: 21 additions & 0 deletions gui/pages/Content/Marketplace/Market.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,24 @@
.settings_tab_img{
margin-top: -1px;
}

.checkboxGroup {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
height: 15vh;
}

.checkboxLabel {
display: flex;
align-items: center;
width: 15vw;
cursor:pointer
}

.checkboxText {
font-weight: 400;
font-size: 12px;
color: #FFF;
margin-left:5px;
}
2 changes: 1 addition & 1 deletion gui/pages/Content/Toolkits/Metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export default function Metrics({toolName, knowledgeName}) {
<table className="w_100 margin_0 padding_0">
<thead>
<tr className="border_top_none text_align_left border_bottom_none">
<th className="table_header w_15">Longest Timestamp</th>
<th className="table_header w_15">Log Timestamp</th>
<th className="table_header w_15">Agent Name</th>
<th className="table_header w_40">Run Name</th>
<th className="table_header w_15">Model</th>
Expand Down
6 changes: 6 additions & 0 deletions gui/pages/Dashboard/Settings/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Image from "next/image";
import Model from "@/pages/Dashboard/Settings/Model";
import Database from "@/pages/Dashboard/Settings/Database";
import ApiKeys from "@/pages/Dashboard/Settings/ApiKeys";
import Webhooks from "@/pages/Dashboard/Settings/Webhooks";

export default function Settings({organisationId, sendDatabaseData}) {
const [activeTab, setActiveTab] = useState('model');
Expand Down Expand Up @@ -38,12 +39,17 @@ export default function Settings({organisationId, sendDatabaseData}) {
<Image width={14} height={14} src="/images/key_white.svg" alt="api-key-icon"/>
<span>API Keys</span>
</button>
<button onClick={() => switchTab('webhooks')} className={activeTab === 'webhooks' ? 'tab_button_selected' : 'tab_button'}>
<Image className={styles.settings_tab_img} width={14} height={14} src="/images/webhook_icon.svg"
alt="database-icon"/>&nbsp;Webhooks
</button>
</div>
</div>
<div>
{activeTab === 'model' && <Model organisationId={organisationId}/>}
{activeTab === 'database' && <Database sendDatabaseData={sendDatabaseData} organisationId={organisationId}/>}
{activeTab === 'apikeys' && <ApiKeys />}
{activeTab === 'webhooks' && <Webhooks />}
</div>
</div>
</div>
Expand Down
148 changes: 148 additions & 0 deletions gui/pages/Dashboard/Settings/Webhooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, {useState, useEffect} from 'react';
import {ToastContainer, toast} from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import agentStyles from "@/pages/Content/Agents/Agents.module.css";
import {
editWebhook,
getWebhook, saveWebhook,
} from "@/pages/api/DashboardService";
import {loadingTextEffect, removeTab} from "@/utils/utils";
import styles from "@/pages/Content/Marketplace/Market.module.css";
export default function Webhooks() {
const [webhookUrl, setWebhookUrl] = useState('');
const [webhookId, setWebhookId] = useState(-1);
const [isLoading, setIsLoading] = useState(true)
const [existingWebhook, setExistingWebhook] = useState(false)
const [isEdtiting, setIsEdtiting] = useState(false)
const [loadingText, setLoadingText] = useState("Loading Webhooks");
const [selectedCheckboxes, setSelectedCheckboxes] = useState([]);
const checkboxes = [
{ label: 'Agent is running', value: 'RUNNING' },
{ label: 'Agent run is paused', value: 'PAUSED' },
{ label: 'Agent run is completed', value: 'COMPLETED' },
{ label: 'Agent is terminated ', value: 'TERMINATED' },
{ label: 'Agent run max iteration reached', value: 'MAX ITERATION REACHED' },
];


useEffect(() => {
loadingTextEffect('Loading Webhooks', setLoadingText, 500);
fetchWebhooks();
}, []);

const handleWebhookChange = (event) => {
setWebhookUrl(event.target.value);
};

const handleSaveWebhook = () => {
if(!webhookUrl || webhookUrl.trim() === ""){
toast.error("Enter valid webhook", {autoClose: 1800});
return;
}
if(isEdtiting){
editWebhook(webhookId, { url: webhookUrl, filters: {status: selectedCheckboxes}})
.then((response) => {
setIsEdtiting(false)
fetchWebhooks()
toast.success("Webhook deleted successfully", {autoClose: 1800});
})
.catch((error) => {
console.error('Error fetching webhook', error);
});
return;
}
saveWebhook({name : "Webhook 1", url: webhookUrl, headers: {}, filters: {status: selectedCheckboxes}})
.then((response) => {
setExistingWebhook(true)
setWebhookId(response.data.id)
toast.success("Webhook created successfully", {autoClose: 1800});
})
.catch((error) => {
toast.error("Unable to create webhook", {autoClose: 1800});
console.error('Error saving webhook', error);
});
}

const fetchWebhooks = () => {
getWebhook()
.then((response) => {
setIsLoading(false)
if(response.data){
setWebhookUrl(response.data.url)
setExistingWebhook(true)
setWebhookId(response.data.id)
setSelectedCheckboxes(response.data.filters.status)
}
else{
setWebhookUrl('')
setExistingWebhook(false)
setWebhookId(-1)
}
})
.catch((error) => {
console.error('Error fetching webhook', error);
});
}

const toggleCheckbox = (value) => {
if (selectedCheckboxes.includes(value)) {
setSelectedCheckboxes(selectedCheckboxes.filter((item) => item !== value));
} else {
setSelectedCheckboxes([...selectedCheckboxes, value]);
}
};

return (<>
<div className="row">
<div className="col-3"></div>
<div className="col-6 col-6-scrollable">
{!isLoading ? <div>
<div className="title_wrapper mb_15">
<div className={styles.page_title}>Webhooks</div>
{existingWebhook &&
<button className="primary_button" onClick={() => {setExistingWebhook(false);setIsEdtiting(true)} } >
Edit
</button>}
</div>

<div>
<label className={agentStyles.form_label}>Destination URL</label>
<input disabled={existingWebhook ? true : false} className="input_medium" placeholder="Enter your destination url" type="text" value={webhookUrl}
onChange={handleWebhookChange}/>
<br />
<label className={agentStyles.form_label}>Events to include</label>
<div className={styles.checkboxGroup} >
{checkboxes.map((checkbox) => (
<label key={checkbox.value} className={styles.checkboxLabel}>
<input
disabled={existingWebhook ? true : false}
className="checkbox"
type="checkbox"
value={checkbox.value}
checked={selectedCheckboxes.includes(checkbox.value)}
onChange={() => toggleCheckbox(checkbox.value)}
/>
<span className={styles.checkboxText}>&nbsp;{checkbox.label}</span>
</label>
))}
</div>
</div>

{!existingWebhook && <div className="justify_end display_flex_container mt_15">
<button onClick={() => removeTab(-3, "Settings", "Settings", 0)} className="secondary_button mr_10">
Cancel
</button>
<button className="primary_button" onClick={handleSaveWebhook}>
{isEdtiting ? "Update" : "Create"}
</button>
</div>}

</div> : <div className="loading_container">
<div className="signInInfo loading_text">{loadingText}</div>
</div>}
</div>
<div className="col-3"></div>
</div>
<ToastContainer/>
</>)
}
12 changes: 12 additions & 0 deletions gui/pages/api/DashboardService.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,18 @@ export const deleteApiKey = (apiId) => {
return api.delete(`/api-keys/${apiId}`);
};

export const saveWebhook = (webhook) => {
return api.post(`/webhook/add`, webhook);
};

export const getWebhook = () => {
return api.get(`/webhook/get`);
};

export const editWebhook = (webhook_id, webook_data) => {
return api.post(`/webhook/edit/${webhook_id}`, webook_data);
};

export const publishToMarketplace = (executionId) => {
return api.post(`/agent_templates/publish_template/agent_execution_id/${executionId}`);
};
Expand Down
3 changes: 3 additions & 0 deletions gui/public/images/webhook_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions migrations/versions/40affbf3022b_add_filter_colume_in_webhooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""add filter colume in webhooks
Revision ID: 40affbf3022b
Revises: 5d5f801f28e7
Create Date: 2023-08-28 12:30:35.171176
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '40affbf3022b'
down_revision = '5d5f801f28e7'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('webhooks', sa.Column('filters', sa.JSON(), nullable=True))
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('webhooks', 'filters')
# ### end Alembic commands ###
65 changes: 60 additions & 5 deletions superagi/controllers/webhook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime

from fastapi import APIRouter
from typing import Optional
from fastapi import APIRouter, HTTPException
from fastapi import Depends
from fastapi_jwt_auth import AuthJWT
from fastapi_sqlalchemy import db
Expand All @@ -17,6 +17,7 @@ class WebHookIn(BaseModel):
name: str
url: str
headers: dict
filters: dict

class Config:
orm_mode = True
Expand All @@ -31,12 +32,21 @@ class WebHookOut(BaseModel):
is_deleted: bool
created_at: datetime
updated_at: datetime
filters: dict

class Config:
orm_mode = True

class WebHookEdit(BaseModel):
url: str
filters: dict

class Config:
orm_mode = True


# CRUD Operations

# CRUD Operations`
@router.post("/add", response_model=WebHookOut, status_code=201)
def create_webhook(webhook: WebHookIn, Authorize: AuthJWT = Depends(check_auth),
organisation=Depends(get_user_organisation)):
Expand All @@ -52,9 +62,54 @@ def create_webhook(webhook: WebHookIn, Authorize: AuthJWT = Depends(check_auth),
HTTPException (Status Code=404): If the associated project is not found.
"""
db_webhook = Webhooks(name=webhook.name, url=webhook.url, headers=webhook.headers, org_id=organisation.id,
is_deleted=False)
is_deleted=False, filters=webhook.filters)
db.session.add(db_webhook)
db.session.commit()
db.session.flush()

return db_webhook

@router.get("/get", response_model=Optional[WebHookOut])
def get_all_webhooks(
Authorize: AuthJWT = Depends(check_auth),
organisation=Depends(get_user_organisation),
):
"""
Retrieves a single webhook for the authenticated user's organisation.
Returns:
JSONResponse: A JSON response containing the retrieved webhook.
Raises:
"""
webhook = db.session.query(Webhooks).filter(Webhooks.org_id == organisation.id, Webhooks.is_deleted == False).first()
return webhook

@router.post("/edit/{webhook_id}", response_model=WebHookOut)
def edit_webhook(
updated_webhook: WebHookEdit,
webhook_id: int,
Authorize: AuthJWT = Depends(check_auth),
organisation=Depends(get_user_organisation),
):
"""
Soft-deletes a webhook by setting the value of is_deleted to True.
Args:
webhook_id (int): The ID of the webhook to delete.
Returns:
WebHookOut: The deleted webhook.
Raises:
HTTPException (Status Code=404): If the webhook is not found.
"""
webhook = db.session.query(Webhooks).filter(Webhooks.org_id == organisation.id, Webhooks.id == webhook_id, Webhooks.is_deleted == False).first()
if webhook is None:
raise HTTPException(status_code=404, detail="Webhook not found")

webhook.url = updated_webhook.url
webhook.filters = updated_webhook.filters

db.session.commit()

return webhook
Loading

0 comments on commit 946a4a0

Please sign in to comment.