mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
### **User description** Design at : security/sso-claim-parser/configuration.md ___ ### **PR Type** Enhancement, Tests ___ ### **Description** - Introduce rquickjs-based JS runtime for functions - Add compile_js_function and apply_js_fn utilities - Extend pipeline to support JS via CompiledFunctionRuntime - Update web UI: select JS/VRL transforms, show JS errors ___ ### Diagram Walkthrough ```mermaid flowchart LR A["FunctionNode"] -->|JS| B["compile_js_function"] A -->|VRL| C["compile_vrl_function"] B --> D["apply_js_fn"] C --> E["apply_vrl_fn"] D --> F["send to children"] E --> F ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>13 files</summary><table> <tr> <td><strong>function.ts</strong><dd><code>Add optional `trans_type` to TestFunctionPayload</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-c6766f6c671adce05f9eef5d4a57a32371fa7da35628ef5c142fa503d191cb16">+16/-0</a> </td> </tr> <tr> <td><strong>js.rs</strong><dd><code>Introduce JS runtime, compile and apply functions</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-88e490bed5fa6c9c8e0aa2738b95c5284bb97899298f54b9cd587713e3ef0109">+756/-0</a> </td> </tr> <tr> <td><strong>batch_execution.rs</strong><dd><code>Extend pipeline execution to handle JS functions</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-3f6824a6eddaf1603e5cb0f1a4311ac6a45e1ffb967266ccc1f0bba944625e69">+465/-96</a></td> </tr> <tr> <td><strong>functions.rs</strong><dd><code>Validate and test-run JS transforms, restrict to _meta</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-ed5611a9aa6b5c033e70d7631ba72fafad3cc842672077a7fcf965d11a4fef23">+319/-35</a></td> </tr> <tr> <td><strong>mod.rs</strong><dd><code>Forbid JavaScript functions in pipelines globally</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-c186c796a7bdfd9d1301b1429df11456430f1d0d4758783810b9e2e44384e3b2">+39/-1</a> </td> </tr> <tr> <td><strong>jwt.rs</strong><dd><code>Add JS claim parser execution function</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-232cab74f4cbf135af424744272e17732fbda8ac005d33c0acb4597e65479d02">+25/-2</a> </td> </tr> <tr> <td><strong>mod.rs</strong><dd><code>Wrap JS compile/apply in ingestion API</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-7c7b5bca85f0ced652965cae66540c96f56dfcca4f4c16b77bf7b45aac203d1b">+24/-1</a> </td> </tr> <tr> <td><strong>mod.rs</strong><dd><code>Include `trans_type` in test_function API payload</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-a5e1b3644cef9feb689dfe66382e44e5434ecadbff3469127346a333d4733102">+8/-3</a> </td> </tr> <tr> <td><strong>AddFunction.vue</strong><dd><code>UI: add JS/VRL transform type selector</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-ebfd6c538f3cd3ecb9ab17550fd76e2e89e7a1abccc83a7505077e4af3eee110">+42/-25</a> </td> </tr> <tr> <td><strong>FunctionsToolbar.vue</strong><dd><code>Toolbar: transform type radio buttons</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-8283d2ff9c4a66b4b59eac306672aca2d54655a462e2ddb88e14e039d79dcdaf">+51/-3</a> </td> </tr> <tr> <td><strong>DomainManagement.vue</strong><dd><code>Track claim-parser function changes for save button</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-5e47cd01fcb63f7ef27dc64d2ecf66c39e17097e29944951c3ae1bfe715bb97c">+17/-9</a> </td> </tr> <tr> <td><strong>TestFunction.vue</strong><dd><code>Support JS error messaging in test UI</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-b9af9a2d1bf04d23422c26a13e6a763c6953c8713357a2f64ed8073ce6c926a3">+15/-5</a> </td> </tr> <tr> <td><strong>PipelineEditor.vue</strong><dd><code>Filter out JS functions in pipeline editor UI</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-68c21f647336067eb79737ce9aca2d49632f2fa1c0878bcfdae55607e3473a15">+12/-4</a> </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>2 files</summary><table> <tr> <td><strong>function.rs</strong><dd><code>Extend `Transform` model for JS support</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-27c9f3d93723397a0e234bc45c9f543b43eff5fd518d76e735f78d95a58f3f1c">+16/-6</a> </td> </tr> <tr> <td><strong>mod.rs</strong><dd><code>Register new `js` utils module</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-2aa59ce4464e7dbabdb9367fc8bb8c1b489c914b8690577d4916870463ab12db">+1/-0</a> </td> </tr> </table></details></td></tr><tr><td><strong>Bug fix</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>errors.rs</strong><dd><code>Rename `MemoryTableOverflowError` display message</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-bf45932466265b732f13d0cd9b9cffbaefbe37c5861ace9315449eeca9250713">+1/-1</a> </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>result-array-functions.spec.js</strong><dd><code>Add E2E tests for `#ResultArray#` functions in UI</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-e9911625ad2bab76f2b760f0ad254791331993a6a874b96f6a11fc6d88038b10">+470/-0</a> </td> </tr> <tr> <td><strong>cleanup.spec.js</strong><dd><code>Clean up JS function test data in UI tests</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-c214275a512fa285a7e4f2ab61dfcb42d71a0436b0e4241f94d7b45693587983">+31/-0</a> </td> </tr> <tr> <td><strong>page-manager.js</strong><dd><code>Register `FunctionsPage` in `PageManager`</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-565eab8b3c767ae50c9d05e591621d14f66b81af84d6b5ea6efe136c1c3933df">+6/-0</a> </td> </tr> </table></details></td></tr><tr><td><strong>Dependencies</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>Cargo.toml</strong><dd><code>Add `rquickjs` dependency for JS runtime</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542">+2/-0</a> </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>18 files</summary><table> <tr> <td><strong>test_functions_meta_org_restriction.py</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-99ef1207d6ae6d7eeaed37d70c8cf0c4770ba78f6f51fbdc067d35492d2fe9ea">+401/-0</a> </td> </tr> <tr> <td><strong>test_js_enrichment.py</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-c5547d46bec60b888d947d1e9b7c5445aee72cf8e837182f7138e0fc903eedbb">+356/-0</a> </td> </tr> <tr> <td><strong>test_js_result_array.py</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-9ed1363a1f58c94e018bb2b0aaa9d08fa71f89e7e55a6ff6be4a82bbeed8985c">+390/-0</a> </td> </tr> <tr> <td><strong>functionsPage.js</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-7bab9cc2eb8f36d3ee5d0aab453c353ec6d6dfecbc401cf401322ff57faa8440">+326/-0</a> </td> </tr> <tr> <td><strong>javascript-functions.spec.js</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-bdae6529346a7222bdec19f75f490cb9d0f3578a260ba835225efcd27db3f64e">+314/-0</a> </td> </tr> <tr> <td><strong>meta-org-js-restriction.spec.js</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-16cc2373f6f1955f8344a59d5f2edcd6ce121c4fdee1d432b1f0ec8e27f25881">+483/-0</a> </td> </tr> <tr> <td><strong>FunctionList.vue</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-6ff9af50a1597504b7f0dca91e61cf30c800b4a17b4be2ed2c3a4f7a7b2d7068">+1/-0</a> </td> </tr> <tr> <td><strong>de.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-78f24ecb2dbcfc38fdf3ec22b2559a5b40d236ea689d1c55f5d3e87168310264">+30/-18</a> </td> </tr> <tr> <td><strong>en.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-892aa3d75ab6e129ef9116aa21bec1b9a15ba69ce5366d2a19128078c690d824">+15/-7</a> </td> </tr> <tr> <td><strong>es.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-ac2a2167b8b973e3f776f9fe3b43bef4576470d8ad33f3d7cfaff6edff0e5778">+30/-18</a> </td> </tr> <tr> <td><strong>fr.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-db80e762b88ddf183820c050cdc6b215e6b02b365b5e73f5152af46b0d26f1a2">+30/-18</a> </td> </tr> <tr> <td><strong>it.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-2de78074a5f887afddce60d694ead9322e6ab223eb70251c13819f3028bdc70d">+30/-18</a> </td> </tr> <tr> <td><strong>ja.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-092e49c5f9fbc1b539ab9c2866d108d0ce1d314022b4c294eb4611b285e7bbd3">+30/-18</a> </td> </tr> <tr> <td><strong>ko.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-50e27ca1b4825a5026f3a251bfcf79c97cab07cfade2ebd512d332453d707c48">+30/-18</a> </td> </tr> <tr> <td><strong>nl.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-a1706021687ae5d8a1c006c49eab3946174fecd895f9b70a89c2e76d97250906">+30/-18</a> </td> </tr> <tr> <td><strong>pt.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-38e804f64cb5e0e451849fc84e9b46e53c954a4a4263d51c18665ad45f4b4815">+30/-18</a> </td> </tr> <tr> <td><strong>tr.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-6f0f131e9e2591480c4c5d1eaffc72d02dc31cbd8d3f90d8f012f08cabac8818">+30/-18</a> </td> </tr> <tr> <td><strong>zh.json</strong></td> <td><a href="https://github.com/openobserve/openobserve/pull/9873/files#diff-8818055db58f83230aeea4ebfd4cec31d924e8c47b6f91298e356959ab25339e">+254/-242</a></td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___ --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
836 lines
25 KiB
Vue
836 lines
25 KiB
Vue
<!-- Copyright 2023 OpenObserve Inc.
|
|
|
|
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/>.
|
|
-->
|
|
|
|
<!-- eslint-disable vue/v-on-event-hyphenation -->
|
|
<!-- eslint-disable vue/attribute-hyphenation -->
|
|
<template>
|
|
<q-page>
|
|
<div v-if="!showAddJSTransformDialog">
|
|
<div class="tw:w-full tw:h-full tw:pr-[0.625rem] tw:pb-[0.625rem]">
|
|
<div class="card-container tw:mb-[0.625rem]">
|
|
<div class="tw:flex tw:items-center tw:justify-between tw:py-3 tw:px-4 tw:h-[68px]">
|
|
<div class="q-table__title tw:font-[600]">
|
|
{{ t("function.header") }}
|
|
</div>
|
|
<div class="q-ml-auto" data-test="functions-list-search-input">
|
|
<q-input
|
|
v-model="filterQuery"
|
|
borderless
|
|
dense
|
|
class="q-ml-auto no-border o2-search-input"
|
|
:placeholder="t('function.search')"
|
|
>
|
|
<template #prepend>
|
|
<q-icon class="o2-search-input-icon" name="search" />
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
<q-btn
|
|
class="q-ml-sm o2-primary-button tw:h-[36px]"
|
|
flat
|
|
no-caps
|
|
:label="t(`function.add`)"
|
|
data-test="function-list-add-function-btn"
|
|
@click="showAddUpdateFn({})"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="tw:w-full tw:h-full tw:pb-[0.625rem]">
|
|
<div class="card-container tw:h-[calc(100vh-127px)]">
|
|
<q-table
|
|
ref="qTable"
|
|
:rows="visibleRows"
|
|
:columns="columns"
|
|
row-key="name"
|
|
:pagination="pagination"
|
|
:filter="filterQuery"
|
|
selection="multiple"
|
|
v-model:selected="selectedFunctions"
|
|
style="width: 100%"
|
|
:style="hasVisibleRows
|
|
? 'width: 100%; height: calc(100vh - 130px)'
|
|
: 'width: 100%'"
|
|
class="o2-quasar-table o2-row-md o2-quasar-table-header-sticky"
|
|
>
|
|
<template #no-data>
|
|
<NoData />
|
|
</template>
|
|
<template v-slot:body-cell-actions="props">
|
|
<q-td :props="props">
|
|
<q-btn
|
|
padding="sm"
|
|
unelevated
|
|
size="sm"
|
|
icon="edit"
|
|
round
|
|
flat
|
|
:title="t('function.updateTitle')"
|
|
@click="showAddUpdateFn(props)"
|
|
>
|
|
</q-btn>
|
|
<q-btn
|
|
padding="sm"
|
|
unelevated
|
|
size="sm"
|
|
:icon="outlinedDelete"
|
|
round
|
|
flat
|
|
:title="t('function.delete')"
|
|
@click="showDeleteDialogFn(props)"
|
|
>
|
|
</q-btn>
|
|
<q-btn
|
|
padding="sm"
|
|
unelevated
|
|
size="sm"
|
|
:icon="outlinedAccountTree"
|
|
round
|
|
flat
|
|
:title="'Associated Pipelines'"
|
|
@click="getAssociatedPipelines(props)"
|
|
>
|
|
</q-btn>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:body-cell-function="props">
|
|
<q-td :props="props">
|
|
<q-tooltip>
|
|
<pre>{{ props.row.function }}</pre>
|
|
</q-tooltip>
|
|
<pre style="white-space: break-spaces">{{
|
|
props.row.function
|
|
}}</pre>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:body-selection="scope">
|
|
<q-checkbox v-model="scope.selected" size="sm" class="o2-table-checkbox" />
|
|
</template>
|
|
|
|
<template #bottom="scope">
|
|
<div class="tw:flex tw:items-center tw:justify-between tw:w-full tw:h-[48px]">
|
|
<div class="o2-table-footer-title tw:flex tw:items-center tw:w-[100px] tw:mr-md">
|
|
{{ resultTotal }} {{ t('function.header') }}
|
|
</div>
|
|
<q-btn
|
|
v-if="selectedFunctions.length > 0"
|
|
data-test="function-list-delete-functions-btn"
|
|
class="flex items-center q-mr-sm no-border o2-secondary-button tw:h-[36px]"
|
|
:class="
|
|
store.state.theme === 'dark'
|
|
? 'o2-secondary-button-dark'
|
|
: 'o2-secondary-button-light'
|
|
"
|
|
no-caps
|
|
dense
|
|
@click="openBulkDeleteDialog"
|
|
>
|
|
<q-icon name="delete" size="16px" />
|
|
<span class="tw:ml-2">Delete</span>
|
|
</q-btn>
|
|
<QTablePagination
|
|
:scope="scope"
|
|
:position="'bottom'"
|
|
:resultTotal="resultTotal"
|
|
:perPageOptions="perPageOptions"
|
|
@update:changeRecordPerPage="changePagination"
|
|
/>
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<!-- Adding this block to render the select-all checkbox -->
|
|
<q-th v-if="columns.length > 0" auto-width>
|
|
<q-checkbox
|
|
v-model="props.selected"
|
|
size="sm"
|
|
:class="store.state.theme === 'dark' ? 'o2-table-checkbox-dark' : 'o2-table-checkbox-light'"
|
|
class="o2-table-checkbox"
|
|
/>
|
|
</q-th>
|
|
|
|
<!-- Rendering the rest of the columns -->
|
|
<q-th
|
|
v-for="col in props.cols"
|
|
:key="col.name"
|
|
:props="props"
|
|
:class="col.classes"
|
|
:style="col.style"
|
|
>
|
|
{{ col.label }}
|
|
</q-th>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<AddFunction
|
|
v-model="formData"
|
|
:isUpdated="isUpdated"
|
|
class="q-pa-sm"
|
|
@update:list="refreshList"
|
|
@cancel:hideform="hideForm"
|
|
@sendToAiChat="sendToAiChat"
|
|
/>
|
|
</div>
|
|
<ConfirmDialog
|
|
title="Delete Transform"
|
|
message="Are you sure you want to delete transform?"
|
|
@update:ok="deleteFn"
|
|
@update:cancel="confirmDelete = false"
|
|
v-model="confirmDelete"
|
|
/>
|
|
|
|
<ConfirmDialog
|
|
title="Delete Functions"
|
|
:message="`Are you sure you want to delete ${selectedFunctions.length} function(s)?`"
|
|
@update:ok="bulkDeleteFunctions"
|
|
@update:cancel="confirmBulkDelete = false"
|
|
v-model="confirmBulkDelete"
|
|
/>
|
|
|
|
<q-dialog v-model="confirmForceDelete" persistent>
|
|
<q-card style="width: 40vw; max-height: 90vh; overflow-y: auto">
|
|
<q-card-section
|
|
class="text-h6 dialog-heading tw:flex tw:justify-between tw:items-center"
|
|
>
|
|
<div>
|
|
Pipelines Associated with
|
|
<strong> {{ selectedDelete.name }}</strong>
|
|
</div>
|
|
<q-icon
|
|
name="close"
|
|
size="18px"
|
|
@click="closeDialog"
|
|
style="cursor: pointer"
|
|
/>
|
|
</q-card-section>
|
|
<q-card-section>
|
|
<div
|
|
v-if="transformedPipelineList.length > 0"
|
|
class="pipeline-list-container"
|
|
>
|
|
<q-list class="scrollable-list">
|
|
<q-item
|
|
v-for="(pipeline, index) in transformedPipelineList"
|
|
:key="pipeline.value"
|
|
clickable
|
|
@click="onPipelineSelect(pipeline)"
|
|
>
|
|
<q-item-section>
|
|
{{ index + 1 }}. {{ pipeline.label }}
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</div>
|
|
<div v-else>
|
|
<div class="text-h6 text-center">
|
|
No pipelines associated with this function
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-dialog>
|
|
</q-page>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import {
|
|
defineAsyncComponent,
|
|
defineComponent,
|
|
ref,
|
|
computed,
|
|
watch,
|
|
onMounted,
|
|
} from "vue";
|
|
import { useStore } from "vuex";
|
|
import { useRouter } from "vue-router";
|
|
import { useQuasar, type QTableProps } from "quasar";
|
|
import { useI18n } from "vue-i18n";
|
|
|
|
import QTablePagination from "../shared/grid/Pagination.vue";
|
|
import jsTransformService from "../../services/jstransform";
|
|
import NoData from "../shared/grid/NoData.vue";
|
|
import ConfirmDialog from "../ConfirmDialog.vue";
|
|
import segment from "../../services/segment_analytics";
|
|
import { getImageURL, verifyOrganizationStatus } from "../../utils/zincutils";
|
|
import {
|
|
outlinedDelete,
|
|
outlinedAccountTree,
|
|
} from "@quasar/extras/material-icons-outlined";
|
|
import useLogs from "@/composables/useLogs";
|
|
import { useReo } from "@/services/reodotdev_analytics";
|
|
|
|
export default defineComponent({
|
|
name: "functionList",
|
|
components: {
|
|
QTablePagination,
|
|
AddFunction: defineAsyncComponent(() => import("./AddFunction.vue")),
|
|
NoData,
|
|
ConfirmDialog,
|
|
},
|
|
emits: [
|
|
"updated:fields",
|
|
"update:changeRecordPerPage",
|
|
"update:maxRecordToReturn",
|
|
"sendToAiChat"
|
|
],
|
|
setup(props, { emit }) {
|
|
const store = useStore();
|
|
const { t } = useI18n();
|
|
const $q = useQuasar();
|
|
const router = useRouter();
|
|
const jsTransforms: any = ref([]);
|
|
const formData: any = ref({});
|
|
const showAddJSTransformDialog: any = ref(false);
|
|
const qTable: any = ref(null);
|
|
const selectedDelete: any = ref(null);
|
|
const isUpdated: any = ref(false);
|
|
const confirmDelete = ref<boolean>(false);
|
|
const confirmForceDelete = ref<boolean>(false);
|
|
const confirmBulkDelete = ref<boolean>(false);
|
|
const selectedFunctions = ref<any[]>([]);
|
|
const { searchObj } = useLogs();
|
|
const pipelineList = ref([]);
|
|
const selectedPipeline = ref("");
|
|
const filterQuery = ref("");
|
|
const { track } = useReo();
|
|
const columns: any = ref<QTableProps["columns"]>([
|
|
{
|
|
name: "#",
|
|
label: "#",
|
|
field: "#",
|
|
align: "left",
|
|
style: "width: 67px",
|
|
},
|
|
{
|
|
name: "name",
|
|
field: "name",
|
|
label: t("function.name"),
|
|
align: "left",
|
|
sortable: true,
|
|
},
|
|
{
|
|
name: "actions",
|
|
field: "actions",
|
|
label: t("function.actions"),
|
|
align: "center",
|
|
sortable: false,
|
|
classes:'actions-column'
|
|
},
|
|
]);
|
|
|
|
const onPipelineSelect = (pipeline: any) => {
|
|
const routeUrl = router.resolve({
|
|
name: "pipelineEditor",
|
|
query: {
|
|
id: pipeline.value,
|
|
name: pipeline.label,
|
|
org_identifier: store.state.selectedOrganization.identifier,
|
|
},
|
|
}).href;
|
|
|
|
window.open(routeUrl, "_blank");
|
|
};
|
|
|
|
const getJSTransforms = () => {
|
|
const dismiss = $q.notify({
|
|
spinner: true,
|
|
message: "Please wait while loading functions...",
|
|
});
|
|
|
|
jsTransformService
|
|
.list(
|
|
1,
|
|
100000,
|
|
"name",
|
|
false,
|
|
"",
|
|
store.state.selectedOrganization.identifier,
|
|
)
|
|
.then((res) => {
|
|
var counter = 1;
|
|
resultTotal.value = res.data.list.length;
|
|
if (router.currentRoute.value.query.action == "add") {
|
|
showAddUpdateFn({ row: undefined });
|
|
}
|
|
jsTransforms.value = res.data.list.map((data: any) => {
|
|
if (router.currentRoute.value.query.action == "update") {
|
|
if (router.currentRoute.value.query.name == data.name) {
|
|
showAddUpdateFn({ row: data });
|
|
}
|
|
}
|
|
|
|
return {
|
|
"#": counter <= 9 ? `0${counter++}` : counter++,
|
|
name: data.name,
|
|
function: data.function,
|
|
params: data.params,
|
|
// order: data.order ? data.order : 1,
|
|
// stream_name: data.stream_name ? data.stream_name : "--",
|
|
// stream_type: data.stream_type ? data.stream_type : "--",
|
|
transType: data.transType.toString(),
|
|
// ingest: data.stream_name ? true : false,
|
|
actions: "",
|
|
};
|
|
});
|
|
|
|
searchObj.data.transforms = jsTransforms.value;
|
|
|
|
dismiss();
|
|
})
|
|
.catch((err) => {
|
|
console.error("Error while pulling function", err);
|
|
|
|
dismiss();
|
|
if (err.response.status != 403) {
|
|
$q.notify({
|
|
type: "negative",
|
|
message: "Error while pulling function.",
|
|
timeout: 2000,
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
if (jsTransforms.value == "" || jsTransforms.value == undefined) {
|
|
getJSTransforms();
|
|
}
|
|
|
|
interface OptionType {
|
|
label: String;
|
|
value: number | String;
|
|
}
|
|
const perPageOptions: any = [
|
|
{ label: "20", value: 20 },
|
|
{ label: "50", value: 50 },
|
|
{ label: "100", value: 100 },
|
|
{ label: "250", value: 250 },
|
|
{ label: "500", value: 500 },
|
|
];
|
|
const resultTotal = ref<number>(0);
|
|
const maxRecordToReturn = ref<number>(100);
|
|
const selectedPerPage = ref<number>(20);
|
|
const pagination: any = ref({
|
|
rowsPerPage: 20,
|
|
});
|
|
const changePagination = (val: { label: string; value: any }) => {
|
|
selectedPerPage.value = val.value;
|
|
pagination.value.rowsPerPage = val.value;
|
|
qTable.value.setPagination(pagination.value);
|
|
};
|
|
const changeMaxRecordToReturn = (val: any) => {
|
|
maxRecordToReturn.value = val;
|
|
};
|
|
|
|
const addTransform = () => {
|
|
showAddJSTransformDialog.value = true;
|
|
};
|
|
|
|
const transformedPipelineList = computed(() => {
|
|
return pipelineList.value.map((pipeline: any) => ({
|
|
label: pipeline.name,
|
|
value: pipeline.id,
|
|
}));
|
|
});
|
|
|
|
const showAddUpdateFn = (props: any) => {
|
|
formData.value = props.row;
|
|
let action;
|
|
if (!props.row) {
|
|
isUpdated.value = false;
|
|
action = "Add Function";
|
|
router.push({
|
|
name: "functionList",
|
|
query: {
|
|
action: "add",
|
|
org_identifier: store.state.selectedOrganization.identifier,
|
|
},
|
|
});
|
|
track("Button Click", {
|
|
button: "Add Function",
|
|
page: "Functions"
|
|
});
|
|
} else {
|
|
isUpdated.value = true;
|
|
action = "Update Function";
|
|
router.push({
|
|
name: "functionList",
|
|
query: {
|
|
action: "update",
|
|
name: props.row.name,
|
|
org_identifier: store.state.selectedOrganization.identifier,
|
|
},
|
|
});
|
|
track("Button Click", {
|
|
button: "Update Function",
|
|
page: "Functions"
|
|
});
|
|
}
|
|
addTransform();
|
|
|
|
segment.track("Button Click", {
|
|
button: action,
|
|
user_org: store.state.selectedOrganization.identifier,
|
|
user_id: store.state.userInfo.email,
|
|
page: "Functions",
|
|
});
|
|
};
|
|
|
|
const refreshList = () => {
|
|
router.push({
|
|
name: "functionList",
|
|
query: {
|
|
org_identifier: store.state.selectedOrganization.identifier,
|
|
},
|
|
});
|
|
showAddJSTransformDialog.value = false;
|
|
getJSTransforms();
|
|
};
|
|
|
|
const hideForm = () => {
|
|
showAddJSTransformDialog.value = false;
|
|
router.replace({
|
|
name: "functionList",
|
|
query: {
|
|
org_identifier: store.state.selectedOrganization.identifier,
|
|
},
|
|
});
|
|
};
|
|
|
|
const deleteFn = () => {
|
|
jsTransformService
|
|
.delete(
|
|
store.state.selectedOrganization.identifier,
|
|
selectedDelete.value.name,
|
|
)
|
|
.then((res: any) => {
|
|
if (res.data.code == 200) {
|
|
$q.notify({
|
|
type: "positive",
|
|
message: res.data.message,
|
|
timeout: 2000,
|
|
});
|
|
getJSTransforms();
|
|
} else {
|
|
$q.notify({
|
|
type: "negative",
|
|
message: res.data.message,
|
|
timeout: 2000,
|
|
});
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
if (err.response.data.code == 409) {
|
|
$q.notify({
|
|
type: "negative",
|
|
message:
|
|
"Function deletion failed as it is associated with pipelines. Click on view button to get associated pipelines.",
|
|
timeout: 10000,
|
|
actions: [
|
|
{
|
|
label: "View",
|
|
color: "white",
|
|
handler: () => {
|
|
forceRemoveFunction(err.response.data["message"]);
|
|
},
|
|
},
|
|
],
|
|
});
|
|
return;
|
|
}
|
|
if (err.response.status != 403) {
|
|
$q.notify({
|
|
type: "negative",
|
|
message:
|
|
JSON.stringify(err.response.data["message"]) ||
|
|
"Function deletion failed.",
|
|
});
|
|
}
|
|
});
|
|
|
|
segment.track("Button Click", {
|
|
button: "Delete Function",
|
|
user_org: store.state.selectedOrganization.identifier,
|
|
user_id: store.state.userInfo.email,
|
|
function_name: selectedDelete.value.name,
|
|
is_ingest_func: selectedDelete.value.ingest,
|
|
page: "Functions",
|
|
});
|
|
};
|
|
|
|
const showDeleteDialogFn = (props: any) => {
|
|
selectedDelete.value = props.row;
|
|
confirmDelete.value = true;
|
|
};
|
|
|
|
const getAssociatedPipelines = (props: any) => {
|
|
selectedDelete.value = props.row;
|
|
jsTransformService
|
|
.getAssociatedPipelines(
|
|
store.state.selectedOrganization.identifier,
|
|
props.row.name,
|
|
)
|
|
.then((res: any) => {
|
|
pipelineList.value = res.data.list;
|
|
confirmForceDelete.value = true;
|
|
})
|
|
.catch((err) => {
|
|
console.log(err);
|
|
});
|
|
};
|
|
|
|
const forceRemoveFunction = (message: any) => {
|
|
const match = message.match(/\[([^\]]+)\]/);
|
|
if (match) {
|
|
// Convert the matched string to an array of pipeline names
|
|
pipelineList.value = JSON.parse(match[0].replace(/'/g, '"'));
|
|
}
|
|
|
|
confirmForceDelete.value = true;
|
|
};
|
|
|
|
const closeDialog = () => {
|
|
confirmForceDelete.value = false;
|
|
};
|
|
|
|
const forceDeleteFn = () => {};
|
|
|
|
const sendToAiChat = (value: any) => {
|
|
emit("sendToAiChat", value);
|
|
};
|
|
|
|
const filterData = (rows: any, terms: any) => {
|
|
var filtered = [];
|
|
terms = terms.toLowerCase();
|
|
for (var i = 0; i < rows.length; i++) {
|
|
if (rows[i]["name"].toLowerCase().includes(terms)) {
|
|
filtered.push(rows[i]);
|
|
}
|
|
}
|
|
return filtered;
|
|
};
|
|
|
|
const visibleRows = computed(() => {
|
|
if (!filterQuery.value) return jsTransforms.value || []
|
|
return filterData(jsTransforms.value || [], filterQuery.value)
|
|
});
|
|
const hasVisibleRows = computed(() => visibleRows.value.length > 0);
|
|
|
|
// Watch visibleRows to sync resultTotal with search filter
|
|
watch(visibleRows, (newVisibleRows) => {
|
|
resultTotal.value = newVisibleRows.length;
|
|
}, { immediate: true });
|
|
|
|
|
|
const openBulkDeleteDialog = () => {
|
|
confirmBulkDelete.value = true;
|
|
};
|
|
|
|
const bulkDeleteFunctions = async () => {
|
|
const dismiss = $q.notify({
|
|
spinner: true,
|
|
message: "Deleting functions...",
|
|
timeout: 0,
|
|
});
|
|
|
|
try {
|
|
if (selectedFunctions.value.length === 0) {
|
|
$q.notify({
|
|
type: "negative",
|
|
message: "No functions selected for deletion",
|
|
timeout: 2000,
|
|
});
|
|
dismiss();
|
|
return;
|
|
}
|
|
|
|
// Extract function names for the API call (BE supports names)
|
|
const payload = {
|
|
ids: selectedFunctions.value.map((f: any) => f.name),
|
|
};
|
|
|
|
const response = await jsTransformService.bulkDelete(
|
|
store.state.selectedOrganization.identifier,
|
|
payload
|
|
);
|
|
|
|
dismiss();
|
|
|
|
// Handle response based on successful/unsuccessful arrays
|
|
if (response.data) {
|
|
const { successful = [], unsuccessful = [] } = response.data;
|
|
const successCount = successful.length;
|
|
const failCount = unsuccessful.length;
|
|
|
|
if (failCount > 0 && successCount > 0) {
|
|
// Partial success
|
|
$q.notify({
|
|
type: "warning",
|
|
message: `${successCount} function(s) deleted successfully, ${failCount} failed`,
|
|
timeout: 5000,
|
|
});
|
|
} else if (failCount > 0) {
|
|
// All failed
|
|
$q.notify({
|
|
type: "negative",
|
|
message: `Failed to delete ${failCount} function(s)`,
|
|
timeout: 3000,
|
|
});
|
|
} else {
|
|
// All successful
|
|
$q.notify({
|
|
type: "positive",
|
|
message: `${successCount} function(s) deleted successfully`,
|
|
timeout: 2000,
|
|
});
|
|
}
|
|
} else {
|
|
// Fallback success message
|
|
$q.notify({
|
|
type: "positive",
|
|
message: `${selectedFunctions.value.length} function(s) deleted successfully`,
|
|
timeout: 2000,
|
|
});
|
|
}
|
|
|
|
selectedFunctions.value = [];
|
|
// Refresh functions list
|
|
getJSTransforms();
|
|
} catch (error: any) {
|
|
dismiss();
|
|
console.error("Error deleting functions:", error);
|
|
|
|
// Show error message from response if available
|
|
const errorMessage = error.response?.data?.message || error?.message || "Error deleting functions. Please try again.";
|
|
if (error.response?.status != 403 || error?.status != 403) {
|
|
$q.notify({
|
|
type: "negative",
|
|
message: errorMessage,
|
|
timeout: 3000,
|
|
});
|
|
}
|
|
}
|
|
|
|
confirmBulkDelete.value = false;
|
|
};
|
|
|
|
return {
|
|
t,
|
|
qTable,
|
|
store,
|
|
router,
|
|
jsTransforms,
|
|
columns,
|
|
formData,
|
|
hideForm,
|
|
confirmDelete,
|
|
selectedDelete,
|
|
getJSTransforms,
|
|
pagination,
|
|
resultTotal,
|
|
refreshList,
|
|
perPageOptions,
|
|
selectedPerPage,
|
|
addTransform,
|
|
deleteFn,
|
|
isUpdated,
|
|
showAddUpdateFn,
|
|
showDeleteDialogFn,
|
|
changePagination,
|
|
maxRecordToReturn,
|
|
showAddJSTransformDialog,
|
|
changeMaxRecordToReturn,
|
|
outlinedDelete,
|
|
outlinedAccountTree,
|
|
forceDeleteFn,
|
|
confirmForceDelete,
|
|
pipelineList,
|
|
selectedPipeline,
|
|
closeDialog,
|
|
onPipelineSelect,
|
|
transformedPipelineList,
|
|
getAssociatedPipelines,
|
|
filterQuery,
|
|
filterData,
|
|
getImageURL,
|
|
verifyOrganizationStatus,
|
|
sendToAiChat,
|
|
visibleRows,
|
|
hasVisibleRows,
|
|
openBulkDeleteDialog,
|
|
bulkDeleteFunctions,
|
|
confirmBulkDelete,
|
|
selectedFunctions,
|
|
};
|
|
},
|
|
computed: {
|
|
// selectedOrg() {
|
|
// return this.store.state.selectedOrganization.identifier;
|
|
// },
|
|
},
|
|
watch: {
|
|
// selectedOrg(newVal: any, oldVal: any) {
|
|
// this.verifyOrganizationStatus(
|
|
// this.store.state.organizations,
|
|
// this.router
|
|
// );
|
|
// if (
|
|
// (newVal != oldVal || this.jsTransforms.value == undefined) &&
|
|
// this.router.currentRoute.value.name == "AppFunctions"
|
|
// ) {
|
|
// this.resultTotal = 0;
|
|
// this.jsTransforms = [];
|
|
// this.getJSTransforms();
|
|
// }
|
|
// },
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.pipeline-list-container {
|
|
max-height: 200px; /* Adjust based on item height to fit 5 items */
|
|
overflow-y: auto;
|
|
}
|
|
.dialog-heading {
|
|
border-bottom: 1px solid $border-color;
|
|
}
|
|
|
|
.scrollable-list::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
.scrollable-list::-webkit-scrollbar-thumb {
|
|
background-color: #888; /* Desired thumb color */
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.scrollable-list::-webkit-scrollbar-thumb:hover {
|
|
background-color: blue; /* Darker shade on hover */
|
|
}
|
|
|
|
.scrollable-list::-webkit-scrollbar-track {
|
|
background-color: blue; /* Track color */
|
|
}
|
|
</style>
|