1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 05:25:26 +02:00

feat: mealplan-webhooks (#1403)

* fix type errors on event bus

* webhooks fields required for new implementation

* db migration

* wip: webhook query + tests and stub function

* ignore type checker error

* type and method cleanup

* datetime and time utc validator

* update testing code for utc scheduled time

* fix file cmp function call

* update version_number

* add support for translating "time" objects when restoring backup

* bump recipe-scrapers

* use specific import syntax

* generate frontend types

* utilize names exports

* use utc times

* add task to scheduler

* implement new scheduler functionality

* stub for type annotation

* implement meal-plan data getter

* add experimental banner
This commit is contained in:
Hayden 2022-06-17 13:25:47 -08:00 committed by GitHub
parent b1256f4ad2
commit 5a053cdcd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 428 additions and 93 deletions

View file

@ -0,0 +1,84 @@
<template>
<div>
<v-card-text>
<v-switch v-model="webhookCopy.enabled" label="Enabled"></v-switch>
<v-text-field v-model="webhookCopy.name" label="Webhook Name"></v-text-field>
<v-text-field v-model="webhookCopy.url" label="Webhook Url"></v-text-field>
<v-time-picker v-model="scheduledTime" class="elevation-2" ampm-in-title format="ampm"></v-time-picker>
</v-card-text>
<v-card-actions class="py-0 justify-end">
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.delete,
text: $tc('general.delete'),
event: 'delete',
},
{
icon: $globals.icons.testTube,
text: $tc('general.test'),
event: 'test',
},
{
icon: $globals.icons.save,
text: $tc('general.save'),
event: 'save',
},
]"
@delete="$emit('delete', webhookCopy.id)"
@save="handleSave"
@test="$emit('test', webhookCopy.id)"
/>
</v-card-actions>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
import { ReadWebhook } from "~/types/api-types/group";
import { timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
export default defineComponent({
props: {
webhook: {
type: Object as () => ReadWebhook,
required: true,
},
},
emits: ["delete", "save", "test"],
setup(props, { emit }) {
const itemUTC = ref(props.webhook.scheduledTime);
const itemLocal = ref(timeUTCToLocal(props.webhook.scheduledTime));
const scheduledTime = computed({
get() {
return itemLocal.value;
},
set(v) {
itemUTC.value = timeLocalToUTC(v);
itemLocal.value = v;
},
});
const webhookCopy = ref({ ...props.webhook });
function handleSave() {
webhookCopy.value.scheduledTime = itemLocal.value;
emit("save", webhookCopy.value);
}
return {
webhookCopy,
scheduledTime,
handleSave,
itemUTC,
itemLocal,
};
},
head() {
return {
title: this.$tc("settings.webhooks.webhooks"),
};
},
});
</script>

View file

@ -37,7 +37,7 @@ export const useGroupWebhooks = function () {
enabled: false,
name: "New Webhook",
url: "",
time: "00:00",
scheduledTime: "00:00",
};
const { data } = await api.groupWebhooks.createOne(payload);
@ -52,8 +52,23 @@ export const useGroupWebhooks = function () {
return;
}
// Convert to UTC time
const [hours, minutes] = updateData.scheduledTime.split(":");
const newDt = new Date();
newDt.setHours(Number(hours));
newDt.setMinutes(Number(minutes));
updateData.scheduledTime = `${pad(newDt.getUTCHours(), 2)}:${pad(newDt.getUTCMinutes(), 2)}`;
console.log(updateData.scheduledTime);
const payload = {
...updateData,
scheduledTime: updateData.scheduledTime,
};
loading.value = true;
const { data } = await api.groupWebhooks.updateOne(updateData.id, updateData);
const { data } = await api.groupWebhooks.updateOne(updateData.id, payload);
if (data) {
this.refreshAll();
}
@ -73,3 +88,25 @@ export const useGroupWebhooks = function () {
return { webhooks, actions, validForm };
};
function pad(num: number, size: number) {
let numStr = num.toString();
while (numStr.length < size) numStr = "0" + numStr;
return numStr;
}
export function timeUTCToLocal(time: string): string {
const [hours, minutes] = time.split(":");
const dt = new Date();
dt.setUTCMinutes(Number(minutes));
dt.setUTCHours(Number(hours));
return `${pad(dt.getHours(), 2)}:${pad(dt.getMinutes(), 2)}`;
}
export function timeLocalToUTC(time: string) {
const [hours, minutes] = time.split(":");
const dt = new Date();
dt.setHours(Number(hours));
dt.setMinutes(Number(minutes));
return `${pad(dt.getUTCHours(), 2)}:${pad(dt.getUTCMinutes(), 2)}`;
}

View file

@ -5,10 +5,16 @@
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-webhooks.svg')"></v-img>
</template>
<template #title> Webhooks </template>
The webhooks defined below will be executed when a meal is defined for the day. At the scheduled time the webhooks
will be sent with the data from the recipe that is scheduled for the day
<v-card-text class="pb-0">
The webhooks defined below will be executed when a meal is defined for the day. At the scheduled time the
webhooks will be sent with the data from the recipe that is scheduled for the day. Note that webhook execution
is not exact. The webhooks are executed on a 5 minutes interval so the webhooks will be executed within 5 +/-
minutes of the scheduled.
</v-card-text>
</BasePageTitle>
<BannerExperimental />
<BaseButton create @click="actions.createOne()" />
<v-expansion-panels class="mt-2">
<v-expansion-panel v-for="(webhook, index) in webhooks" :key="index" class="my-2 left-border rounded">
@ -17,7 +23,7 @@
<v-icon large left :color="webhook.enabled ? 'info' : null">
{{ $globals.icons.webhook }}
</v-icon>
{{ webhook.name }} - {{ webhook.time }}
{{ webhook.name }} - {{ timeDisplay(timeUTCToLocal(webhook.scheduledTime)) }}
</div>
<template #actions>
<v-btn small icon class="ml-2">
@ -28,35 +34,12 @@
</template>
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-card-text>
<v-switch v-model="webhook.enabled" label="Enabled"></v-switch>
<v-text-field v-model="webhook.name" label="Webhook Name"></v-text-field>
<v-text-field v-model="webhook.url" label="Webhook Url"></v-text-field>
<v-time-picker v-model="webhook.time" class="elevation-2" ampm-in-title format="ampm"></v-time-picker>
</v-card-text>
<v-card-actions class="py-0 justify-end">
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.delete,
text: $t('general.delete'),
event: 'delete',
},
{
icon: $globals.icons.testTube,
text: $t('general.test'),
event: 'test',
},
{
icon: $globals.icons.save,
text: $t('general.save'),
event: 'save',
},
]"
@delete="actions.deleteOne(webhook.id)"
@save="actions.updateOne(webhook)"
/>
</v-card-actions>
<GroupWebhookEditor
:key="webhook.id"
:webhook="webhook"
@save="actions.updateOne($event)"
@delete="actions.deleteOne($event)"
/>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
@ -65,15 +48,28 @@
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { useGroupWebhooks } from "~/composables/use-group-webhooks";
import { useGroupWebhooks, timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
import GroupWebhookEditor from "~/components/Domain/Group/GroupWebhookEditor.vue";
export default defineComponent({
components: { GroupWebhookEditor },
setup() {
const { actions, webhooks } = useGroupWebhooks();
function timeDisplay(time: string): string {
// returns the time in the format HH:MM AM/PM
const [hours, minutes] = time.split(":");
const ampm = Number(hours) < 12 ? "AM" : "PM";
const hour = Number(hours) % 12 || 12;
const minute = minutes.padStart(2, "0");
return `${hour}:${minute} ${ampm}`;
}
return {
webhooks,
actions,
timeLocalToUTC,
timeUTCToLocal,
timeDisplay,
};
},
head() {

View file

@ -5,6 +5,7 @@
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export type WebhookType = "mealplan";
export type SupportedMigrations = "nextcloud" | "chowdown" | "paprika" | "mealie_alpha";
export interface CreateGroupPreferences {
@ -25,7 +26,8 @@ export interface CreateWebhook {
enabled?: boolean;
name?: string;
url?: string;
time?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
}
export interface DataMigrationCreate {
sourceType: SupportedMigrations;
@ -231,7 +233,8 @@ export interface ReadWebhook {
enabled?: boolean;
name?: string;
url?: string;
time?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
groupId: string;
id: string;
}
@ -304,7 +307,8 @@ export interface SaveWebhook {
enabled?: boolean;
name?: string;
url?: string;
time?: string;
webhookType?: WebhookType & string;
scheduledTime: string;
groupId: string;
}
export interface SeederConfig {

View file

@ -18,6 +18,7 @@ import DevDumpJson from "@/components/global/DevDumpJson.vue";
import LanguageDialog from "@/components/global/LanguageDialog.vue";
import InputQuantity from "@/components/global/InputQuantity.vue";
import ToggleState from "@/components/global/ToggleState.vue";
import ContextMenu from "@/components/global/ContextMenu.vue";
import AppButtonCopy from "@/components/global/AppButtonCopy.vue";
import CrudTable from "@/components/global/CrudTable.vue";
import InputColor from "@/components/global/InputColor.vue";
@ -55,6 +56,7 @@ declare module "vue" {
LanguageDialog: typeof LanguageDialog;
InputQuantity: typeof InputQuantity;
ToggleState: typeof ToggleState;
ContextMenu: typeof ContextMenu;
AppButtonCopy: typeof AppButtonCopy;
CrudTable: typeof CrudTable;
InputColor: typeof InputColor;