mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 08:09:27 +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>
471 lines
18 KiB
JavaScript
471 lines
18 KiB
JavaScript
const { test, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
|
|
const testLogger = require('../utils/test-logger.js');
|
|
const PageManager = require('../../pages/page-manager.js');
|
|
const { expect } = require('@playwright/test');
|
|
|
|
test.describe('#ResultArray# Functionality Tests', { tag: ['@resultArray', '@functions'] }, () => {
|
|
let pm;
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
|
|
// Navigate to functions page
|
|
testLogger.info('Navigating to Functions page');
|
|
await pm.functionsPage.navigate();
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
// Wait for page to be ready
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Verify we're on the functions page
|
|
const addButtonVisible = await page.locator('[data-test="function-list-add-function-btn"]').isVisible({ timeout: 5000 }).catch(() => false);
|
|
testLogger.info(`Functions page loaded - Add button visible: ${addButtonVisible}`);
|
|
});
|
|
|
|
test.describe('VRL #ResultArray# Tests', () => {
|
|
test('P0: Create VRL function with #ResultArray# marker', { tag: ['@smoke', '@P0', '@vrlResultArray'] }, async ({ page }) => {
|
|
testLogger.info('Test: Create VRL function with #ResultArray# marker');
|
|
|
|
const functionName = `vrl_result_array_${Date.now()}`;
|
|
const vrlCode = `#ResultArray#
|
|
.new_field = "processed"
|
|
.source = "vrl"
|
|
[., .]`;
|
|
|
|
// Create VRL function with #ResultArray#
|
|
await pm.functionsPage.createVRLFunction(functionName, vrlCode);
|
|
testLogger.info(`✅ VRL function with #ResultArray# created: ${functionName}`);
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
|
|
test('P0: VRL #ResultArray# - Multiple row expansion (1 input -> 3 outputs)', { tag: ['@smoke', '@P0', '@vrlResultArray'] }, async ({ page }) => {
|
|
testLogger.info('Test: VRL #ResultArray# with multiple row expansion');
|
|
|
|
const functionName = `vrl_array_expand_${Date.now()}`;
|
|
const vrlCode = `#ResultArray#
|
|
.result_type = "expanded"
|
|
[
|
|
merge(., {"index": 0}),
|
|
merge(., {"index": 1}),
|
|
merge(., {"index": 2})
|
|
]`;
|
|
|
|
await pm.functionsPage.clickAddFunctionButton();
|
|
await pm.functionsPage.fillFunctionName(functionName);
|
|
await pm.functionsPage.selectVRLType();
|
|
await pm.functionsPage.enterFunctionCode(vrlCode);
|
|
|
|
// Test the function with sample data
|
|
const testEvent = '{"name": "test", "value": 100}';
|
|
await pm.functionsPage.clickTestButton();
|
|
await pm.functionsPage.enterTestEvent(testEvent);
|
|
|
|
const runSuccess = await pm.functionsPage.clickRunTestButton();
|
|
if (runSuccess) {
|
|
const output = await pm.functionsPage.getTestOutput();
|
|
testLogger.info(`VRL array expansion output: ${output}`);
|
|
|
|
// Verify output contains array elements (looking for index values)
|
|
if (output.includes('index') && (output.includes('0') || output.includes('1') || output.includes('2'))) {
|
|
testLogger.info('✅ VRL #ResultArray# successfully expanded to multiple rows');
|
|
} else {
|
|
testLogger.warn('⚠️ Output format may need verification');
|
|
}
|
|
}
|
|
|
|
// Save and cleanup
|
|
await pm.functionsPage.clickSaveButton();
|
|
await page.waitForTimeout(2000);
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
|
|
test('P1: Verify #ResultArray# marker preserved after save (VRL)', { tag: ['@P1', '@vrlResultArray'] }, async ({ page }) => {
|
|
testLogger.info('Test: Verify VRL #ResultArray# marker is preserved');
|
|
|
|
const functionName = `vrl_preserve_marker_${Date.now()}`;
|
|
const vrlCode = `#ResultArray#
|
|
.preserved = true
|
|
[., .]`;
|
|
|
|
// Create function
|
|
await pm.functionsPage.createVRLFunction(functionName, vrlCode);
|
|
testLogger.info('VRL function with #ResultArray# saved');
|
|
|
|
// Navigate away and back
|
|
await page.goto(`${process.env.ZO_BASE_URL}/web/logs?org_identifier=${process.env.ORGID}`);
|
|
await page.waitForLoadState('networkidle');
|
|
await pm.functionsPage.navigate();
|
|
|
|
// Open function and check if #ResultArray# is still there
|
|
await pm.functionsPage.searchFunction(functionName);
|
|
await pm.functionsPage.clickFunctionByName(functionName);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Check editor content contains #ResultArray#
|
|
const editorContent = await page.locator('[data-test="logs-vrl-function-editor"]').textContent();
|
|
if (editorContent && editorContent.includes('#ResultArray#')) {
|
|
testLogger.info('✅ #ResultArray# marker preserved in VRL function');
|
|
} else {
|
|
testLogger.warn('⚠️ #ResultArray# marker may not be visible in editor');
|
|
}
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.clickCancelButton();
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
|
|
test('P2: VRL #ResultArray# - Empty array return', { tag: ['@P2', '@vrlResultArray', '@edgeCases'] }, async ({ page }) => {
|
|
testLogger.info('Test: VRL #ResultArray# with empty array');
|
|
|
|
const functionName = `vrl_empty_array_${Date.now()}`;
|
|
const vrlCode = `#ResultArray#
|
|
# Return empty array based on condition
|
|
if .filter_out == true {
|
|
[]
|
|
} else {
|
|
[.]
|
|
}`;
|
|
|
|
await pm.functionsPage.clickAddFunctionButton();
|
|
await pm.functionsPage.fillFunctionName(functionName);
|
|
await pm.functionsPage.selectVRLType();
|
|
await pm.functionsPage.enterFunctionCode(vrlCode);
|
|
|
|
// Test with filter_out=true to get empty array
|
|
const testEvent = '{"filter_out": true, "name": "test"}';
|
|
await pm.functionsPage.clickTestButton();
|
|
await pm.functionsPage.enterTestEvent(testEvent);
|
|
|
|
const runSuccess = await pm.functionsPage.clickRunTestButton();
|
|
if (runSuccess) {
|
|
const output = await pm.functionsPage.getTestOutput();
|
|
testLogger.info(`VRL empty array output: ${output}`);
|
|
testLogger.info('✅ VRL #ResultArray# handles empty array case');
|
|
}
|
|
|
|
// Cancel without saving
|
|
await pm.functionsPage.clickCancelButton();
|
|
});
|
|
|
|
test('P2: VRL #ResultArray# - Single element array', { tag: ['@P2', '@vrlResultArray', '@edgeCases'] }, async ({ page }) => {
|
|
testLogger.info('Test: VRL #ResultArray# with single element array');
|
|
|
|
const functionName = `vrl_single_array_${Date.now()}`;
|
|
const vrlCode = `#ResultArray#
|
|
.single_element = true
|
|
[.]`;
|
|
|
|
await pm.functionsPage.createVRLFunction(functionName, vrlCode);
|
|
testLogger.info('✅ VRL #ResultArray# with single element created');
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
});
|
|
|
|
test.describe('JavaScript #ResultArray# Tests', () => {
|
|
test('P0: Create JS function with #ResultArray# marker', { tag: ['@smoke', '@P0', '@jsResultArray'] }, async ({ page }) => {
|
|
testLogger.info('Test: Create JavaScript function with #ResultArray# marker');
|
|
|
|
const functionName = `js_result_array_${Date.now()}`;
|
|
const jsCode = `#ResultArray#
|
|
rows[0].new_field = "processed";
|
|
rows[0].source = "javascript";
|
|
rows;`;
|
|
|
|
// Create JS function with #ResultArray#
|
|
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
|
|
testLogger.info(`✅ JavaScript function with #ResultArray# created: ${functionName}`);
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
|
|
test('P0: JS #ResultArray# - Multiple row expansion (1 input -> 3 outputs)', { tag: ['@smoke', '@P0', '@jsResultArray'] }, async ({ page }) => {
|
|
testLogger.info('Test: JavaScript #ResultArray# with multiple row expansion');
|
|
|
|
const functionName = `js_array_expand_${Date.now()}`;
|
|
const jsCode = `#ResultArray#
|
|
// With #ResultArray#, use 'rows' variable (array input)
|
|
// Expand: take first row and create 3 copies with different indices
|
|
if (rows.length > 0) {
|
|
var firstRow = rows[0];
|
|
rows.length = 0; // Clear array
|
|
for (var i = 0; i < 3; i++) {
|
|
var newRow = {};
|
|
for (var key in firstRow) {
|
|
newRow[key] = firstRow[key];
|
|
}
|
|
newRow.index = i;
|
|
newRow.expanded = true;
|
|
rows.push(newRow);
|
|
}
|
|
}`;
|
|
|
|
await pm.functionsPage.clickAddFunctionButton();
|
|
await pm.functionsPage.fillFunctionName(functionName);
|
|
await pm.functionsPage.selectJavaScriptType();
|
|
await pm.functionsPage.enterFunctionCode(jsCode);
|
|
|
|
// Test the function with sample data
|
|
const testEvent = '{"name": "test", "value": 100}';
|
|
await pm.functionsPage.clickTestButton();
|
|
await pm.functionsPage.enterTestEvent(testEvent);
|
|
|
|
const runSuccess = await pm.functionsPage.clickRunTestButton();
|
|
if (runSuccess) {
|
|
const output = await pm.functionsPage.getTestOutput();
|
|
testLogger.info(`JavaScript array expansion output: ${output}`);
|
|
|
|
// Verify output contains array elements
|
|
if (output.includes('index') && output.includes('expanded')) {
|
|
testLogger.info('✅ JavaScript #ResultArray# successfully expanded to multiple rows');
|
|
} else {
|
|
testLogger.warn('⚠️ Output format may need verification');
|
|
}
|
|
}
|
|
|
|
// Save and cleanup
|
|
await pm.functionsPage.clickSaveButton();
|
|
await page.waitForTimeout(2000);
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
|
|
test('P1: Verify #ResultArray# marker preserved after save (JS)', { tag: ['@P1', '@jsResultArray'] }, async ({ page }) => {
|
|
testLogger.info('Test: Verify JavaScript #ResultArray# marker is preserved');
|
|
|
|
const functionName = `js_preserve_marker_${Date.now()}`;
|
|
const jsCode = `#ResultArray#
|
|
// With #ResultArray#, use 'rows' variable
|
|
for (var i = 0; i < rows.length; i++) {
|
|
rows[i].preserved = true;
|
|
}`;
|
|
|
|
// Create function
|
|
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
|
|
testLogger.info('JavaScript function with #ResultArray# saved');
|
|
|
|
// Navigate away and back
|
|
await page.goto(`${process.env.ZO_BASE_URL}/web/logs?org_identifier=${process.env.ORGID}`);
|
|
await page.waitForLoadState('networkidle');
|
|
await pm.functionsPage.navigate();
|
|
|
|
// Open function and check type and #ResultArray# marker
|
|
const functionType = await pm.functionsPage.openFunctionAndCheckType(functionName);
|
|
expect(functionType).toBe('js');
|
|
testLogger.info('✅ JavaScript type verified');
|
|
|
|
// Check editor content contains #ResultArray#
|
|
const editorContent = await page.locator('[data-test="logs-vrl-function-editor"]').textContent();
|
|
if (editorContent && editorContent.includes('#ResultArray#')) {
|
|
testLogger.info('✅ #ResultArray# marker preserved in JavaScript function');
|
|
} else {
|
|
testLogger.warn('⚠️ #ResultArray# marker may not be visible in editor');
|
|
}
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.clickCancelButton();
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
|
|
test('P2: JS #ResultArray# - Empty array return', { tag: ['@P2', '@jsResultArray', '@edgeCases'] }, async ({ page }) => {
|
|
testLogger.info('Test: JavaScript #ResultArray# with empty array');
|
|
|
|
const functionName = `js_empty_array_${Date.now()}`;
|
|
const jsCode = `#ResultArray#
|
|
// With #ResultArray#, use 'rows' variable
|
|
// Filter out rows based on condition
|
|
var filtered = [];
|
|
for (var i = 0; i < rows.length; i++) {
|
|
if (rows[i].filter_out !== true) {
|
|
filtered.push(rows[i]);
|
|
}
|
|
}
|
|
rows.length = 0;
|
|
for (var i = 0; i < filtered.length; i++) {
|
|
rows.push(filtered[i]);
|
|
}`;
|
|
|
|
await pm.functionsPage.clickAddFunctionButton();
|
|
await pm.functionsPage.fillFunctionName(functionName);
|
|
await pm.functionsPage.selectJavaScriptType();
|
|
await pm.functionsPage.enterFunctionCode(jsCode);
|
|
|
|
// Test with filter_out=true to get empty array
|
|
const testEvent = '{"filter_out": true, "name": "test"}';
|
|
await pm.functionsPage.clickTestButton();
|
|
await pm.functionsPage.enterTestEvent(testEvent);
|
|
|
|
const runSuccess = await pm.functionsPage.clickRunTestButton();
|
|
if (runSuccess) {
|
|
const output = await pm.functionsPage.getTestOutput();
|
|
testLogger.info(`JavaScript empty array output: ${output}`);
|
|
testLogger.info('✅ JavaScript #ResultArray# handles empty array case');
|
|
}
|
|
|
|
// Cancel without saving
|
|
await pm.functionsPage.clickCancelButton();
|
|
});
|
|
|
|
test('P2: JS #ResultArray# - Single element array', { tag: ['@P2', '@jsResultArray', '@edgeCases'] }, async ({ page }) => {
|
|
testLogger.info('Test: JavaScript #ResultArray# with single element array');
|
|
|
|
const functionName = `js_single_array_${Date.now()}`;
|
|
const jsCode = `#ResultArray#
|
|
// With #ResultArray#, use 'rows' variable
|
|
for (var i = 0; i < rows.length; i++) {
|
|
rows[i].single_element = true;
|
|
}`;
|
|
|
|
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
|
|
testLogger.info('✅ JavaScript #ResultArray# with single element created');
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
|
|
test('P1: JS #ResultArray# - Complex transformation with map', { tag: ['@P1', '@jsResultArray'] }, async ({ page }) => {
|
|
testLogger.info('Test: JavaScript #ResultArray# with complex array transformation');
|
|
|
|
const functionName = `js_complex_array_${Date.now()}`;
|
|
const jsCode = `#ResultArray#
|
|
// With #ResultArray#, use 'rows' variable
|
|
// Create multiple derived rows from each input row
|
|
var statuses = ['pending', 'processing', 'completed'];
|
|
var expanded = [];
|
|
for (var i = 0; i < rows.length; i++) {
|
|
for (var j = 0; j < statuses.length; j++) {
|
|
expanded.push({
|
|
original_id: rows[i].id,
|
|
status: statuses[j],
|
|
timestamp: Date.now(),
|
|
source: 'javascript'
|
|
});
|
|
}
|
|
}
|
|
rows.length = 0;
|
|
for (var i = 0; i < expanded.length; i++) {
|
|
rows.push(expanded[i]);
|
|
}`;
|
|
|
|
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
|
|
testLogger.info('✅ JavaScript #ResultArray# with complex transformation created');
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
});
|
|
|
|
test.describe('Cross-Language Comparison', () => {
|
|
test('P1: Compare VRL and JS #ResultArray# output', { tag: ['@P1', '@comparison'] }, async ({ page }) => {
|
|
testLogger.info('Test: Compare VRL and JavaScript #ResultArray# behavior');
|
|
|
|
const timestamp = Date.now();
|
|
const vrlFunctionName = `vrl_compare_${timestamp}`;
|
|
const jsFunctionName = `js_compare_${timestamp}`;
|
|
|
|
// Create VRL version
|
|
const vrlCode = `#ResultArray#
|
|
[
|
|
merge(., {"type": "vrl", "index": 0}),
|
|
merge(., {"type": "vrl", "index": 1})
|
|
]`;
|
|
await pm.functionsPage.createVRLFunction(vrlFunctionName, vrlCode);
|
|
testLogger.info('Created VRL comparison function');
|
|
|
|
// Create JS version (using 'rows' for #ResultArray#)
|
|
const jsCode = `#ResultArray#
|
|
// With #ResultArray#, use 'rows' variable
|
|
for (var i = 0; i < rows.length; i++) {
|
|
rows[i].type = "javascript";
|
|
rows[i].index = i;
|
|
}`;
|
|
await pm.functionsPage.createJavaScriptFunction(jsFunctionName, jsCode);
|
|
testLogger.info('Created JavaScript comparison function');
|
|
|
|
testLogger.info('✅ Both VRL and JavaScript #ResultArray# functions created for comparison');
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.deleteFunctionByName(vrlFunctionName);
|
|
await pm.functionsPage.deleteFunctionByName(jsFunctionName);
|
|
});
|
|
});
|
|
|
|
test.describe('Pipeline Integration', () => {
|
|
test('P1: #ResultArray# function in pipeline context', { tag: ['@P1', '@pipelineIntegration'] }, async ({ page }) => {
|
|
testLogger.info('Test: #ResultArray# function integration with pipelines');
|
|
|
|
const functionName = `pipeline_array_fn_${Date.now()}`;
|
|
const jsCode = `#ResultArray#
|
|
// With #ResultArray#, use 'rows' variable
|
|
// Expand each log entry into multiple events
|
|
var events = ['login', 'action', 'logout'];
|
|
var expanded = [];
|
|
for (var i = 0; i < rows.length; i++) {
|
|
for (var j = 0; j < events.length; j++) {
|
|
var newRow = {};
|
|
for (var key in rows[i]) {
|
|
newRow[key] = rows[i][key];
|
|
}
|
|
newRow.event_type = events[j];
|
|
newRow.pipeline_processed = true;
|
|
expanded.push(newRow);
|
|
}
|
|
}
|
|
rows.length = 0;
|
|
for (var i = 0; i < expanded.length; i++) {
|
|
rows.push(expanded[i]);
|
|
}`;
|
|
|
|
await pm.functionsPage.createJavaScriptFunction(functionName, jsCode);
|
|
testLogger.info('✅ #ResultArray# function created for pipeline integration');
|
|
|
|
// Note: Full pipeline integration test would require:
|
|
// 1. Create a pipeline with this function
|
|
// 2. Send test data through the pipeline
|
|
// 3. Verify multiple output rows are generated
|
|
// This is documented for future pipeline E2E tests
|
|
|
|
// Cleanup
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
});
|
|
});
|
|
|
|
test.describe('Error Handling', () => {
|
|
test('P2: #ResultArray# with invalid return value', { tag: ['@P2', '@errorHandling'] }, async ({ page }) => {
|
|
testLogger.info('Test: #ResultArray# error handling for non-array return');
|
|
|
|
const functionName = `invalid_array_${Date.now()}`;
|
|
const jsCode = `#ResultArray#
|
|
// With #ResultArray#, use 'rows' variable
|
|
// This is valid - just marks rows as invalid
|
|
for (var i = 0; i < rows.length; i++) {
|
|
rows[i].invalid = true;
|
|
}`;
|
|
|
|
await pm.functionsPage.clickAddFunctionButton();
|
|
await pm.functionsPage.fillFunctionName(functionName);
|
|
await pm.functionsPage.selectJavaScriptType();
|
|
await pm.functionsPage.enterFunctionCode(jsCode);
|
|
|
|
// Try to save - may succeed (validation at runtime)
|
|
await pm.functionsPage.clickSaveButton();
|
|
await page.waitForTimeout(2000);
|
|
|
|
testLogger.info('✅ Function created - validation occurs at runtime');
|
|
|
|
// Cleanup
|
|
const cancelVisible = await page.locator('[data-test="add-function-cancel-btn"]').isVisible({ timeout: 2000 }).catch(() => false);
|
|
if (cancelVisible) {
|
|
await pm.functionsPage.clickCancelButton();
|
|
} else {
|
|
await pm.functionsPage.deleteFunctionByName(functionName);
|
|
}
|
|
});
|
|
});
|
|
});
|