Browse Source

Merge pull request #3129 from nocodb/fix/gui-v2-webhook

fix(gui-v2): webhook follow up issues
pull/3162/head
navi 2 years ago committed by GitHub
parent
commit
2d41fa2d6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui-v2/components/api-client/Headers.vue
  2. 17
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue
  3. 66
      packages/nc-gui-v2/components/webhook/ChannelMultiSelect.vue
  4. 6
      packages/nc-gui-v2/components/webhook/Drawer.vue
  5. 93
      packages/nc-gui-v2/components/webhook/Editor.vue
  6. 6
      packages/nc-gui-v2/components/webhook/List.vue
  7. 2
      packages/nc-gui-v2/components/webhook/Test.vue
  8. 33
      packages/nc-gui-v2/composables/useViewFilters.ts

4
packages/nc-gui-v2/components/api-client/Headers.vue

@ -85,7 +85,7 @@ const deleteHeaderRow = (idx: number) => vModel.value.splice(idx, 1)
<a-checkbox v-model:checked="headerRow.enabled" />
</a-form-item>
</td>
<td class="px-2">
<td class="px-2 w-min-[400px]">
<a-form-item>
<a-select v-model:value="headerRow.name" size="large" placeholder="Key">
<a-select-option v-for="(header, i) in headerList" :key="i" :value="header">
@ -94,7 +94,7 @@ const deleteHeaderRow = (idx: number) => vModel.value.splice(idx, 1)
</a-select>
</a-form-item>
</td>
<td class="px-2">
<td class="px-2 w-min-[400px]">
<a-form-item>
<a-input v-model:value="headerRow.value" size="large" placeholder="Value" />
</a-form-item>

17
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue

