Browse Source

Merge pull request #1917 from nocodb/webhook-improvements

webhook improvements
pull/1966/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
8b90cda453
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/nc-gui/app.html
  2. 129
      packages/nc-gui/components/monaco/MonacoHandlebarEditor.js
  3. 3
      packages/nc-gui/components/monaco/index.js
  4. 8
      packages/nc-gui/components/project/spreadsheet/components/moreActions.vue
  5. 34
      packages/nc-gui/components/project/tableTabs/webhook/httpWebhook.vue
  6. 539
      packages/nc-gui/components/project/tableTabs/webhook/webhookEditor.vue
  7. 0
      packages/nc-gui/components/project/tableTabs/webhook/webhookEvent.vue
  8. 186
      packages/nc-gui/components/project/tableTabs/webhook/webhookList.vue
  9. 53
      packages/nc-gui/components/project/tableTabs/webhook/webhookModal.vue
  10. 35
      packages/nc-gui/components/project/tableTabs/webhook/webhooksTest.vue
  11. 423
      packages/nc-gui/components/project/tableTabs/webhooks.vue
  12. 26
      packages/nocodb/src/lib/noco-models/HookLog.ts
  13. 5
      packages/nocodb/src/lib/noco/meta/api/hookApis.ts
  14. 4
      packages/nocodb/src/lib/noco/meta/helpers/populateSamplePayload.ts
  15. 41
      packages/nocodb/src/lib/noco/meta/helpers/webhookHelpers.ts

1
packages/nc-gui/app.html

