diff --git a/frontend/components/Domain/Group/GroupWebhookEditor.vue b/frontend/components/Domain/Group/GroupWebhookEditor.vue index 93b4df4362f..b29b70cf01d 100644 --- a/frontend/components/Domain/Group/GroupWebhookEditor.vue +++ b/frontend/components/Domain/Group/GroupWebhookEditor.vue @@ -18,8 +18,6 @@ icon: $globals.icons.testTube, text: $tc('general.test'), event: 'test', - // TODO: There is no functionality hooked up to this. Enable it when there is - disabled: true, }, { icon: $globals.icons.save, diff --git a/frontend/composables/use-group-webhooks.ts b/frontend/composables/use-group-webhooks.ts index 776b4341e01..4199d412a3c 100644 --- a/frontend/composables/use-group-webhooks.ts +++ b/frontend/composables/use-group-webhooks.ts @@ -64,7 +64,6 @@ export const useGroupWebhooks = function () { newDt.setMinutes(Number(minutes)); updateData.scheduledTime = `${pad(newDt.getUTCHours(), 2)}:${pad(newDt.getUTCMinutes(), 2)}`; - console.log(updateData.scheduledTime); const payload = { ...updateData, @@ -85,7 +84,14 @@ export const useGroupWebhooks = function () { if (data) { this.refreshAll(); } + loading.value = false; }, + + async testOne(id: string | number) { + loading.value = true; + await api.groupWebhooks.testOne(id); + loading.value = false; + } }; const webhooks = actions.getAll(); diff --git a/frontend/lib/api/user/group-webhooks.ts b/frontend/lib/api/user/group-webhooks.ts index 8980bec1406..99e0136857a 100644 --- a/frontend/lib/api/user/group-webhooks.ts +++ b/frontend/lib/api/user/group-webhooks.ts @@ -6,9 +6,15 @@ const prefix = "/api"; const routes = { webhooks: `${prefix}/groups/webhooks`, webhooksId: (id: string | number) => `${prefix}/groups/webhooks/${id}`, + webhooksIdTest: (id: string | number) => `${prefix}/groups/webhooks/${id}/test`, }; export class WebhooksAPI extends BaseCRUDAPI { baseRoute = routes.webhooks; itemRoute = routes.webhooksId; + itemTestRoute = routes.webhooksIdTest; + + async testOne(itemId: string | number) { + return await this.requests.post(`${this.itemTestRoute(itemId)}`, {}); + } } diff --git a/frontend/pages/group/webhooks.vue b/frontend/pages/group/webhooks.vue index e5a59ae805d..cbea7b9270a 100644 --- a/frontend/pages/group/webhooks.vue +++ b/frontend/pages/group/webhooks.vue @@ -10,8 +10,6 @@ - - @@ -36,6 +34,7 @@ :webhook="webhook" @save="actions.updateOne($event)" @delete="actions.deleteOne($event)" + @test="actions.testOne($event).then(() => alert.success($tc('events.test-message-sent')))" /> @@ -47,6 +46,7 @@ import { defineComponent } from "@nuxtjs/composition-api"; import { useGroupWebhooks, timeUTC } from "~/composables/use-group-webhooks"; import GroupWebhookEditor from "~/components/Domain/Group/GroupWebhookEditor.vue"; +import { alert } from "~/composables/use-toast"; export default defineComponent({ components: { GroupWebhookEditor }, @@ -55,6 +55,7 @@ export default defineComponent({ const { actions, webhooks } = useGroupWebhooks(); return { + alert, webhooks, actions, timeUTC diff --git a/mealie/routes/groups/controller_webhooks.py b/mealie/routes/groups/controller_webhooks.py index 4259e999457..f91039a1900 100644 --- a/mealie/routes/groups/controller_webhooks.py +++ b/mealie/routes/groups/controller_webhooks.py @@ -1,7 +1,7 @@ from datetime import datetime from functools import cached_property -from fastapi import APIRouter, Depends +from fastapi import APIRouter, BackgroundTasks, Depends from pydantic import UUID4 from mealie.routes._base.base_controllers import BaseUserController @@ -10,7 +10,7 @@ from mealie.schema import mapper from mealie.schema.group.webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination from mealie.schema.response.pagination import PaginationQuery -from mealie.services.scheduler.tasks.post_webhooks import post_group_webhooks +from mealie.services.scheduler.tasks.post_webhooks import post_group_webhooks, post_single_webhook router = APIRouter(prefix="/groups/webhooks", tags=["Groups: Webhooks"]) @@ -52,6 +52,11 @@ def rerun_webhooks(self): def get_one(self, item_id: UUID4): return self.mixins.get_one(item_id) + @router.post("/{item_id}/test") + def test_one(self, item_id: UUID4, bg_tasks: BackgroundTasks): + webhook = self.mixins.get_one(item_id) + bg_tasks.add_task(post_single_webhook, webhook, "Test Webhook") + @router.put("/{item_id}", response_model=ReadWebhook) def update_one(self, item_id: UUID4, data: CreateWebhook): return self.mixins.update_one(data, item_id) diff --git a/mealie/services/event_bus_service/event_types.py b/mealie/services/event_bus_service/event_types.py index 569eb693281..83496372ae2 100644 --- a/mealie/services/event_bus_service/event_types.py +++ b/mealie/services/event_bus_service/event_types.py @@ -3,7 +3,7 @@ from enum import Enum, auto from typing import Any -from pydantic import UUID4, field_validator +from pydantic import UUID4, SerializeAsAny, field_validator from ...schema._mealie.mealie_model import MealieModel @@ -179,7 +179,7 @@ class Event(MealieModel): message: EventBusMessage event_type: EventTypes integration_id: str - document_data: EventDocumentDataBase + document_data: SerializeAsAny[EventDocumentDataBase] # set at instantiation event_id: UUID4 | None = None diff --git a/mealie/services/scheduler/tasks/post_webhooks.py b/mealie/services/scheduler/tasks/post_webhooks.py index a898775c8bc..b9f37a223dd 100644 --- a/mealie/services/scheduler/tasks/post_webhooks.py +++ b/mealie/services/scheduler/tasks/post_webhooks.py @@ -4,10 +4,14 @@ from mealie.db.db_setup import session_context from mealie.repos.all_repositories import get_repositories +from mealie.schema.group.webhook import ReadWebhook from mealie.schema.response.pagination import PaginationQuery +from mealie.services.event_bus_service.event_bus_listeners import WebhookEventListener from mealie.services.event_bus_service.event_bus_service import EventBusService from mealie.services.event_bus_service.event_types import ( INTERNAL_INTEGRATION_ID, + Event, + EventBusMessage, EventDocumentType, EventOperation, EventTypes, @@ -61,3 +65,24 @@ def post_group_webhooks(start_dt: datetime | None = None, group_id: UUID4 | None event_type=event_type, document_data=event_document_data, ) + + +def post_single_webhook(webhook: ReadWebhook, message: str = "") -> None: + dt = datetime.min.replace(tzinfo=timezone.utc) + event_type = EventTypes.webhook_task + + event_document_data = EventWebhookData( + document_type=EventDocumentType.mealplan, + operation=EventOperation.info, + webhook_start_dt=dt, + webhook_end_dt=dt, + ) + event = Event( + message=EventBusMessage.from_type(event_type, body=message), + event_type=event_type, + integration_id=INTERNAL_INTEGRATION_ID, + document_data=event_document_data, + ) + + listener = WebhookEventListener(webhook.group_id) + listener.publish_to_subscribers(event, [webhook])