@ -9,12 +9,19 @@ import { ActiveViewInj, MetaInj, ReloadViewDataHookInj } from '~/context'
import MdiDeleteIcon from '~icons/mdi/close-box'
import MdiAddIcon from '~icons/mdi/plus'
const { nested = false, parentId, autoSave = true } = defineProps<{ nested?: boolean; parentId?: string; autoSave: boolean }>()
const {
nested = false,
parentId,
autoSave = true,
hookId = null,
} = defineProps<{ nested?: boolean; parentId?: string; autoSave: boolean; hookId?: string }>()
const emit = defineEmits(['update:filtersLength'])
const meta = inject(MetaInj)
const activeView = inject(ActiveViewInj)
const reloadDataHook = inject(ReloadViewDataHookInj)
// todo: replace with inject or get from state
@ -76,7 +83,7 @@ const types = computed(() => {
watch(
() => (activeView?.value as any)?.id,
(n, o) => {
if (n !== o) loadFilters()
if (n !== o) loadFilters(hookId as string)
},
{ immediate: true },
)
@ -95,11 +102,11 @@ watch(
},
)
const applyChanges = async () => {
await sync()
const applyChanges = async (hookId?: string) => {
await sync(hookId)
for (const nestedFilter of nestedFilters?.value || []) {
if (nestedFilter.parentId) {
await nestedFilter.applyChanges(true)
await nestedFilter.applyChanges(hookId, true)
}
}
}

66
packages/nc-gui-v2/components/webhook/ChannelMultiSelect.vue

@ -0,0 +1,66 @@
<script setup lang="ts">
import { onMounted } from '@vue/runtime-core'
interface Props {
modelValue: Record<string, any>[]
availableChannelList: Record<string, any>[]
placeholder: string
}
const { availableChannelList, placeholder, ...rest } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const vModel = useVModel(rest, 'modelValue', emit)
// idx of selected channels
const localChannelValues = $ref<number[]>([])
// availableChannelList with idx enriched
let availableChannelWithIdxList = $ref<Record<string, any>[]>()
watch(
() => localChannelValues,
(v) => {
const res = []
for (const channelIdx of v) {
const target = availableChannelWithIdxList.find((availableChannel) => availableChannel.idx === channelIdx)
if (target) {
// push without target.idx
res.push({ webhook_url: target.webhook_url, channel: target.channel })
}
}
vModel.value = res
},
)
onMounted(() => {
if (availableChannelList.length) {
// enrich idx
let idx = 0
availableChannelWithIdxList = availableChannelList.map((channel) => ({
...channel,
idx: idx++,
}))
// build localChannelValues from modelValue
for (const channel of rest.modelValue || []) {
const target = availableChannelWithIdxList.find(
(availableChannelWithIdx) =>
availableChannelWithIdx.webhook_url === channel.webhook_url && availableChannelWithIdx.channel === channel.channel,
)
if (target) {
localChannelValues.push(target.idx)
}
}
}
})
</script>
<template>
<a-select v-model:value="localChannelValues" mode="multiple" :placeholder="placeholder" max-tag-count="responsive">
<a-select-option v-for="channel of availableChannelWithIdxList" :key="channel.idx" :value="channel.idx">{{
channel.channel
}}</a-select-option>
</a-select>
</template>

6
packages/nc-gui-v2/components/webhook/Drawer.vue

@ -31,8 +31,10 @@ async function editHook(hook: Record<string, any>) {
:body-style="{ background: 'rgba(67, 81, 232, 0.05)', padding: '50px' }"
@keydown.esc="vModel = false"
>
<WebhookEditor v-if="editOrAdd" ref="webhookEditorRef" @back-to-list="editOrAdd = false" />
<WebhookList v-else @edit="editHook" @add="editOrAdd = true" />
<div>
<WebhookEditor v-if="editOrAdd" ref="webhookEditorRef" @back-to-list="editOrAdd = false" />
<WebhookList v-else @edit="editHook" @add="editOrAdd = true" />
</div>
<div class="self-center flex flex-column flex-wrap gap-4 items-center mt-4 md:mx-8 md:justify-between justify-center">
<a-button v-t="['e:hiring']" href="https://angel.co/company/nocodb" target="_blank" size="large">
🚀 We are Hiring! 🚀

93
packages/nc-gui-v2/components/webhook/Editor.vue

@ -1,13 +1,6 @@
<script setup lang="ts">
import { Form, message } from 'ant-design-vue'
import { MetaInj } from '~/context'
import { extractSdkResponseErrorMsg, fieldRequiredValidator } from '~/utils'
import { inject, reactive, useApi, useNuxtApp } from '#imports'
interface Option {
label: string
value: string
}
import { MetaInj, extractSdkResponseErrorMsg, fieldRequiredValidator, inject, reactive, useApi, useNuxtApp } from '#imports'
const emit = defineEmits(['backToList', 'editOrAdd'])
@ -51,7 +44,7 @@ const discordChannels = ref<Record<string, any>[]>([])
const mattermostChannels = ref<Record<string, any>[]>([])
const filters = ref([])
const filterRef = ref()
const formInput = ref({
'Email': [
@ -205,21 +198,27 @@ const validators = computed(() => {
})
const { validate, validateInfos } = useForm(hook, validators)
function onNotTypeChange() {
hook.notification.payload = {} as any
function onNotTypeChange(reset = false) {
if (reset) {
hook.notification.payload = {} as Record<string, any>
}
if (hook.notification.type === 'Slack') {
slackChannels.value = (apps && apps?.Slack && apps.Slack.parsedInput) || []
slackChannels.value = (apps.value && apps.value.Slack && apps.Slack.parsedInput) || []
}
if (hook.notification.type === 'Microsoft Teams') {
teamsChannels.value = (apps && apps['Microsoft Teams'] && apps['Microsoft Teams'].parsedInput) || []
teamsChannels.value = (apps.value && apps.value['Microsoft Teams'] && apps.value['Microsoft Teams'].parsedInput) || []
}
if (hook.notification.type === 'Discord') {
discordChannels.value = (apps && apps.Discord && apps.Discord.parsedInput) || []
discordChannels.value = (apps.value && apps.value.Discord && apps.value.Discord.parsedInput) || []
}
if (hook.notification.type === 'Mattermost') {
mattermostChannels.value = (apps && apps.Mattermost && apps.Mattermost.parsedInput) || []
mattermostChannels.value = (apps.value && apps.value.Mattermost && apps.value.Mattermost.parsedInput) || []
}
if (hook.notification.type === 'URL') {
hook.notification.payload.body = '{{ json data }}'
hook.notification.payload.parameters = [{}]
@ -228,10 +227,6 @@ function onNotTypeChange() {
}
}
function filterOption(input: string, option: Option) {
return option.value.toUpperCase().includes(input.toUpperCase())
}
function setHook(newHook: any) {
Object.assign(hook, {
...newHook,
@ -301,6 +296,8 @@ async function loadPluginList() {
if (hook.event && hook.operation) {
hook.eventOperation = `${hook.event} ${hook.operation}`
}
onNotTypeChange()
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -342,12 +339,9 @@ async function saveHooks() {
hook.id = res.id
}
// TODO: wait for filter implementation
// if ($refs.filter) {
// await $refs.filter.applyChanges(false, {
// hookId: hook.id,
// });
// }
if (filterRef.value) {
await filterRef.value.applyChanges(hook.id)
}
message.success('Webhook details updated successfully')
} catch (e: any) {
@ -383,8 +377,8 @@ watch(
},
)
onMounted(() => {
loadPluginList()
onMounted(async () => {
await loadPluginList()
})
</script>
@ -439,7 +433,7 @@ onMounted(() => {
v-model:value="hook.notification.type"
size="large"
:placeholder="$t('general.notification')"
@change="onNotTypeChange"
@change="onNotTypeChange(true)"
>
<a-select-option v-for="(notificationOption, i) in notificationList" :key="i" :value="notificationOption.type">
<div class="flex items-center">
@ -505,12 +499,12 @@ onMounted(() => {
<a-row v-if="hook.notification.type === 'Slack'" type="flex">
<a-col :span="24">
<a-form-item v-bind="validateInfos['notification.channels']">
<a-auto-complete
v-model:value="hook.notification.payload.channels"
size="large"
:options="slackChannels"
<WebhookChannelMultiSelect
v-if="slackChannels.length > 0"
v-model="hook.notification.payload.channels"
:selected-channel-list="hook.notification.payload.channels"
:available-channel-list="slackChannels"
placeholder="Select Slack channels"
:filter-option="filterOption"
/>
</a-form-item>
</a-col>
@ -519,12 +513,12 @@ onMounted(() => {
<a-row v-if="hook.notification.type === 'Microsoft Teams'" type="flex">
<a-col :span="24">
<a-form-item v-bind="validateInfos['notification.channels']">
<a-auto-complete
v-model:value="hook.notification.payload.channels"
size="large"
:options="teamsChannels"
<WebhookChannelMultiSelect
v-if="teamsChannels.length > 0"
v-model="hook.notification.payload.channels"
:selected-channel-list="hook.notification.payload.channels"
:available-channel-list="teamsChannels"
placeholder="Select Microsoft Teams channels"
:filter-option="filterOption"
/>
</a-form-item>
</a-col>
@ -533,12 +527,12 @@ onMounted(() => {
<a-row v-if="hook.notification.type === 'Discord'" type="flex">
<a-col :span="24">
<a-form-item v-bind="validateInfos['notification.channels']">
<a-auto-complete
v-model:value="hook.notification.payload.channels"
size="large"
:options="discordChannels"
<WebhookChannelMultiSelect
v-if="discordChannels.length > 0"
v-model="hook.notification.payload.channels"
:selected-channel-list="hook.notification.payload.channels"
:available-channel-list="discordChannels"
placeholder="Select Discord channels"
:filter-option="filterOption"
/>
</a-form-item>
</a-col>
@ -547,12 +541,12 @@ onMounted(() => {
<a-row v-if="hook.notification.type === 'Mattermost'" type="flex">
<a-col :span="24">
<a-form-item v-bind="validateInfos['notification.channels']">
<a-auto-complete
v-model:value="hook.notification.payload.channels"
size="large"
:options="mattermostChannels"
<WebhookChannelMultiSelect
v-if="mattermostChannels.length > 0"
v-model="hook.notification.payload.channels"
:selected-channel-list="hook.notification.payload.channels"
:available-channel-list="mattermostChannels"
placeholder="Select Mattermost channels"
:filter-option="filterOption"
/>
</a-form-item>
</a-col>
@ -573,7 +567,7 @@ onMounted(() => {
<a-col :span="24">
<a-card>
<a-checkbox v-model:checked="hook.condition">On Condition</a-checkbox>
<SmartsheetToolbarColumnFilter v-if="hook.condition" />
<SmartsheetToolbarColumnFilter v-if="hook.condition" ref="filterRef" :auto-save="false" :hook-id="hook.id" />
</a-card>
</a-col>
</a-row>
@ -601,7 +595,6 @@ onMounted(() => {
ref="webhookTestRef"
:hook="{
...hook,
filters,
notification: {
...hook.notification,
payload: hook.notification.payload,

6
packages/nc-gui-v2/components/webhook/List.vue

@ -58,7 +58,11 @@ onMounted(() => {
</div>
<a-divider />
<div v-if="hooks.length">
<a-list item-layout="horizontal" :data-source="hooks" class="cursor-pointer pl-5 pr-5 pt-2 pb-2">
<a-list
item-layout="horizontal"
:data-source="hooks"
class="cursor-pointer max-h-[75vh] overflow-y-auto scrollbar-thin-primary"
>
<template #renderItem="{ item, index }">
<a-list-item class="pa-2" @click="emit('edit', item)">
<a-list-item-meta>

2
packages/nc-gui-v2/components/webhook/Test.vue

@ -28,7 +28,7 @@ watch(
async function loadSampleData() {
sampleData.value = {
data: await $api.dbTableWebhook.samplePayloadGet(meta?.value?.id as string, hook?.operation),
data: await $api.dbTableWebhook.samplePayloadGet(meta?.value?.id as string, hook?.operation || 'insert'),
}
}

33
packages/nc-gui-v2/composables/useViewFilters.ts

@ -16,15 +16,23 @@ export function useViewFilters(
const { isUIAllowed } = useUIPermission()
const { metas } = useMetas()
const loadFilters = async () => {
if (parentId) {
filters.value = await $api.dbTableFilter.childrenRead(parentId)
const loadFilters = async (hookId?: string) => {
if (hookId) {
if (parentId) {
filters.value = await $api.dbTableFilter.childrenRead(parentId)
} else {
filters.value = (await $api.dbTableWebhookFilter.read(hookId as string)) as any
}
} else {
filters.value = await $api.dbTableFilter.read(view?.value?.id as string)
if (parentId) {
filters.value = await $api.dbTableFilter.childrenRead(parentId)
} else {
filters.value = await $api.dbTableFilter.read(view?.value?.id as string)
}
}
}
const sync = async (_nested = false) => {
const sync = async (hookId?: string, _nested = false) => {
for (const [i, filter] of Object.entries(filters.value)) {
if (filter.status === 'delete') {
await $api.dbTableFilter.delete(filter.id as string)
@ -34,10 +42,17 @@ export function useViewFilters(
fk_parent_id: parentId,
})
} else if (filter.status === 'create') {
filters.value[+i] = (await $api.dbTableFilter.create(view?.value?.id as string, {
...filter,
fk_parent_id: parentId,
})) as any
if (hookId) {
filters.value[+i] = (await $api.dbTableWebhookFilter.create(hookId, {
...filter,
fk_parent_id: parentId,
})) as any
} else {
filters.value[+i] = (await $api.dbTableFilter.create(view?.value?.id as string, {
...filter,
fk_parent_id: parentId,
})) as any
}
}
}
reloadData?.()

Loading…
Cancel
Save