@ -5,6 +5,7 @@
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
<a href="https://nocodb.com" style="display:none">Created with NocoDB</a>
</body>
<script>
setTimeout(() => {

129
packages/nc-gui/components/monaco/MonacoHandlebarEditor.js

@ -0,0 +1,129 @@
/* eslint-disable */
// import assign from "nano-assign";
// import sqlAutoCompletions from "./sqlAutoCompletions";
// import {ext} from "vee-validate/dist/rules.esm";
export default {
name: "MonacoHandlebarEditor",
props: {
value: {
default: "",
type: String
},
theme: {
type: String,
default: "vs-dark"
},
},
model: {
event: "change"
},
watch: {
value(newVal) {
if (newVal !== this.editor.getValue()) {
if (typeof newVal === 'object') {
this.editor.setValue(JSON.stringify(newVal, 0, 2));
} else {
this.editor.setValue(newVal);
}
}
}
},
mounted() {
this.$nextTick(() => {
if (this.amdRequire) {
this.amdRequire(["vs/editor/editor.main"], () => {
this.initMonaco(window.monaco);
});
} else {
// ESM format so it can't be resolved by commonjs `require` in eslint
// eslint-disable import/no-unresolved
const monaco = require("monaco-editor");
// monaco.editor.defineTheme('monokai', require('./Cobalt.json'))
// monaco.editor.setTheme('monokai')
this.monaco = monaco;
// this.completionItemProvider = monaco.languages.registerCompletionItemProvider("sql", {
// async provideCompletionItems(model, position) {
// // console.log(sqlAutoCompletions(monaco).actions[0])
// console.log(model === vm.editor,model,vm.editor)
// return model === vm.editor.getModel() ? {suggestions: await vm.getLiveSuggestionsList(model, position)} : {};
// }
// });
this.initMonaco(monaco);
}
});
},
unmounted() {
},
beforeDestroy() {
this.editor && this.editor.dispose();
},
methods: {
initMonaco(monaco) {
const typescriptCode = this.value;
const model = monaco.editor.createModel(typescriptCode);
this.editor = monaco.editor.create(this.$el, {
model: model,
theme: this.theme
});
this.editor.onDidChangeModelContent(event => {
const value = this.editor.getValue();
if (this.value !== value) {
this.$emit("change", value, event);
}
});
},
getMonaco() {
return this.editor;
},
getMonacoModule() {
return this.monaco;
},
},
render(h) {
return h("div");
},
created() {
},
destroyed() {
}
};
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

3
packages/nc-gui/components/monaco/index.js

@ -1,10 +1,11 @@
import MonacoEditor from './MonacoEditor'
import MonacoJsonEditor from './MonacoJsonEditor'
import MonacoSingleLineEditor from './MonacoSingleLineEditor'
import MonacoHandlebarEditor from './MonacoHandlebarEditor'
export default MonacoEditor
export { MonacoJsonEditor, MonacoSingleLineEditor }
export { MonacoJsonEditor, MonacoSingleLineEditor, MonacoHandlebarEditor }
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.component(MonacoEditor.name, MonacoEditor)

8
packages/nc-gui/components/project/spreadsheet/components/moreActions.vue

@ -82,7 +82,7 @@
v-if="_isUIAllowed('webhook') && !isView"
v-t="['c:actions:webhook']"
dense
@click="$emit('webhook')"
@click="webhookModal = true"
>
<v-list-item-title>
<v-icon small class="mr-1" color="">
@ -104,6 +104,7 @@
:parsed-csv="parsedCsv"
@import="importData"
/>
<webhook-modal v-model="webhookModal" :meta="meta" />
</div>
</template>
@ -115,10 +116,12 @@ import DropOrSelectFileModal from '~/components/import/dropOrSelectFileModal'
import ColumnMappingModal from '~/components/project/spreadsheet/components/importExport/columnMappingModal'
import CSVTemplateAdapter from '~/components/import/templateParsers/CSVTemplateAdapter'
import { UITypes } from '~/components/project/spreadsheet/helpers/uiTypes'
import WebhookModal from '~/components/project/tableTabs/webhook/webhookModal'
export default {
name: 'ExportImport',
components: {
WebhookModal,
ColumnMappingModal,
DropOrSelectFileModal
},
@ -135,7 +138,8 @@ export default {
return {
importModal: false,
columnMappingModal: false,
parsedCsv: {}
parsedCsv: {},
webhookModal: false
}
},

34
packages/nc-gui/components/project/tableTabs/webhook/httpWebhook.vue

@ -23,6 +23,9 @@
class="req-tabs"
height="24"
>
<v-tab v-ge="['api-client','body']" class="caption">
<span class="text-capitalize">Body</span>
</v-tab>
<v-tab v-ge="['api-client','params']" class="caption">
<span class="text-capitalize"> Params&nbsp;<b
v-if="paramsCount"
@ -37,12 +40,19 @@
headersCount
}})</b></span>
</v-tab>
<v-tab v-ge="['api-client','body']" class="caption">
<span class="text-capitalize">Body</span>
</v-tab>
<v-tab v-ge="['api-client','auth']" class="caption">
<span class="text-capitalize">Auth</span>
</v-tab>
<v-tab-item>
<monaco-handlebar-editor
v-model="api.body"
style="height: 250px"
class="editor card text-left"
theme="vs-dark"
lang="json"
:options="{validate:true,documentFormattingEdits:true,foldingRanges:true}"
/>
</v-tab-item>
<v-tab-item>
<params
v-model="api.parameters"
@ -55,23 +65,13 @@
:env.sync="selectedEnv"
/>
</v-tab-item>
<v-tab-item>
<monaco-json-editor
v-model="api.body"
style="height: 250px"
class="editor card text-left"
theme="vs-dark"
lang="json"
:options="{validate:true,documentFormattingEdits:true,foldingRanges:true}"
/>
</v-tab-item>
<v-tab-item>
<monaco-json-editor
<monaco-handlebar-editor
v-model="api.auth"
style="height: 250px"
class="editor card text-left"
theme="vs-dark"
lang="json"
:options="{validate:true,documentFormattingEdits:true,foldingRanges:true}"
/>
<span class="caption grey--text">For more about auth option refer <a href="https://github.com/axios/axios#request-config" target="_blank">axios docs</a>.</span>
@ -84,7 +84,7 @@
import params from '../../../apiClient/params'
import headers from '../../../apiClient/headers'
import { MonacoJsonEditor } from '../../../monaco/index'
import { MonacoHandlebarEditor } from '../../../monaco/index'
export default {
tab: 0,
@ -92,7 +92,7 @@ export default {
components: {
params,
headers,
MonacoJsonEditor
MonacoHandlebarEditor
},
props: {
value: Object

539
packages/nc-gui/components/project/tableTabs/webhook/webhookEditor.vue

@ -0,0 +1,539 @@
<template>
<v-form v-if="hook" ref="form" v-model="valid" class="mx-4" lazy-validation>
<v-card-title>
<a class="pointer mr-1" @click="$emit('backToList')">
<v-icon>mdi-arrow-left-bold</v-icon>
</a>
<v-spacer />
{{ meta.title }} : {{ hook.title || 'Webhook' }}
<v-spacer />
<div style="width: 24px;height: 24px" />
</v-card-title>
<div class="mx-4 d-flex m-2">
<v-spacer />
<v-btn
outlined
tooltip="Save"
small
:disabled="loading || !valid || !hook.event"
@click.prevent="$refs.webhookTest.testWebhook()"
>
Test webhook
</v-btn>
<v-btn
outlined
tooltip="Save"
color="primary"
small
:disabled="loading || !valid || !hook.event"
@click.prevent="saveHooks"
>
<v-icon small left>
save
</v-icon>
<!-- Save -->
{{ $t("general.save") }}
</v-btn>
</div>
<v-card-text>
<v-text-field
v-model="hook.title"
class="caption"
outlined
dense
:label="$t('general.title')"
required
:rules="[(v) => !!v || `${$t('general.required')}`]"
/>
<webhook-event
:event.sync="hook.event"
:operation.sync="hook.operation"
/>
<v-card class="mb-8 nc-filter-wrapper">
<v-card-text>
<v-checkbox
v-model="hook.condition"
dense
hide-details
class="mt-1"
label="On Condition"
/>
<column-filter
v-if="hook.condition"
:key="key"
ref="filter"
v-model="filters"
:meta="meta"
:field-list="fieldList"
dense
style="max-width: 100%"
:hook-id="hook.id"
web-hook
/>
</v-card-text>
</v-card>
<v-select
v-model="hook.notification.type"
outlined
dense
:label="$t('general.notification')"
required
:items="notificationList"
:rules="[(v) => !!v || `${$t('general.required')}`]"
class="caption"
:prepend-inner-icon="notificationIcon[hook.notification.type]"
@change="onNotTypeChange"
>
<template #item="{ item }">
<v-list-item-icon>
<v-icon small>
{{ notificationIcon[item] }}
</v-icon>
</v-list-item-icon>
<v-list-item-title>
{{ item }}
</v-list-item-title>
</template>
</v-select>
<template v-if="hook.notification.type === 'URL'">
<http-webhook v-model="notification" />
</template>
<template v-if="hook.notification.type === 'Slack'">
<v-combobox
v-if="slackChannels"
v-model="notification.channels"
:rules="[(v) => !!v || `${$t('general.required')}`]"
:items="slackChannels"
item-text="channel"
label="Select Slack channels"
multiple
outlined
dense
class="caption"
/>
</template>
<template v-if="hook.notification.type === 'Microsoft Teams'">
<v-combobox
v-if="teamsChannels"
v-model="notification.channels"
:rules="[(v) => !!v || `${$t('general.required')}`]"
:items="teamsChannels"
item-text="channel"
label="Select Teams channels"
multiple
outlined
dense
class="caption"
/>
</template>
<template v-if="hook.notification.type === 'Discord'">
<v-combobox
v-if="discordChannels"
v-model="notification.channels"
:rules="[(v) => !!v || `${$t('general.required')}`]"
:items="discordChannels"
item-text="channel"
label="Select Discord channels"
multiple
outlined
dense
class="caption"
/>
</template>
<template v-if="hook.notification.type === 'Mattermost'">
<v-combobox
v-if="mattermostChannels"
v-model="notification.channels"
:rules="[(v) => !!v || `${$t('general.required')}`]"
:items="mattermostChannels"
item-text="channel"
label="Select Mattermost channels"
multiple
outlined
dense
class="caption"
/>
</template>
<template v-if="inputs[hook.notification.type] && notification">
<template v-for="input in inputs[hook.notification.type]">
<v-textarea
v-if="input.type === 'LongText'"
:key="input.key"
v-model="notification[input.key]"
class="caption"
dense
outlined
:label="input.label"
:rules="[
(v) =>
!input.required || !!v || `${$t('general.required')}`,
]"
/>
<v-text-field
v-else
:key="input.key"
v-model="notification[input.key]"
class="caption"
dense
outlined
:label="input.label"
:rules="[
(v) =>
!input.required || !!v || `${$t('general.required')}`,
]"
/>
</template>
</template>
</v-card-text>
<v-card-text>
<span class="caption grey--text">
<em>Available context variables are
<strong>data and user</strong></em>
<v-tooltip top>
<template #activator="{ on }">
<v-icon
small
color="grey"
class="ml-2"
v-on="on"
>mdi-information</v-icon>
</template>
<span class="caption">
<strong>data</strong> : Row data <br>
<strong>user</strong> : User information<br>
</span>
</v-tooltip>
<br>
<a
href="https://docs.nocodb.com/developer-resources/webhooks/"
>
<!--Document Reference-->
{{ $t("labels.docReference") }}
</a>
</span>
<webhooks-test
ref="webhookTest"
class="mt-3"
:model-id="meta.id"
hide-test-btn
:hook="{
...hook,
filters,
notification: {
...hook.notification,
payload: notification,
},
}"
/>
</v-card-text>
</v-form>
<span v-else />
</template>
<script>
import WebhooksTest from '~/components/project/tableTabs/webhook/webhooksTest'
import WebhookEvent from '~/components/project/tableTabs/webhook/webhookEvent'
import HttpWebhook from '~/components/project/tableTabs/webhook/httpWebhook'
import ColumnFilter from '~/components/project/spreadsheet/components/columnFilter'
export default {
name: 'WebhookEditor',
components: { ColumnFilter, HttpWebhook, WebhookEvent, WebhooksTest },
props: {
meta: Object
},
data: () => ({
notification: {},
hook: {
notification: {
type: 'URL'
}
},
valid: false,
apps: {},
slackChannels: null,
teamsChannels: null,
discordChannels: null,
mattermostChannels: null,
enableCondition: false,
notificationList: [
'URL',
'Email',
'Slack',
'Microsoft Teams',
'Discord',
'Mattermost',
'Twilio',
'Whatsapp Twilio'
],
filters: [],
notificationIcon: {
URL: 'mdi-link',
Email: 'mdi-email',
Slack: 'mdi-slack',
'Microsoft Teams': 'mdi-microsoft-teams',
Discord: 'mdi-discord',
Mattermost: 'mdi-chat',
'Whatsapp Twilio': 'mdi-whatsapp',
Twilio: 'mdi-cellphone-message'
},
inputs: {
Email: [
{
key: 'to',
label: 'To Address',
placeholder: 'To Address',
type: 'SingleLineText',
required: true
},
{
key: 'subject',
label: 'Subject',
placeholder: 'Subject',
type: 'SingleLineText',
required: true
},
{
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
Slack: [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
'Microsoft Teams': [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
Discord: [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
Mattermost: [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
Twilio: [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
},
{
key: 'to',
label: 'Comma separated Mobile #',
placeholder: 'Comma separated Mobile #',
type: 'LongText',
required: true
}
],
'Whatsapp Twilio': [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
},
{
key: 'to',
label: 'Comma separated Mobile #',
placeholder: 'Comma separated Mobile #',
type: 'LongText',
required: true
}
]
}
}),
created() {
this.loadPluginList()
},
methods: {
async loadPluginList() {
try {
// const plugins = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'xcPluginList'])
const plugins = (await this.$api.plugin.list()).list
// plugins.push(...plugins.splice(0, 3))
this.apps = plugins.reduce((o, p) => {
p.tags = p.tags ? p.tags.split(',') : []
p.parsedInput = p.input && JSON.parse(p.input)
o[p.title] = p
return o
}, {})
} catch (e) {}
},
addNewHook() {
this.onEventChange()
this.$refs.form.resetValidation()
},
async onNotTypeChange() {
this.notification = {}
if (this.hook.notification.type === 'Slack') {
this.slackChannels =
(this.apps && this.apps.Slack && this.apps.Slack.parsedInput) || []
}
if (this.hook.notification.type === 'Microsoft Teams') {
this.teamsChannels =
(this.apps &&
this.apps['Microsoft Teams'] &&
this.apps['Microsoft Teams'].parsedInput) ||
[]
}
if (this.hook.notification.type === 'Discord') {
this.discordChannels =
(this.apps && this.apps.Discord && this.apps.Discord.parsedInput) ||
[]
}
if (this.hook.notification.type === 'Mattermost') {
this.mattermostChannels =
(this.apps &&
this.apps.Mattermost &&
this.apps.Mattermost.parsedInput) ||
[]
}
if (this.hook.notification.type === 'URL') {
this.notification = this.notification || {}
this.$set(this.notification, 'body', '{{ json data }}')
}
this.$nextTick(() => this.$refs.form.validate())
},
async onEventChange() {
const { notification: { payload, type } = {}, ...hook } = this.hook
this.hook = {
...hook,
notification: {
type
}
}
// this.enableCondition = !!(this.hook && this.hook.condition && Object.keys(this.hook.condition).length)
await this.onNotTypeChange()
this.notification = payload
if (this.hook.notification.type === 'Slack') {
this.notification.webhook_url =
this.notification.webhook_url &&
this.notification.webhook_url.map(v =>
this.slackChannels.find(s => v.webhook_url === s.webhook_url)
)
}
if (this.hook.notification.type === 'Microsoft Teams') {
this.notification.webhook_url =
this.notification.webhook_url &&
this.notification.webhook_url.map(v =>
this.teamsChannels.find(s => v.webhook_url === s.webhook_url)
)
}
if (this.hook.notification.type === 'Discord') {
this.notification.webhook_url =
this.notification.webhook_url &&
this.notification.webhook_url.map(v =>
this.discordChannels.find(s => v.webhook_url === s.webhook_url)
)
}
if (this.hook.notification.type === 'Mattermost') {
this.notification.webhook_url =
this.notification.webhook_url &&
this.notification.webhook_url.map(v =>
this.mattermostChannels.find(s => v.webhook_url === s.webhook_url)
)
}
if (this.hook.notification.type === 'URL') {
this.notification = this.notification || {}
this.$set(this.notification, 'body', this.notification.body || '{{ json data }}')
}
},
async saveHooks() {
if (!this.$refs.form.validate() || !this.valid || !this.hook.event) {
return
}
this.loading = true
try {
let res
if (this.hook.id) {
res = await this.$api.dbTableWebhook.update(this.hook.id, {
...this.hook,
notification: {
...this.hook.notification,
payload: this.notification
}
})
} else {
res = await this.$api.dbTableWebhook.create(this.meta.id, {
...this.hook,
notification: {
...this.hook.notification,
payload: this.notification
}
})
}
if (!this.hook.id && res) {
this.hook.id = res.id
}
if (this.$refs.filter) {
await this.$refs.filter.applyChanges(false, {
hookId: this.hook.id
})
}
this.$toast
.success('Webhook details updated successfully')
.goAway(3000)
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
this.loading = false
this.$e('a:webhook:add', {
operation: this.hook.operation,
condition: this.hook.condition,
notification: this.hook.notification.type
})
}
}
}
</script>
<style scoped>
/deep/ .nc-filter-wrapper label {
font-size: 0.75rem !important;
}
</style>

0
packages/nc-gui/components/project/tableTabs/webhookEvent.vue → packages/nc-gui/components/project/tableTabs/webhook/webhookEvent.vue

186
packages/nc-gui/components/project/tableTabs/webhook/webhookList.vue

@ -0,0 +1,186 @@
<template>
<div>
<v-card-title>
Webhooks
<v-spacer />
<v-btn
outlined
tooltip="Save"
small
@click.prevent="$emit('add')"
>
Create webhook
</v-btn>
</v-card-title>
<div v-if="hooks " class="pa-4">
<template v-if=" hooks.length">
<v-card v-for="(hook,i) in hooks" :key="hook.id" class="elevation-0 backgroundColor nc-hook" @click="$emit('edit', hook)">
<div class="pa-4 ">
<h4 class="nc-text">
{{ hook.title }}
</h4>
<div class="d-flex">
<!--Title-->
<span class="caption textColor1--text">{{ $t("general.event") }} : {{ hook.event }} {{
hook.operation
}}</span>
<v-spacer />
<!--Notify Via-->
<span class="caption textColor1--text">{{
$t("labels.notifyVia")
}} : {{ hook.notification && hook.notification.type }}
</span>
</div>
</div>
<v-icon class="nc-hook-delete-icon" small @click.stop="deleteHook(hook,i)">
mdi-delete-outline
</v-icon>
</v-card>
</template>
<div v-else class="pa-4 backgroundColor caption textColor--text text--lighten-3">
Webhooks list is empty, create new webhook by clicking 'Create webhook' button.
</div>
</div>
<!-- <v-simple-table dense>
<template #default>
<thead>
<tr>
<th>
&lt;!&ndash;Title&ndash;&gt;
{{ $t("general.title") }}
</th>
<th>
&lt;!&ndash;Event&ndash;&gt;
{{ $t("general.event") }}
</th>
<th>
&lt;!&ndash;Condition&ndash;&gt;
{{ $t("general.condition") }}
</th>
<th>
&lt;!&ndash;Notify Via&ndash;&gt;
{{ $t("labels.notifyVia") }}
</th>
<th>
&lt;!&ndash;Action&ndash;&gt;
{{ $t("labels.action") }}
</th>
</tr>
</thead>
<tbody>
<template v-if="hooks && hooks.length">
<tr v-for="(item, i) in hooks" :key="i">
<td>{{ item.title }}</td>
<td>{{ item.event }} {{ item.operation }}</td>
<td>
<v-icon v-if="item.condition" color="success" small>
mdi-check-bold
</v-icon>
</td>
<td>
{{ item.notification && item.notification.type }}
</td>
<td>
<x-icon
small
color="error"
@click.stop="deleteHook(item, i)"
>
mdi-delete
</x-icon>
&lt;!&ndash; <x-icon small :color="loading || !valid || !hook.event ? 'grey' : 'primary'"
@click.stop="(!loading && valid && hook.event) && saveHooks()">save
</x-icon>&ndash;&gt;
</td>
</tr>
</template>
<tr>
<td colspan="6" class="text-center py-5">
&lt;!&ndash;:tooltip="$t('tooltip.saveChanges')"&ndash;&gt;
<x-btn
v-ge="['hooks', 'add new']"
outlined
color="primary"
small
@click.prevent="$emit('add')"
>
<v-icon small left>
mdi-plus
</v-icon>
&lt;!&ndash;Add New Webhook&ndash;&gt;
{{ $t("activity.addWebhook") }}
</x-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>-->
</div>
</template>
<script>
export default {
name: 'WebhookList',
props: { meta: Object },
data: () => ({
hooks: null, loading: false
}),
mounted() {
this.loadHooksList()
},
methods: {
async loadHooksList() {
this.key++
this.loading = true
const hooks = await this.$api.dbTableWebhook.list(this.meta.id)
this.hooks = hooks.list.map((h) => {
h.notification = h.notification && JSON.parse(h.notification)
return h
})
this.loading = false
},
async deleteHook(item, i) {
try {
if (item.id) {
await this.$api.dbTableWebhook.delete(item.id)
this.hooks.splice(i, 1)
} else {
this.hooks.splice(i, 1)
}
this.$toast.success('Hook deleted successfully').goAway(3000)
if (!this.hooks.length) {
this.hook = null
}
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
this.$e('a:webhook:delete')
}
}
}
</script>
<style scoped lang="scss">
.nc-hook {
position: relative;
.nc-hook-delete-icon {
position: absolute;
opacity: 0;
transition: .3s opacity;
right: 16px;
top: 16px
}
&:hover .nc-hook-delete-icon {
opacity: 1;
}
}
</style>

53
packages/nc-gui/components/project/tableTabs/webhook/webhookModal.vue

@ -0,0 +1,53 @@
<template>
<v-dialog v-model="webhookModal" width="min(700px,90%)" overlay-opacity=".9">
<v-card
v-if="webhookModal"
width="100%"
min-height="350px"
class="pa-4"
>
<webhook-editor v-if="editOrAdd" ref="editor" :meta="meta" @backToList="editOrAdd = false" />
<webhook-list v-else :meta="meta" @edit="editHook" @add="editOrAdd = true" />
</v-card>
</v-dialog>
</template>
<script>
import WebhookList from '~/components/project/tableTabs/webhook/webhookList'
import WebhookEditor from '~/components/project/tableTabs/webhook/webhookEditor'
export default {
name: 'WebhookModal',
components: { WebhookEditor, WebhookList },
props: {
meta: Object,
value: Boolean
},
data: () => ({
editOrAdd: false,
activePage: 'role'
}),
computed: {
webhookModal: {
get() {
return this.value
},
set(v) {
this.$emit('input', v)
}
}
},
methods: {
editHook(hook) {
this.editOrAdd = true
this.$nextTick(() => {
this.$refs.editor.hook = { ...hook }
this.$refs.editor.onEventChange()
})
}
}
}
</script>
<style scoped lang="scss">
</style>

35
packages/nc-gui/components/project/tableTabs/webhooksTest.vue → packages/nc-gui/components/project/tableTabs/webhook/webhooksTest.vue

@ -1,8 +1,14 @@
<template>
<div>
<h5>Sample payload</h5>
<monaco-json-object-editor v-model="sampleData" read-only style="min-height: 300px" class="caption mb-2" />
<v-btn small @click="testWebhook">
<h5 @click="isVisible=!isVisible">
Sample payload <v-icon x-small>
mdi-chevron-{{ isVisible? 'up' : 'down' }}
</v-icon>
</h5>
<div :class="{active:isVisible}" class="nc-sample-data">
<monaco-json-object-editor v-model="sampleData" read-only style="min-height: 300px" class="caption mb-2 " />
</div>
<v-btn v-if="!hideTestBtn" small @click="testWebhook">
Test webhook
</v-btn>
</div>
@ -16,10 +22,12 @@ export default {
components: { MonacoJsonObjectEditor },
props: {
modelId: String,
hook: Object
hook: Object,
hideTestBtn: Boolean
},
data: () => ({
sampleData: null
sampleData: null,
isVisible: false
}),
watch: {
async 'hook.operation'() {
@ -38,15 +46,15 @@ export default {
},
async testWebhook() {
try {
const res = await this.$api.dbTableWebhook.test(this.modelId, {
await this.$api.dbTableWebhook.test(this.modelId, {
hook: this.hook,
payload: this.sampleData
})
this.$toast.success('Webhook tested successfully').goAway(3000)
} catch (_e) {
const e = await this._extractSdkResponseError(_e)
this.$toast.error(e.message).goAway(3000)
} catch (e) {
const msg = await this._extractSdkResponseErrorMsg(e)
this.$toast.error(msg).goAway(3000)
}
}
}
@ -61,6 +69,15 @@ export default {
/deep/ label {
font-size: 0.75rem !important
}
.nc-sample-data{
overflow-y: hidden;
height:0;
transition: .3s height;
}
.nc-sample-data.active{
height:300px
}
</style>
<!--
/**

423
packages/nc-gui/components/project/tableTabs/webhooks.vue

@ -24,7 +24,9 @@
small
>
<template #divider>
<v-icon small color="grey lighten-2"> forward </v-icon>
<v-icon small color="grey lighten-2">
forward
</v-icon>
</template>
</v-breadcrumbs>
</v-toolbar-title>
@ -32,7 +34,9 @@
<!--tooltip="Close webhooks modal"-->
<x-btn outlined small @click.prevent="$emit('close')">
<v-icon small left> mdi-close-circle-outline </v-icon>
<v-icon small left>
mdi-close-circle-outline
</v-icon>
<!-- Close -->
{{ $t("general.close") }}
</x-btn>
@ -45,7 +49,9 @@
small
@click.prevent="loadHooksList"
>
<v-icon small left> mdi-reload </v-icon>
<v-icon small left>
mdi-reload
</v-icon>
<!-- Reload -->
{{ $t("general.reload") }}
</x-btn>
@ -58,7 +64,9 @@
small
@click.prevent="addNewHook"
>
<v-icon small left> mdi-plus </v-icon>
<v-icon small left>
mdi-plus
</v-icon>
<!--Add New-->
{{ $t("activity.addWebhook") }}
</x-btn>
@ -149,7 +157,9 @@
small
@click.prevent="addNewHook"
>
<v-icon small left> mdi-plus </v-icon>
<v-icon small left>
mdi-plus
</v-icon>
<!--Add New Webhook-->
{{ $t("activity.addWebhook") }}
</x-btn>
@ -175,7 +185,9 @@
:disabled="loading || !valid || !hook.event"
@click.prevent="saveHooks"
>
<v-icon small left> save </v-icon>
<v-icon small left>
save
</v-icon>
<!-- Save -->
{{ $t("general.save") }}
</x-btn>
@ -341,22 +353,23 @@
<v-card-text>
<span class="caption grey--text">
<em
>Available context variables are
<strong>data and user</strong></em
>
<em>Available context variables are
<strong>data and user</strong></em>
<v-tooltip top>
<template #activator="{ on }">
<v-icon small color="grey" class="ml-2" v-on="on"
>mdi-information</v-icon
>
<v-icon
small
color="grey"
class="ml-2"
v-on="on"
>mdi-information</v-icon>
</template>
<span class="caption">
<strong>data</strong> : Row data <br />
<strong>user</strong> : User information<br />
<strong>data</strong> : Row data <br>
<strong>user</strong> : User information<br>
</span>
</v-tooltip>
<br />
<br>
<a
href="https://docs.nocodb.com/developer-resources/webhooks/"
>
@ -386,22 +399,22 @@
</template>
<script>
import HttpWebhook from "./webhook/httpWebhook";
import ColumnFilter from "~/components/project/spreadsheet/components/columnFilter";
import HttpWebhook from './webhook/httpWebhook'
import ColumnFilter from '~/components/project/spreadsheet/components/columnFilter'
// import FormInput from '~/components/project/appStore/FormInput'
import WebhookEvent from "~/components/project/tableTabs/webhookEvent";
import WebhooksTest from "~/components/project/tableTabs/webhooksTest";
import WebhookEvent from '~/components/project/tableTabs/webhook/webhookEvent'
import WebhooksTest from '~/components/project/tableTabs/webhook/webhooksTest'
export default {
name: "Webhooks",
name: 'Webhooks',
components: {
WebhooksTest,
HttpWebhook,
WebhookEvent,
// FormInput,
ColumnFilter,
ColumnFilter
},
props: ["nodes"],
props: ['nodes'],
data: () => ({
key: 0,
apps: {},
@ -416,147 +429,147 @@ export default {
meta: null,
loading: false,
notificationList: [
"Email",
"Slack",
"Microsoft Teams",
"Discord",
"Mattermost",
"Twilio",
"Whatsapp Twilio",
"URL",
'URL',
'Email',
'Slack',
'Microsoft Teams',
'Discord',
'Mattermost',
'Twilio',
'Whatsapp Twilio'
],
filters: [],
hook: null,
notification: {},
notificationIcon: {
URL: "mdi-link",
Email: "mdi-email",
Slack: "mdi-slack",
"Microsoft Teams": "mdi-microsoft-teams",
Discord: "mdi-discord",
Mattermost: "mdi-chat",
"Whatsapp Twilio": "mdi-whatsapp",
Twilio: "mdi-cellphone-message",
URL: 'mdi-link',
Email: 'mdi-email',
Slack: 'mdi-slack',
'Microsoft Teams': 'mdi-microsoft-teams',
Discord: 'mdi-discord',
Mattermost: 'mdi-chat',
'Whatsapp Twilio': 'mdi-whatsapp',
Twilio: 'mdi-cellphone-message'
},
urlRules: [
(v) =>
!v || !v.trim() || /^https?:\/\/.{1,}/.test(v) || "Not a valid URL",
v =>
!v || !v.trim() || /^https?:\/\/.{1,}/.test(v) || 'Not a valid URL'
],
fieldList: [],
inputs: {
Email: [
{
key: "to",
label: "To Address",
placeholder: "To Address",
type: "SingleLineText",
required: true,
key: 'to',
label: 'To Address',
placeholder: 'To Address',
type: 'SingleLineText',
required: true
},
{
key: "subject",
label: "Subject",
placeholder: "Subject",
type: "SingleLineText",
required: true,
key: 'subject',
label: 'Subject',
placeholder: 'Subject',
type: 'SingleLineText',
required: true
},
{
key: "body",
label: "Body",
placeholder: "Body",
type: "LongText",
required: true,
},
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
Slack: [
{
key: "body",
label: "Body",
placeholder: "Body",
type: "LongText",
required: true,
},
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
"Microsoft Teams": [
'Microsoft Teams': [
{
key: "body",
label: "Body",
placeholder: "Body",
type: "LongText",
required: true,
},
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
Discord: [
{
key: "body",
label: "Body",
placeholder: "Body",
type: "LongText",
required: true,
},
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
Mattermost: [
{
key: "body",
label: "Body",
placeholder: "Body",
type: "LongText",
required: true,
},
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
}
],
Twilio: [
{
key: "body",
label: "Body",
placeholder: "Body",
type: "LongText",
required: true,
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
},
{
key: "to",
label: "Comma separated Mobile #",
placeholder: "Comma separated Mobile #",
type: "LongText",
required: true,
},
key: 'to',
label: 'Comma separated Mobile #',
placeholder: 'Comma separated Mobile #',
type: 'LongText',
required: true
}
],
"Whatsapp Twilio": [
'Whatsapp Twilio': [
{
key: "body",
label: "Body",
placeholder: "Body",
type: "LongText",
required: true,
key: 'body',
label: 'Body',
placeholder: 'Body',
type: 'LongText',
required: true
},
{
key: "to",
label: "Comma separated Mobile #",
placeholder: "Comma separated Mobile #",
type: "LongText",
required: true,
},
],
},
key: 'to',
label: 'Comma separated Mobile #',
placeholder: 'Comma separated Mobile #',
type: 'LongText',
required: true
}
]
}
}),
async created() {
await this.loadMeta();
await this.loadHooksList();
await this.loadMeta()
await this.loadHooksList()
// todo: load only necessary plugins
await this.loadPluginList();
this.selectedHook = 0;
this.onEventChange();
await this.loadPluginList()
this.selectedHook = 0
this.onEventChange()
},
methods: {
async loadPluginList() {
try {
// const plugins = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'xcPluginList'])
const plugins = (await this.$api.plugin.list()).list;
const plugins = (await this.$api.plugin.list()).list
// plugins.push(...plugins.splice(0, 3))
this.apps = plugins.reduce((o, p) => {
p.tags = p.tags ? p.tags.split(",") : [];
p.parsedInput = p.input && JSON.parse(p.input);
o[p.title] = p;
return o;
}, {});
p.tags = p.tags ? p.tags.split(',') : []
p.parsedInput = p.input && JSON.parse(p.input)
o[p.title] = p
return o
}, {})
} catch (e) {}
},
checkConditionAvail() {
@ -567,36 +580,36 @@ export default {
// this.hook.condition = []
},
async onNotTypeChange() {
this.notification = {};
if (this.hook.notification.type === "Slack") {
this.notification = {}
if (this.hook.notification.type === 'Slack') {
// const plugin = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'xcPluginRead', {
// title: 'Slack'
// }])
// this.slackChannels = JSON.parse(plugin.input) || []
this.slackChannels =
(this.apps && this.apps.Slack && this.apps.Slack.parsedInput) || [];
(this.apps && this.apps.Slack && this.apps.Slack.parsedInput) || []
}
if (this.hook.notification.type === "Microsoft Teams") {
if (this.hook.notification.type === 'Microsoft Teams') {
// const plugin = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'xcPluginRead', {
// title: 'Microsoft Teams'
// }])
// this.teamsChannels = JSON.parse(plugin.input) || []
this.teamsChannels =
(this.apps &&
this.apps["Microsoft Teams"] &&
this.apps["Microsoft Teams"].parsedInput) ||
[];
this.apps['Microsoft Teams'] &&
this.apps['Microsoft Teams'].parsedInput) ||
[]
}
if (this.hook.notification.type === "Discord") {
if (this.hook.notification.type === 'Discord') {
// const plugin = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'xcPluginRead', {
// title: 'Discord'
// }])
this.discordChannels =
(this.apps && this.apps.Discord && this.apps.Discord.parsedInput) ||
[];
[]
}
if (this.hook.notification.type === "Mattermost") {
if (this.hook.notification.type === 'Mattermost') {
// const plugin = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'xcPluginRead', {
// title: 'Mattermost'
// }])
@ -605,64 +618,64 @@ export default {
(this.apps &&
this.apps.Mattermost &&
this.apps.Mattermost.parsedInput) ||
[];
[]
}
},
async onEventChange() {
this.key++;
this.key++
if (!this.hooks || !this.hooks.length) {
return;
return
}
const { notification: { payload, type } = {}, ...hook } =
this.hooks[this.selectedHook] || {};
this.hooks[this.selectedHook] || {}
this.hook = {
...hook,
notification: {
type,
},
};
type
}
}
// this.enableCondition = !!(this.hook && this.hook.condition && Object.keys(this.hook.condition).length)
await this.onNotTypeChange();
this.notification = payload;
if (this.hook.notification.type === "Slack") {
await this.onNotTypeChange()
this.notification = payload
if (this.hook.notification.type === 'Slack') {
this.notification.webhook_url =
this.notification.webhook_url &&
this.notification.webhook_url.map((v) =>
this.slackChannels.find((s) => v.webhook_url === s.webhook_url)
);
this.notification.webhook_url.map(v =>
this.slackChannels.find(s => v.webhook_url === s.webhook_url)
)
}
if (this.hook.notification.type === "Microsoft Teams") {
if (this.hook.notification.type === 'Microsoft Teams') {
this.notification.webhook_url =
this.notification.webhook_url &&
this.notification.webhook_url.map((v) =>
this.teamsChannels.find((s) => v.webhook_url === s.webhook_url)
);
this.notification.webhook_url.map(v =>
this.teamsChannels.find(s => v.webhook_url === s.webhook_url)
)
}
if (this.hook.notification.type === "Discord") {
if (this.hook.notification.type === 'Discord') {
this.notification.webhook_url =
this.notification.webhook_url &&
this.notification.webhook_url.map((v) =>
this.discordChannels.find((s) => v.webhook_url === s.webhook_url)
);
this.notification.webhook_url.map(v =>
this.discordChannels.find(s => v.webhook_url === s.webhook_url)
)
}
if (this.hook.notification.type === "Mattermost") {
if (this.hook.notification.type === 'Mattermost') {
this.notification.webhook_url =
this.notification.webhook_url &&
this.notification.webhook_url.map((v) =>
this.mattermostChannels.find((s) => v.webhook_url === s.webhook_url)
);
this.notification.webhook_url.map(v =>
this.mattermostChannels.find(s => v.webhook_url === s.webhook_url)
)
}
if (this.hook.notification.type === "URL") {
if (this.hook.notification.type === 'URL') {
// eslint-disable-next-line no-self-assign
this.notification.api = this.notification.api;
this.notification.api = this.notification.api
}
},
async saveHooks() {
if (!this.$refs.form.validate() || !this.valid || !this.hook.event) {
return;
return
}
this.loading = true;
this.loading = true
try {
// const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [
// {
@ -679,66 +692,66 @@ export default {
// }
// }
// ])
let res;
let res
if (this.hook.id) {
res = await this.$api.dbTableWebhook.update(this.hook.id, {
...this.hook,
notification: {
...this.hook.notification,
payload: this.notification,
},
});
payload: this.notification
}
})
} else {
res = await this.$api.dbTableWebhook.create(this.meta.id, {
...this.hook,
notification: {
...this.hook.notification,
payload: this.notification,
},
});
payload: this.notification
}
})
}
if (!this.hook.id && res) {
this.hook.id = res.id;
this.hook.id = res.id
}
if (this.$refs.filter) {
await this.$refs.filter.applyChanges(false, {
hookId: this.hook.id,
});
hookId: this.hook.id
})
}
this.$toast
.success("Webhook details updated successfully")
.goAway(3000);
.success('Webhook details updated successfully')
.goAway(3000)
} catch (e) {
this.$toast.error(e.message).goAway(3000);
this.$toast.error(e.message).goAway(3000)
}
this.loading = false;
await this.loadHooksList();
this.loading = false
await this.loadHooksList()
this.$e("a:webhook:add", {
this.$e('a:webhook:add', {
operation: this.hook.operation,
condition: this.hook.condition,
notification: this.hook.notification.type,
});
notification: this.hook.notification.type
})
},
async loadMeta() {
this.loadingMeta = true;
this.loadingMeta = true
// const tableMeta = await this.$store.dispatch('sqlMgr/ActSqlOp', [{
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
// }, 'tableXcModelGet', {
// tn: this.nodes.table_name
// }] )
this.meta = await this.$store.dispatch("meta/ActLoadMeta", {
table_name: this.nodes.table_name,
}); // JSON.parse(tableMeta.meta)
this.fieldList = this.meta.columns.map((c) => c.column_name);
this.loadingMeta = false;
this.meta = await this.$store.dispatch('meta/ActLoadMeta', {
table_name: this.nodes.table_name
}) // JSON.parse(tableMeta.meta)
this.fieldList = this.meta.columns.map(c => c.column_name)
this.loadingMeta = false
},
async loadHooksList() {
this.key++;
this.loading = true;
this.key++
this.loading = true
// const hooks = await this.$store.dispatch('sqlMgr/ActSqlOp', [{
// env: this.nodes.env,
// dbAlias: this.nodes.dbAlias
@ -746,49 +759,49 @@ export default {
// tn: this.nodes.table_name
// }])
const hooks = await this.$api.dbTableWebhook.list(this.meta.id);
const hooks = await this.$api.dbTableWebhook.list(this.meta.id)
this.hooks = hooks.list.map((h) => {
h.notification = h.notification && JSON.parse(h.notification);
h.notification = h.notification && JSON.parse(h.notification)
// h.condition = h.condition && JSON.parse(h.condition)
return h;
});
this.loading = false;
return h
})
this.loading = false
},
addNewHook() {
this.key++;
this.selectedHook = this.hooks.length;
this.key++
this.selectedHook = this.hooks.length
this.hooks.push({
notification: {
// type:'Email'
},
});
this.onEventChange();
this.$refs.form.resetValidation();
}
})
this.onEventChange()
this.$refs.form.resetValidation()
this.$e("c:webhook:add", { count: this.hooks.length });
this.$e('c:webhook:add', { count: this.hooks.length })
},
async deleteHook(item, i) {
try {
if (item.id) {
await this.$api.dbTableWebhook.delete(item.id);
this.hooks.splice(i, 1);
await this.$api.dbTableWebhook.delete(item.id)
this.hooks.splice(i, 1)
} else {
this.hooks.splice(i, 1);
this.hooks.splice(i, 1)
}
this.$toast.success("Hook deleted successfully").goAway(3000);
this.$toast.success('Hook deleted successfully').goAway(3000)
if (!this.hooks.length) {
this.hook = null;
this.hook = null
}
} catch (e) {
this.$toast.error(e.message).goAway(3000);
this.$toast.error(e.message).goAway(3000)
}
this.$e("a:webhook:delete");
},
},
};
this.$e('a:webhook:delete')
}
}
}
</script>
<style scoped>

26
packages/nocodb/src/lib/noco-models/HookLog.ts

@ -66,14 +66,14 @@ export default class HookLog implements HookLogType {
public static async insert(
hookLog: Partial<
(HookLog | HookLogType) & {
HookLog & {
created_at?;
updated_at?;
}
>,
ncMeta = Noco.ncMeta
) {
const insertObj = extractProps(hookLog, [
const insertObj: any = extractProps(hookLog, [
'base_id',
'project_id',
'fk_hook_id',
@ -98,24 +98,12 @@ export default class HookLog implements HookLogType {
insertObj.base_id = hook.base_id;
}
return await ncMeta.metaInsert2(null, null, MetaTable.HOOK_LOGS, insertObj);
if (typeof insertObj.notification === 'object') {
insertObj.notification = JSON.stringify(insertObj.notification);
}
// todo: redis cache ??
// await NocoCache.appendToList(
// CacheScope.HOOK,
// [insertObj.fk_mo_id],
// `${CacheScope.HOOK}:${id}`
// );
insertObj.execution_time = parseInt(insertObj.execution_time) || 0;
// return this.get(id, ncMeta);
return await ncMeta.metaInsert2(null, null, MetaTable.HOOK_LOGS, insertObj);
}
// static async delete(hookId: any, ncMeta = Noco.ncMeta) {
// await NocoCache.deepDel(
// CacheScope.HOOK,
// `${CacheScope.HOOK}:${hookId}`,
// CacheDelDirection.CHILD_TO_PARENT
// );
// return await ncMeta.metaDelete(null, null, MetaTable.HOOKS, hookId);
// }
}

5
packages/nocodb/src/lib/noco/meta/api/hookApis.ts

@ -61,7 +61,8 @@ export async function hookTest(req: Request<any, any>, res: Response) {
model,
data,
user,
(hook as any)?.filters
(hook as any)?.filters,
true
);
Tele.emit('evt', { evt_type: 'webhooks:tested' });
@ -72,7 +73,7 @@ export async function tableSampleData(req: Request, res: Response) {
const model = await Model.getByIdOrName({ id: req.params.tableId });
res // todo: pagination
.json(await populateSamplePayload(model, true, req.params.operation));
.json(await populateSamplePayload(model, false, req.params.operation));
}
const router = Router({ mergeParams: true });

4
packages/nocodb/src/lib/noco/meta/helpers/populateSamplePayload.ts

@ -33,6 +33,7 @@ export default async function populateSamplePayload(
[UITypes.LinkToAnotherRecord, UITypes.Lookup].includes(column.uidt)
)
continue;
if (operation === 'delete' && model.primaryKey?.title !== column.title)
continue;
@ -55,6 +56,9 @@ async function getSampleColumnValue(column: Column): Promise<any> {
const sampleVal = await populateSamplePayload(
await colOpt.getRelatedTable()
);
if (colOpt.type !== RelationTypes.BELONGS_TO) {
return undefined;
}
return colOpt.type === RelationTypes.BELONGS_TO
? sampleVal
: [sampleVal];

41
packages/nocodb/src/lib/noco/meta/helpers/webhookHelpers.ts

@ -112,26 +112,26 @@ export async function validateCondition(filters: Filter[], data: any) {
return isValid;
}
export async function handleHttpWebHook(apiMeta, apiReq, data) {
export async function handleHttpWebHook(apiMeta, user, data) {
// try {
const req = axiosRequestMake(apiMeta, apiReq, data);
const req = axiosRequestMake(apiMeta, user, data);
await require('axios')(req);
// } catch (e) {
// console.log(e);
// }
}
export function axiosRequestMake(_apiMeta, apiReq, data) {
export function axiosRequestMake(_apiMeta, user, data) {
const apiMeta = { ..._apiMeta };
if (apiMeta.body) {
try {
apiMeta.body = JSON.parse(apiMeta.body, (_key, value) => {
return typeof value === 'string'
? parseBody(value, apiReq, data, apiMeta)
? parseBody(value, user, data, apiMeta)
: value;
});
} catch (e) {
apiMeta.body = parseBody(apiMeta.body, apiReq, data, apiMeta);
apiMeta.body = parseBody(apiMeta.body, user, data, apiMeta);
console.log(e);
}
}
@ -139,11 +139,11 @@ export function axiosRequestMake(_apiMeta, apiReq, data) {
try {
apiMeta.auth = JSON.parse(apiMeta.auth, (_key, value) => {
return typeof value === 'string'
? parseBody(value, apiReq, data, apiMeta)
? parseBody(value, user, data, apiMeta)
: value;
});
} catch (e) {
apiMeta.auth = parseBody(apiMeta.auth, apiReq, data, apiMeta);
apiMeta.auth = parseBody(apiMeta.auth, user, data, apiMeta);
console.log(e);
}
}
@ -152,17 +152,12 @@ export function axiosRequestMake(_apiMeta, apiReq, data) {
params: apiMeta.parameters
? apiMeta.parameters.reduce((paramsObj, param) => {
if (param.name && param.enabled) {
paramsObj[param.name] = parseBody(
param.value,
apiReq,
data,
apiMeta
);
paramsObj[param.name] = parseBody(param.value, user, data, apiMeta);
}
return paramsObj;
}, {})
: {},
url: parseBody(apiMeta.path, apiReq, data, apiMeta),
url: parseBody(apiMeta.path, user, data, apiMeta),
method: apiMeta.method,
data: apiMeta.body,
headers: apiMeta.headers
@ -170,7 +165,7 @@ export function axiosRequestMake(_apiMeta, apiReq, data) {
if (header.name && header.enabled) {
headersObj[header.name] = parseBody(
header.value,
apiReq,
user,
data,
apiMeta
);
@ -188,7 +183,8 @@ export async function invokeWebhook(
_model: Model,
data,
user,
testFilters = null
testFilters = null,
throwErrorOnFailure = false
) {
let hookLog: HookLogType;
const startTime = process.hrtime();
@ -289,16 +285,19 @@ export async function invokeWebhook(
}
} catch (e) {
console.log(e);
hookLog = {
...hook,
error_code: e.error_code,
error_message: e.message,
error: JSON.stringify(e)
};
if (throwErrorOnFailure) throw e;
} finally {
hookLog.execution_time = parseHrtimeToMilliSeconds(
process.hrtime(startTime)
);
if (hookLog) HookLog.insert({ ...hookLog, test_call: !!testFilters });
}
hookLog.execution_time = parseHrtimeToMilliSeconds(process.hrtime(startTime));
if (hookLog) await HookLog.insert({ ...hookLog, test_call: !!testFilters });
}
export function _transformSubmittedFormDataForEmail(
@ -342,6 +341,6 @@ export function _transformSubmittedFormDataForEmail(
}
function parseHrtimeToMilliSeconds(hrtime) {
const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3);
return seconds;
const milliseconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3);
return milliseconds;
}

Loading…
Cancel
Save