fix: Updated UI unit test setup (#6520)

Co-authored-by: Sai Nikhil <nikhil@openobserve.ai>
This commit is contained in:
Omkar Kesarkhane
2025-05-30 13:21:40 +05:30
committed by GitHub
co-authored by Sai Nikhil
parent c8bee3ff9f
commit 666dfcdb62
89 changed files with 6134 additions and 3973 deletions
+7 -7
View File
@@ -245,7 +245,7 @@ export class LogsPage {
async kubernetesContainerNameJoin() {
await this.page
.locator('[data-test="logs-search-bar-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator(".inputarea")
.fill('SELECT a.kubernetes_container_name , b.kubernetes_container_name FROM "default" as a join "e2e_automate" as b on a.kubernetes_container_name = b.kubernetes_container_name');
await this.page.waitForTimeout(5000);
}
@@ -253,7 +253,7 @@ export class LogsPage {
async kubernetesContainerNameJoinLimit() {
await this.page
.locator('[data-test="logs-search-bar-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator(".inputarea")
.fill('SELECT a.kubernetes_container_name , b.kubernetes_container_name FROM "default" as a join "e2e_automate" as b on a.kubernetes_container_name = b.kubernetes_container_name LIMIT 10');
await this.page.waitForTimeout(5000);
}
@@ -262,7 +262,7 @@ export class LogsPage {
async kubernetesContainerNameJoinLike() {
await this.page
.locator('[data-test="logs-search-bar-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator(".inputarea")
.fill('SELECT a.kubernetes_container_name , b.kubernetes_container_name FROM "default" as a join "e2e_automate" as b on a.kubernetes_container_name = b.kubernetes_container_name WHERE a.kubernetes_container_name LIKE "%ziox%"');
await this.page.waitForTimeout(5000);
}
@@ -270,7 +270,7 @@ export class LogsPage {
async kubernetesContainerNameLeftJoin() {
await this.page
.locator('[data-test="logs-search-bar-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator(".inputarea")
.fill('SELECT a.kubernetes_container_name , b.kubernetes_container_name FROM "default" as a LEFT JOIN "e2e_automate" as b on a.kubernetes_container_name = b.kubernetes_container_name');
await this.page.waitForTimeout(5000);
}
@@ -278,7 +278,7 @@ export class LogsPage {
async kubernetesContainerNameRightJoin() {
await this.page
.locator('[data-test="logs-search-bar-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator(".inputarea")
.fill('SELECT a.kubernetes_container_name , b.kubernetes_container_name FROM "default" as a RIGHT JOIN "e2e_automate" as b on a.kubernetes_container_name = b.kubernetes_container_name');
await this.page.waitForTimeout(5000);
}
@@ -286,7 +286,7 @@ export class LogsPage {
async kubernetesContainerNameFullJoin() {
await this.page
.locator('[data-test="logs-search-bar-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator(".inputarea")
.fill('SELECT a.kubernetes_container_name , b.kubernetes_container_name FROM "default" as a FULL JOIN "e2e_automate" as b on a.kubernetes_container_name = b.kubernetes_container_name');
await this.page.waitForTimeout(5000);
}
@@ -466,7 +466,7 @@ async selectIndexStreamDefault() {
}
async clearAndFillQueryEditor(query) {
const editor = this.page.locator('[data-test="logs-search-bar-query-editor"]').getByLabel('Editor content;Press Alt+F1');
const editor = this.page.locator('[data-test="logs-search-bar-query-editor"]').locator(".inputarea");
await editor.fill(''); // Clear the editor
await this.page.waitForTimeout(1000); // Optional: adjust or remove as per your needs
await editor.fill(query); // Fill with the new query
@@ -95,7 +95,7 @@ test.describe("Sanity Tests", () => { let dashboardPage
await page.waitForTimeout(1000);
await page.locator('[data-test="dashboard-panel-error-bar-icon"]').click();
await page.locator('[data-test="dashboard-panel-query-editor"]').getByRole("textbox", { name: "Editor content;Press Alt+F1" }).fill('select * from "e2e_automate"');
await page.locator('[data-test="dashboard-panel-query-editor"]').locator('.inputarea').fill('select * from "e2e_automate"');
await page.locator('[data-test="dashboard-apply"]').click();
await page.waitForTimeout(3000);
await expect(page.getByText("Unsafe code detected: Access to 'document' is not allowed")).toBeVisible();
@@ -114,7 +114,7 @@ test("Add line JSON in Monaco Editor", async ({ page }) => {
await page.waitForTimeout(1000);
await page.locator('[data-test="dashboard-panel-error-bar-icon"]').click();
await page.locator('[data-test="dashboard-panel-query-editor"]').getByRole("textbox", { name: "Editor content;Press Alt+F1" }).fill('select * from "e2e_automate"');
await page.locator('[data-test="dashboard-panel-query-editor"]').locator('.inputarea').fill('select * from "e2e_automate"');
await page.waitForTimeout(3000);
await page.locator('[data-test="dashboard-apply"]').click();
await page.waitForTimeout(3000);
@@ -405,7 +405,7 @@ test.describe("dashboard UI testcases", () => {
.click();
await page
.locator('[data-test="dashboard-vrl-function-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrl=100");
await page.waitForTimeout(2000);
@@ -484,7 +484,7 @@ test.describe("dashboard UI testcases", () => {
.click();
await page
.locator('[data-test="dashboard-vrl-function-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrl=100");
await page.locator('[data-test="selected-chart-table-item"] img').click();
@@ -305,7 +305,7 @@ test.describe("dashboard UI testcases", () => {
await page.locator(".view-line").first().click();
await page
.locator('[data-test="dashboard-panel-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(
'SELECT histogram(_timestamp) as "x_axis_1", count(_timestamp) as "y_axis_1", kubernetes_container_name as "breakdown_1" FROM "e2e_automate" GROUP BY x_axis_1, breakdown_1'
);
@@ -131,7 +131,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrl12=123");
await page.waitForTimeout(3000);
@@ -181,7 +181,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrL=1000");
await page.waitForTimeout(3000);
@@ -240,7 +240,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".VRL=1000");
await page.waitForTimeout(3000);
@@ -326,7 +326,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrl=11abc");
await page.waitForTimeout(2000);
@@ -338,11 +338,11 @@ test.describe(" visualize UI testcases", () => {
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.press("Control+a");
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrl=123");
await page.waitForTimeout(3000);
@@ -392,7 +392,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrl=100");
await page.waitForTimeout(3000);
@@ -432,7 +432,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrlsanity=100");
await page.waitForTimeout(3000);
@@ -451,7 +451,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.press("Control+a");
await page.locator('[data-test="logs-search-bar-refresh-btn"]').click();
await page.locator('[data-test="logs-search-bar-refresh-btn"]').click();
@@ -471,7 +471,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".vrl=123");
await page.waitForTimeout(3000);
@@ -527,7 +527,7 @@ test.describe(" visualize UI testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".VRLsanity=1000");
await page.waitForTimeout(3000);
@@ -403,7 +403,7 @@ test.describe(" visualize UI testcases", () => {
// Enter an invalid query into the search bar
await page
.locator('[data-test="logs-search-bar-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill("select from user whare ID =1");
// Refresh the search
@@ -214,8 +214,9 @@ test.describe("Enrichment data testcases", () => {
}
// Explore the file
await page.getByRole("button", { name: "Explore" }).click();
await page.locator("#fnEditor").getByLabel("Editor content;Press Alt+F1")
await page.locator(`[data-test="${fileName}-explore-btn"]`).click();
await page.waitForSelector('[data-test="logs-vrl-function-editor"]');
await page.locator('[data-test="logs-vrl-function-editor"]').locator('.inputarea')
.fill(`
abc, err = get_enrichment_table_record("${fileName}", {
"protocol_number": to_string!(.protocol_number)
@@ -124,7 +124,7 @@ test.describe("Logs UI testcases", () => {
await applyQueryButton(page);
await page.locator('#fnEditor').getByLabel('Editor content;Press Alt+F1').fill('.a=2');
await page.locator('#fnEditor').locator('.inputarea').fill('.a=2');
await page.waitForTimeout(1000);
await applyQueryButton(page);
const warningElement = page.locator('text=warning Query execution');
@@ -401,7 +401,7 @@ test.describe("Logs UI testcases", () => {
await page.locator('[data-test="logs-search-bar-refresh-btn"]').click();
await page.locator('[data-test="logs-search-bar-function-dropdown"] button').filter({ hasText: 'save' }).click();
await page.locator('#fnEditor > .monaco-editor > .overflow-guard > .monaco-scrollable-element > .lines-content > .view-lines > .view-line').click();
await page.locator('#fnEditor').getByLabel('Editor content;Press Alt+F1').fill('.a=2');
await page.locator('#fnEditor').locator('.inputarea').fill('.a=2');
await page.waitForTimeout(1000);
await page.locator('[data-test="logs-search-bar-function-dropdown"] button').filter({ hasText: 'save' }).click();
await page.locator('[data-test="saved-function-name-input"]').click();
@@ -420,7 +420,7 @@ test.describe("Logs UI testcases", () => {
test('should display click save directly while creating a function', async ({ page }) => {
await page.waitForTimeout(1000);
await page.locator('#fnEditor').getByLabel('Editor content;Press Alt+F1').fill('.a=2');
await page.locator('#fnEditor').locator('.inputarea').fill('.a=2');
await page.waitForTimeout(1000);
await page.locator('[data-test="logs-search-bar-function-dropdown"] button').filter({ hasText: 'save' }).click();
await page.locator('[data-test="saved-view-dialog-save-btn"]').click();
@@ -429,7 +429,7 @@ test.describe("Logs UI testcases", () => {
test('should display error on adding only blank spaces under function name', async ({ page }) => {
await page.waitForTimeout(1000);
await page.locator('#fnEditor').getByLabel('Editor content;Press Alt+F1').fill('.a=2');
await page.locator('#fnEditor').locator('.inputarea').fill('.a=2');
await page.waitForTimeout(1000);
await page.locator('[data-test="logs-search-bar-function-dropdown"] button').filter({ hasText: 'save' }).click();
await page.locator('[data-test="saved-function-name-input"]').fill(' ');
@@ -440,7 +440,7 @@ test.describe("Logs UI testcases", () => {
test('should display error on adding invalid characters under function name', async ({ page }) => {
await page.waitForTimeout(1000);
await page.locator('#fnEditor').getByLabel('Editor content;Press Alt+F1').fill('.a=2');
await page.locator('#fnEditor').locator('.inputarea').fill('.a=2');
await page.waitForTimeout(1000);
await page.locator('[data-test="logs-search-bar-function-dropdown"] button').filter({ hasText: 'save' }).click();
await page.locator('[data-test="saved-function-name-input"]').fill('e2e@@@');
@@ -450,7 +450,7 @@ test.describe("Logs UI testcases", () => {
test('should display added function on switching between tabs and again navigate to log', async ({ page }) => {
await page.waitForTimeout(1000);
await page.locator('#fnEditor').getByLabel('Editor content;Press Alt+F1').fill('.a=2');
await page.locator('#fnEditor').locator('.inputarea').fill('.a=2');
await page.waitForTimeout(1000);
await page.locator('[data-test="logs-search-bar-refresh-btn"]').click();
await page.locator('[data-test="menu-link-\\/metrics-item"]').click();
@@ -496,7 +496,7 @@ test.describe("Logs UI testcases", () => {
test.skip('should display results for search around after adding function', async ({ page }) => {
await page.waitForTimeout(1000);
await page.locator('#fnEditor > .monaco-editor > .overflow-guard > .monaco-scrollable-element > .lines-content > .view-lines > .view-line').click();
await page.locator('#fnEditor').getByLabel('Editor content;Press Alt+F1').fill('.a=1');
await page.locator('#fnEditor').locator('.inputarea').fill('.a=1');
await page.waitForTimeout(1000);
await page.locator('[data-test="logs-search-bar-refresh-btn"]').click();
await page.locator('[data-test="log-table-column-3-source"]').getByText('{"_timestamp":').click();
@@ -134,7 +134,7 @@ async function multistreamselect(page) {
test("should add a function and display it in streams", async ({ page }) => {
await multistreamselect(page);
await page.locator('#fnEditor').getByLabel('Editor content;Press Alt+F1').fill('.a=2');
await page.locator('#fnEditor').locator('.inputarea').fill('.a=2');
await page.waitForTimeout(1000);
await applyQueryButton(page);
await page
@@ -233,7 +233,7 @@ test.describe("Sanity testcases", () => {
.click();
await page
.locator('[data-test="logs-search-bar-query-editor"]')
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill('SELECT * FROM "e2e_automate" ORDER BY _timestamp DESC limit 5');
await page.waitForTimeout(2000);
await page
@@ -265,7 +265,7 @@ test.describe("Sanity testcases", () => {
.click();
await page
.locator("#fnEditor")
.getByLabel("Editor content;Press Alt+F1")
.locator('.inputarea')
.fill(".a=2");
await page.waitForTimeout(1000);
await page
@@ -300,13 +300,13 @@ test.describe("Sanity testcases", () => {
await page.getByLabel("Name").click();
await page.getByLabel("Name").fill("sanitytest");
await page.locator('[data-test="logs-vrl-function-editor"]').click();
await page.locator('[data-test="logs-vrl-function-editor"]').getByLabel("Editor content;Press Alt+F1").fill("sanity=1");
await page.locator('[data-test="logs-vrl-function-editor"]').locator('.inputarea').fill("sanity=1");
await page.locator('[data-test="logs-vrl-function-editor"]').getByText("sanity=").click();
await page.locator('[data-test="logs-vrl-function-editor"]').getByLabel("Editor content;Press Alt+F1").press("ArrowLeft");
await page.locator('[data-test="logs-vrl-function-editor"]').getByLabel("Editor content;Press Alt+F1").press("ArrowLeft");
await page.locator('[data-test="logs-vrl-function-editor"]').getByLabel("Editor content;Press Alt+F1").press("ArrowLeft");
await page.locator('[data-test="logs-vrl-function-editor"]').getByLabel("Editor content;Press Alt+F1").press("ArrowLeft");
await page.locator('[data-test="logs-vrl-function-editor"]').getByLabel("Editor content;Press Alt+F1").fill(".sanity=1");
await page.locator('[data-test="logs-vrl-function-editor"]').locator('.inputarea').press("ArrowLeft");
await page.locator('[data-test="logs-vrl-function-editor"]').locator('.inputarea').press("ArrowLeft");
await page.locator('[data-test="logs-vrl-function-editor"]').locator('.inputarea').press("ArrowLeft");
await page.locator('[data-test="logs-vrl-function-editor"]').locator('.inputarea').press("ArrowLeft");
await page.locator('[data-test="logs-vrl-function-editor"]').locator('.inputarea').fill(".sanity=1");
await page.getByRole("button", { name: "Save" }).click();
await page.getByPlaceholder("Search Function").click();
await page.getByPlaceholder("Search Function").fill("sanity");
+2914 -2804
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -79,6 +79,7 @@
"@types/axios": "^0.14.4",
"@types/d3-scale": "^4.0.8",
"@types/dompurify": "^3.2.0",
"@types/fs-extra": "^11.0.4",
"@types/js-cookie": "^3.0.6",
"@types/jsdom": "^21.1.7",
"@types/leaflet": "^1.9.16",
@@ -88,7 +89,7 @@
"@types/uuid": "^10.0.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vitest/coverage-v8": "^3.0.5",
"@vitest/coverage-v8": "^3.1.1",
"@vitest/ui": "^3.0.5",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.3.0",
@@ -106,7 +107,7 @@
"fs-extra": "^11.3.0",
"happy-dom": "^16.7.2",
"jsdom": "^26.0.0",
"msw": "^2.7.0",
"msw": "^2.7.3",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.39",
"prettier": "^3.4.2",
@@ -120,9 +121,8 @@
"vite": "^6.3.4",
"vite-plugin-istanbul": "^6.0.2",
"vite-plugin-monaco-editor": "^1.1.0",
"vitest": "^3.0.5",
"vitest": "^3.1.1",
"vitest-canvas-mock": "^0.3.3",
"vue-tsc": "^2.2.0",
"whatwg-fetch": "^3.6.20"
"vue-tsc": "^2.2.0"
}
}
@@ -1,8 +1,23 @@
// 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/>.
import { DOMWrapper, flushPromises, mount } from "@vue/test-utils";
import { describe, expect, it, beforeEach } from "vitest";
import { installQuasar } from "../helpers/install-quasar-plugin";
import ConfirmDialog from "../../../components/ConfirmDialog.vue";
import i18n from "../../../locales";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import ConfirmDialog from "@/components/ConfirmDialog.vue";
import i18n from "@/locales";
import { Dialog } from "quasar";
installQuasar({
@@ -1,7 +1,22 @@
// 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/>.
import { mount } from "@vue/test-utils";
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
import { installQuasar } from "../helpers/install-quasar-plugin";
import HighLight from "../../../components/HighLight.vue";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import HighLight from "@/components/HighLight.vue";
installQuasar();
@@ -1,9 +1,24 @@
// 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/>.
import { mount } from "@vue/test-utils";
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
import { installQuasar } from "../helpers/install-quasar-plugin";
import MenuLink from "../../../components/MenuLink.vue";
import i18n from "../../../locales";
import store from "../helpers/store";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import MenuLink from "@/components/MenuLink.vue";
import i18n from "@/locales";
import store from "@/test/unit/helpers/store";
installQuasar();
File diff suppressed because it is too large Load Diff
+7 -2
View File
@@ -53,7 +53,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
<div class="row justify-start items-start" style="width: 1024px">
<div style="width: calc(100% - 401px)">
<q-form class="add-alert-form" ref="addAlertForm" @submit="onSubmit">
<q-form
class="add-alert-form"
data-test="add-alert-form"
ref="addAlertForm"
@submit="onSubmit"
>
<div
class="flex justify-start items-center q-pb-sm q-col-gutter-md flex-wrap"
>
@@ -1548,7 +1553,7 @@ export default defineComponent({
);
callAlert
.then((res: { data: any }) => {
this.formData = { ...defaultValue };
this.formData = { ...defaultValue() };
this.$emit("update:list", this.activeFolderId);
this.addAlertForm.resetValidation();
dismiss();
@@ -15,15 +15,15 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises, DOMWrapper } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import i18n from "@/locales";
import store from "../../helpers/store";
import store from "@/test/unit/helpers/store";
import DestinationService from "@/services/alert_destination";
// @ts-ignore
import { rest } from "msw";
import { AddDestination } from "@/components/alerts";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
installQuasar({
plugins: [Dialog, Notify],
@@ -15,15 +15,15 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises, DOMWrapper } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import i18n from "@/locales";
import store from "../../helpers/store";
import store from "@/test/unit/helpers/store";
import TemplateService from "@/services/alert_templates";
// @ts-ignore
import { rest } from "msw";
import { AddTemplate } from "@/components/alerts";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
installQuasar({
plugins: [Dialog, Notify],
@@ -15,15 +15,15 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises, DOMWrapper } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import AlertList from "@/components/alerts/AlertList.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import store from "@/test/unit/helpers/store";
// @ts-ignore
import { rest } from "msw";
import AlertService from "@/services/alerts";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
@@ -15,16 +15,16 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises, DOMWrapper } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import DestinationList from "@/components/alerts/AlertsDestinationList.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import store from "@/test/unit/helpers/store";
// @ts-ignore
import { rest } from "msw";
import destinationService from "@/services/alert_destination";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
@@ -15,15 +15,14 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises, DOMWrapper } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import TemplateList from "@/components/alerts/TemplateList.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
// @ts-ignore
import { rest } from "msw";
import TemplateList from "./TemplateList.vue";
import { http, HttpResponse } from "msw";
import templateService from "@/services/alert_templates";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import store from "@/test/unit/helpers/store";
import i18n from "@/locales";
const node = document.createElement("div");
node.setAttribute("id", "app");
@@ -103,7 +102,7 @@ describe("Alert List", async () => {
name: "alertTemplates",
query: {
action: "add",
org_identifier: "default_organization_01",
org_identifier: store.state.selectedOrganization.identifier,
},
});
});
@@ -113,29 +112,26 @@ describe("Alert List", async () => {
const deleteAlert = vi.spyOn(templateService, "delete");
beforeEach(async () => {
global.server.use(
rest.delete(
http.delete(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/alerts/templates/${template_name}`,
(req: any, res: any, ctx: any) => {
return res(ctx.status(200), ctx.json({ code: 200 }));
() => {
return HttpResponse.json({ code: 200 });
}
)
);
global.server.use(
rest.get(
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/templates`,
(req: any, res: any, ctx: any) => {
return res(
ctx.status(200),
ctx.json({
list: [
{
() => {
return HttpResponse.json({
list: [
{
name: "Template3",
body: '\r\n[\r\n {\r\n "labels": {\r\n "alertname": "{alert_name}",\r\n "stream": "{stream_name}",\r\n "organization": "{org_name}",\r\n "alerttype": "{alert_type}",\r\n "severity": "critical"\r\n },\r\n "annotations": {\r\n "timestamp": "{timestamp}"\r\n }\r\n }\r\n]',
isDefault: true,
},
],
})
);
});
}
)
);
+33 -38
View File
@@ -16,7 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template>
<q-page class="q-pa-none" style="min-height: inherit">
<div v-if=" !showImportTemplate && !showTemplateEditor">
<div v-if="!showImportTemplate && !showTemplateEditor">
<q-table
data-test="alert-templates-list-table"
ref="q-table"
@@ -118,14 +118,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
</div>
<div v-else>
<ImportTemplate
:templates="templates"
@update:templates="getTemplates"
/>
<ImportTemplate :templates="templates" @update:templates="getTemplates" />
</div>
<ConfirmDialog
title="Delete Template"
message="Are you sure you want to delete template?"
@@ -150,7 +145,7 @@ import { outlinedDelete } from "@quasar/extras/material-icons-outlined";
import ImportTemplate from "./ImportTemplate.vue";
const AddTemplate = defineAsyncComponent(
() => import("@/components/alerts/AddTemplate.vue")
() => import("@/components/alerts/AddTemplate.vue"),
);
const store = useStore();
@@ -203,11 +198,11 @@ onMounted(() => {
watch(
() => router.currentRoute.value.query.action,
(action) => {
if (!action){
if (!action) {
showTemplateEditor.value = false;
showImportTemplate.value = false;
};
}
}
},
);
const getTemplates = () => {
@@ -215,6 +210,7 @@ const getTemplates = () => {
spinner: true,
message: "Please wait while loading templates...",
});
templateService
.list({
org_identifier: store.state.selectedOrganization.identifier,
@@ -228,7 +224,7 @@ const getTemplates = () => {
})
.catch((err) => {
dismiss();
if(err.response.status != 403){
if (err.response.status !== 403) {
q.notify({
type: "negative",
message: "Error while pulling templates.",
@@ -244,10 +240,10 @@ const updateRoute = () => {
if (router.currentRoute.value.query.action === "add") editTemplate();
if (router.currentRoute.value.query.action === "update")
editTemplate(
getTemplateByName(router.currentRoute.value.query.name as string)
getTemplateByName(router.currentRoute.value.query.name as string),
);
if(router.currentRoute.value.query.action === "import") {
showImportTemplate.value = true
if (router.currentRoute.value.query.action === "import") {
showImportTemplate.value = true;
}
};
const getTemplateByName = (name: string) => {
@@ -317,13 +313,13 @@ const deleteTemplate = () => {
const importTemplate = () => {
showImportTemplate.value = true;
router.push({
name: "alertTemplates",
query: {
action: "import",
org_identifier: store.state.selectedOrganization.identifier,
},
});
}
name: "alertTemplates",
query: {
action: "import",
org_identifier: store.state.selectedOrganization.identifier,
},
});
};
const conformDeleteDestination = (destination: any) => {
confirmDelete.value.visible = true;
confirmDelete.value.data = destination;
@@ -354,24 +350,23 @@ const filterData = (rows: any, terms: any) => {
};
const exportTemplate = (row: any) => {
const findTemplate: any = getTemplateByName(row.name);
const templateByName = {...findTemplate}
if(templateByName.hasOwnProperty("#")) delete templateByName["#"];
const templateJson = JSON.stringify(templateByName,null,2);
const blob = new Blob([templateJson], { type: 'application/json' });
const url = URL.createObjectURL(blob);
// Create an anchor element to trigger the download
const link = document.createElement('a');
link.href = url;
const templateByName = { ...findTemplate };
if (templateByName.hasOwnProperty("#")) delete templateByName["#"];
const templateJson = JSON.stringify(templateByName, null, 2);
const blob = new Blob([templateJson], { type: "application/json" });
const url = URL.createObjectURL(blob);
// Create an anchor element to trigger the download
const link = document.createElement("a");
link.href = url;
// Set the filename of the download
link.download = `${templateByName.name}.json`;
// Set the filename of the download
link.download = `${templateByName.name}.json`;
// Trigger the download by simulating a click
link.click();
// Trigger the download by simulating a click
link.click();
// Clean up the URL object after download
URL.revokeObjectURL(url);
}
// Clean up the URL object after download
URL.revokeObjectURL(url);
};
</script>
<style lang=""></style>
@@ -36,6 +36,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template v-slot:body-cell-actions="props">
<q-td :props="props">
<q-btn
:data-test="`${props.row.name}-explore-btn`"
icon="search"
:title="t('logStream.explore')"
class="q-ml-xs"
@@ -1,12 +1,27 @@
// 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/>.
import { beforeEach, describe, expect, it } from "vitest";
import { mount } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { createStore } from "vuex";
import Curl from "@/components/ingestion/logs/Curl.vue";
installQuasar();
const store = createStore({
state: {
state: {
organizationPasscode: 11,
API_ENDPOINT: "http://localhost:8080",
},
@@ -1,6 +1,21 @@
// 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/>.
import { describe, expect, it, beforeEach } from "vitest";
import { flushPromises, mount } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { createStore } from "vuex";
import FluentBit from "@/components/ingestion/logs/FluentBit.vue";
@@ -1,6 +1,21 @@
// 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/>.
import { describe, expect, it, beforeEach } from "vitest";
import { mount } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { createStore } from "vuex";
import Fluentd from "@/components/ingestion/logs/Fluentd.vue";
@@ -1,6 +1,21 @@
// 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/>.
import { describe, expect, it, beforeEach } from "vitest";
import { mount } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { createStore } from "vuex";
import Vector from "@/components/ingestion/logs/Vector.vue";
+787
View File
@@ -0,0 +1,787 @@
// 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/>.
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import LogStream from "@/components/logstream/schema.vue";
import i18n from "@/locales";
// @ts-ignore
import store from "@/test/unit/helpers/store";
import StreamService from "@/services/stream";
installQuasar({
plugins: [Dialog, Notify],
});
describe("Streams", async () => {
let wrapper: any;
beforeEach(async () => {
vi.useFakeTimers();
const updateStream = vi.spyOn(StreamService, "updateSettings");
wrapper = mount(LogStream, {
props: {
modelValue: {
name: "k8s_json",
storage_type: "s3",
stream_type: "logs",
stats: {
doc_time_min: 1678448628630259,
doc_time_max: 1678448628652947,
doc_num: 400,
file_num: 1,
storage_size: 0.74,
compressed_size: 0.03,
},
schema: [
{
name: "_timestamp",
type: "Int64",
},
{
name: "kubernetes.container_hash",
type: "Utf8",
},
{
name: "log",
type: "Utf8",
},
{
name: "message",
type: "Utf8",
},
{
name: "test_this_field",
type: "Utf8",
},
],
settings: {
partition_time_level: "hourly",
partition_keys: {},
full_text_search_keys: ["level", "log"],
index_fields: [],
bloom_filter_fields: [],
distinct_value_fields: [],
data_retention: 3650,
max_query_range: 0,
store_original_data: false,
approx_partition: false,
index_updated_at: 1737970280285469,
extended_retention_days: [],
},
},
},
global: {
provide: {
store: store,
},
plugins: [i18n],
},
});
await flushPromises();
});
afterEach(() => {
wrapper.unmount();
vi.restoreAllMocks();
});
it("should display title", () => {
const pageTitle = wrapper.find('[data-test="schema-title-text"]');
expect(pageTitle.text()).toBe("Stream Detail");
});
it("Should display stream title", () => {
const streamTitle = wrapper.find('[data-test="schema-stream-title-text"]');
expect(streamTitle.text()).toBe("Stream Name k8s_json");
});
it("Should have cancel button", () => {
const cancelButton = wrapper.find('[data-test="schema-cancel-button"]');
expect(cancelButton.exists()).toBeTruthy();
expect(cancelButton.text()).toBe("Cancel");
});
it("Should have Update Settings button", () => {
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.exists()).toBeTruthy();
expect(updateSettingsButton.text()).toBe("Update Settings");
});
it("Should display stream fields mapping table", async () => {
const table = await wrapper.find(
'[data-test="schema-log-stream-field-mapping-table"]',
);
expect(table.exists()).toBeTruthy();
});
it("Should display stream fields mapping title", () => {
const table = wrapper.find(
'[data-test="schema-log-stream-mapping-title-text"]',
);
expect(table.text()).toBe(
"Mapping Default FTS keys used (no custom keys set).Store Original Data",
);
});
it("Should display stream fields mapping table headers", () => {
const tableHeaders = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find("thead")
.find("tr")
.findAll("th");
expect(tableHeaders[0].text()).toBe("");
expect(tableHeaders[1].text()).toBe("Fieldarrow_upward");
});
it.skip("Should display stream fields mapping table data", () => {
const tableData = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find("tbody")
.findAll("tr")[0]
.findAll("td");
expect(tableData[0].text()).toBe("");
expect(tableData[1].text()).toBe("_timestamp");
expect(tableData[2].text()).toBe("Int64");
});
// TODO : Check if we can update this test case
// - expect(logCheckbox.find(".q-checkbox__inner--truthy").exists()).toBeTruthy();
// + expect(wrapper.vm.ftsKeys.includes('log')).toBeTruthy();
describe("When user make changes and update settings", () => {
const updateStream = vi.spyOn(StreamService, "updateSettings");
vi.useFakeTimers();
let logPartition, timeStampCheckbox, updateSettingsButton: any;
beforeEach(async () => {
});
it("Should make api call when user updates the form and click update settings ", async () => {
// Find the q-toggle component wrapper
const toggleWrapper = await wrapper.find(
'[data-test="log-stream-store-original-data-toggle-btn"]',
);
// Verify initial state of the toggle
const checkBox = await toggleWrapper.find('input[type="checkbox"]');
expect(checkBox.element.checked).toBe(false);
// Simulate a click on the toggle
await checkBox.trigger("click");
await wrapper.vm.$nextTick();
await flushPromises();
await checkBox.setValue(true);
expect(checkBox.element.checked).toBe(true);
// Verify that formDirtyFlag has been updated
expect(wrapper.vm.formDirtyFlag).toBe(true);
// Find and trigger the update settings button
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.attributes("disabled")).toBe(undefined); // Ensure it's enabled
await updateSettingsButton.trigger("submit");
// Simulate form submission
// const settingsForm = wrapper.find('[data-test="settings-form"]');
// await settingsForm.trigger("submit");
await flushPromises();
await vi.advanceTimersByTime(500);
await flushPromises();
// Verify that the API call was made (mocked in your setup)
expect(updateStream).toHaveBeenCalledTimes(1); // Uncomment if you have a mocked function
});
it("Should prevent multiple API calls on rapid form submissions", async () => {
// Simulate form changes
const toggleWrapper = wrapper.find(
'[data-test="log-stream-store-original-data-toggle-btn"]',
);
const checkBox = toggleWrapper.find('input[type="checkbox"]');
await checkBox.setValue(true);
await checkBox.trigger("click");
await wrapper.vm.$nextTick();
await checkBox.setValue(true);
expect(checkBox.element.checked).toBe(true);
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
// Rapidly trigger multiple submissions
await Promise.all([
updateSettingsButton.trigger("submit"),
updateSettingsButton.trigger("submit"),
updateSettingsButton.trigger("submit"),
]);
await flushPromises();
// Verify that only one API call was made
expect(updateStream).toHaveBeenCalledTimes(1);
});
it("Should not call update settings API if retention days is less than 1", async () => {
// Simulate form changes
const retentionDaysInput = await wrapper.find(
'[data-test="stream-details-data-retention-input"]',
);
await retentionDaysInput.setValue(0); // Set retention days to less than 1
await retentionDaysInput.trigger("input");
await wrapper.vm.$nextTick();
// Verify that formDirtyFlag has been updated
expect(wrapper.vm.formDirtyFlag).toBe(true);
// Find and trigger the update settings button
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.attributes("disabled")).toBe(undefined); // Ensure it's enabled
await updateSettingsButton.trigger("submit");
await flushPromises();
await vi.advanceTimersByTime(500);
await flushPromises();
// Verify that the API call was not made
expect(updateStream).toHaveBeenCalledTimes(0);
});
it("Should handle API error when updating settings", async () => {
// Mock the API to return an error
await flushPromises();
// Simulate form changes
const toggleWrapper = wrapper.find(
'[data-test="log-stream-store-original-data-toggle-btn"]',
);
const checkBox = toggleWrapper.find('input[type="checkbox"]');
await checkBox.setValue(true);
await checkBox.trigger("click");
await wrapper.vm.$nextTick();
await checkBox.setValue(true);
expect(checkBox.element.checked).toBe(true);
// Verify that formDirtyFlag has been updated
expect(wrapper.vm.formDirtyFlag).toBe(true);
// Find and trigger the update settings button
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.attributes("disabled")).toBe(undefined); // Ensure it's enabled
await updateSettingsButton.trigger("submit");
await flushPromises();
await vi.advanceTimersByTime(500);
await flushPromises();
// Verify that the API call was made and handled the error
expect(updateStream).toHaveBeenCalledTimes(1);
expect(
updateStream.mock.settledResults[0].value.response.data.message,
).toBe("Internal Server Error"); // Assuming you set an errorMessage in your component
});
it("Should update index type to prefixPartition when selected and verify using update call", async () => {
await flushPromises();
// Find the index type select dropdown within the table row
const indexTypeSelectWrapper = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.findAll("tbody tr")
.at(1) // Assuming you want to select the first row for this test
.find('[data-test="schema-stream-index-select"]');
const indexTypeSelector = indexTypeSelectWrapper.findComponent({
name: "QSelect",
});
// Simulate selecting the "prefixPartition" option
await indexTypeSelector.vm.$emit("update:modelValue", [
"prefixPartition",
]);
await wrapper.vm.$nextTick();
// Verify that the selected value is "prefixPartition"
// expect(indexTypeSelect.element.value).toBe("prefixPartition");
// Verify that formDirtyFlag has been updated
expect(wrapper.vm.formDirtyFlag).toBe(true);
// Find and trigger the update settings button
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.attributes("disabled")).toBe(undefined); // Ensure it's enabled
await updateSettingsButton.trigger("submit");
await flushPromises();
await vi.advanceTimersByTime(500);
await flushPromises();
const parsedRequest = JSON.parse(
updateStream.mock.settledResults[0].value.config.data,
);
parsedRequest.partition_keys.add.forEach((index: any) => {
if (index.field == "kubernetes.container_hash") {
expect(index.types).toEqual("prefix");
}
});
// Verify that the API call was made
expect(updateStream).toHaveBeenCalledTimes(1);
});
it("Should update index type to fullTextSearchKey when selected and verify using update call", async () => {
await flushPromises();
// Find the index type select dropdown within the table row
const indexTypeSelectWrapper = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.findAll("tbody tr")
.at(1) // Assuming you want to select the first row for this test
.find('[data-test="schema-stream-index-select"]');
const indexTypeSelector = indexTypeSelectWrapper.findComponent({
name: "QSelect",
});
// Simulate selecting the "prefixPartition" option
await indexTypeSelector.vm.$emit("update:modelValue", [
"fullTextSearchKey",
]);
await wrapper.vm.$nextTick();
// Verify that the selected value is "prefixPartition"
// expect(indexTypeSelect.element.value).toBe("prefixPartition");
// Verify that formDirtyFlag has been updated
expect(wrapper.vm.formDirtyFlag).toBe(true);
// Find and trigger the update settings button
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.attributes("disabled")).toBe(undefined); // Ensure it's enabled
await updateSettingsButton.trigger("submit");
await flushPromises();
await vi.advanceTimersByTime(500);
await flushPromises();
const parsedRequest = JSON.parse(
updateStream.mock.settledResults[0].value.config.data,
);
expect(parsedRequest.full_text_search_keys.add[0]).toEqual(
"kubernetes.container_hash",
);
// Verify that the API call was made
expect(updateStream).toHaveBeenCalledTimes(1);
});
it("Should update index type to keyPartition when selected and verify using update call", async () => {
await flushPromises();
// Find the index type select dropdown within the table row
const indexTypeSelectWrapper = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.findAll("tbody tr")
.at(2) // Assuming you want to select the first row for this test
.find('[data-test="schema-stream-index-select"]');
const indexTypeSelector = indexTypeSelectWrapper.findComponent({
name: "QSelect",
});
// Simulate selecting the "prefixPartition" option
await indexTypeSelector.vm.$emit("update:modelValue", ["keyPartition"]);
await wrapper.vm.$nextTick();
// Verify that the selected value is "prefixPartition"
// expect(indexTypeSelect.element.value).toBe("prefixPartition");
// Verify that formDirtyFlag has been updated
expect(wrapper.vm.formDirtyFlag).toBe(true);
// Find and trigger the update settings button
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.attributes("disabled")).toBe(undefined); // Ensure it's enabled
await updateSettingsButton.trigger("submit");
await flushPromises();
await vi.advanceTimersByTime(500);
await flushPromises();
const parsedRequest = JSON.parse(
updateStream.mock.settledResults[0].value.config.data,
);
parsedRequest.partition_keys.add.forEach((index: any) => {
if (index.field == "log") {
expect(index.types).toEqual("value");
}
});
// Verify that the API call was made
expect(updateStream).toHaveBeenCalledTimes(1);
});
it("Should update index type to hashPartition_8 when selected and verify using update call", async () => {
await flushPromises();
// Find the index type select dropdown within the table row
const indexTypeSelectWrapper = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.findAll("tbody tr")
.at(3) // Assuming you want to select the first row for this test
.find('[data-test="schema-stream-index-select"]');
const indexTypeSelector = indexTypeSelectWrapper.findComponent({
name: "QSelect",
});
// Simulate selecting the "prefixPartition" option
await indexTypeSelector.vm.$emit("update:modelValue", [
"hashPartition_8",
]);
await wrapper.vm.$nextTick();
// Verify that the selected value is "prefixPartition"
// expect(indexTypeSelect.element.value).toBe("prefixPartition");
// Verify that formDirtyFlag has been updated
expect(wrapper.vm.formDirtyFlag).toBe(true);
// Find and trigger the update settings button
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.attributes("disabled")).toBe(undefined); // Ensure it's enabled
await updateSettingsButton.trigger("submit");
await flushPromises();
await vi.advanceTimersByTime(500);
await flushPromises();
const parsedRequest = JSON.parse(
updateStream.mock.settledResults[0].value.config.data,
);
parsedRequest.partition_keys.add.forEach((index: any) => {
if (index.field == "message") {
expect(index.types.hash).toEqual(8);
}
});
// Verify that the API call was made
expect(updateStream).toHaveBeenCalledTimes(1);
});
it("Should add fields to user defined schema", async () => {
await flushPromises();
// Find the index type select dropdown within the table row
const indexTypeSelectWrapper = wrapper.find(
'[data-test="schema-stream-delete-log-field-fts-key-checkbox"]',
);
const indexTypeSelector = await indexTypeSelectWrapper.find(
'input[type="checkbox"]',
);
await indexTypeSelector.setValue(true);
await indexTypeSelector.trigger("click");
await wrapper.vm.$nextTick();
const updateSchemaBtn = await wrapper.find(
'[data-test="schema-add-field-button"]',
);
await updateSchemaBtn.trigger("click");
// wrapper.vm.formDirtyFlag = true;
await wrapper.vm.$nextTick(); // Find and trigger the update settings button
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
await updateSettingsButton.trigger("submit");
await flushPromises();
await vi.advanceTimersByTime(500);
await flushPromises();
const parsedRequest = JSON.parse(
updateStream.mock.settledResults[0].value.config.data,
);
parsedRequest.partition_keys.add.forEach((index: any) => {
expect(index).toEqual("log");
});
// Verify that the API call was made
expect(updateStream).toHaveBeenCalledTimes(1);
});
describe("disable options test cases", () => {
it("disable Options: should disable options correctly based on row data", async () => {
const wrapper = mount(LogStream, {
props: {
modelValue: {
name: "k8s_json",
storage_type: "s3",
stream_type: "logs",
stats: {
doc_time_min: 1678448628630259,
doc_time_max: 1678448628652947,
doc_num: 400,
file_num: 1,
storage_size: 0.74,
compressed_size: 0.03,
},
schema: [
{
name: "_timestamp",
type: "Int64",
},
{
name: "kubernetes.container_hash",
type: "Utf8",
},
{
name: "log",
type: "Utf8",
},
{
name: "message",
type: "Utf8",
},
{
name: "test_this_field",
type: "Utf8",
},
],
settings: {
partition_time_level: "hourly",
partition_keys: {},
full_text_search_keys: ["level", "log"],
index_fields: [],
bloom_filter_fields: [],
distinct_value_fields: [],
data_retention: 3650,
max_query_range: 0,
store_original_data: false,
approx_partition: false,
index_updated_at: 1737970280285469,
extended_retention_days: [],
},
},
},
global: {
provide: {
store: store,
},
plugins: [i18n],
},
});
const disableOptions = wrapper.vm.disableOptions;
const row = {
name: "log",
index_type: ["fullTextSearchKey"],
};
const option = { value: "prefixPartition" };
const result = disableOptions(row, option);
expect(result).toBe(false);
row.index_type = ["keyPartition"];
const result2 = disableOptions(row, option);
expect(result2).toBe(true);
});
});
describe("filterFieldFn test cases", () => {
const rows = [
{ name: "log" },
{ name: "message" },
{ name: "kubernetes.container_hash" },
{ name: "_timestamp" },
];
const indexData = {
value: {
defined_schema_fields: ["log", "message"],
},
};
it("should filter rows based on field name", () => {
const filterFieldFn = wrapper.vm.filterFieldFn;
const terms = "log";
const result = filterFieldFn(rows, terms);
expect(result).toEqual([{ name: "log" }]);
});
it("should filter rows based on field name and schemaFields type", () => {
const filterFieldFn = wrapper.vm.filterFieldFn;
const terms = "message";
const result = filterFieldFn(rows, terms);
expect(result).toEqual([{ name: "message" }]);
});
it("should return all rows if field is empty and type is schemaFields", () => {
const filterFieldFn = wrapper.vm.filterFieldFn;
const terms = "@schemaFields";
const result = filterFieldFn(rows, terms);
expect(result).toEqual([]);
});
it("should return all rows if field is empty", () => {
const filterFieldFn = wrapper.vm.filterFieldFn;
const terms = "";
const result = filterFieldFn(rows, terms);
expect(result).toEqual(rows);
});
it("should return empty array if no rows match the field", () => {
const filterFieldFn = wrapper.vm.filterFieldFn;
const terms = "nonexistent";
const result = filterFieldFn(rows, terms);
expect(result).toEqual([]);
});
it("should filter rows based on field name case insensitively", () => {
const filterFieldFn = wrapper.vm.filterFieldFn;
const terms = "LOG";
const result = filterFieldFn(rows, terms);
expect(result).toEqual([{ name: "log" }]);
});
});
describe("Schema Field Management", () => {
let wrapper: any;
beforeEach(async () => {
vi.useFakeTimers();
wrapper = mount(LogStream, {
props: {
modelValue: {
name: "k8s_json",
storage_type: "s3",
stream_type: "logs",
stats: {
doc_time_min: 1678448628630259,
doc_time_max: 1678448628652947,
doc_num: 400,
file_num: 1,
storage_size: 0.74,
compressed_size: 0.03,
},
schema: [
{
name: "_timestamp",
type: "Int64",
},
{
name: "kubernetes.container_hash",
type: "Utf8",
},
{
name: "log",
type: "Utf8",
},
{
name: "message",
type: "Utf8",
},
{
name: "test_this_field",
type: "Utf8",
},
],
settings: {
partition_time_level: "hourly",
partition_keys: {},
full_text_search_keys: ["level", "log"],
index_fields: [],
bloom_filter_fields: [],
distinct_value_fields: [],
data_retention: 3650,
max_query_range: 0,
store_original_data: false,
approx_partition: false,
index_updated_at: 1737970280285469,
extended_retention_days: [],
},
},
},
global: {
provide: {
store: store,
},
plugins: [i18n],
},
});
await flushPromises();
});
afterEach(() => {
wrapper.unmount();
vi.restoreAllMocks();
});
it("should add a new schema field", async () => {
const addSchemaFieldButton = wrapper.find(
'[data-test="schema-add-fields-title"]',
);
await addSchemaFieldButton.trigger("click");
expect(wrapper.vm.newSchemaFields.length).toBe(1);
expect(wrapper.vm.newSchemaFields[0]).toEqual({
name: "",
type: "",
index_type: [],
});
expect(wrapper.vm.formDirtyFlag).toBe(true);
});
it("should remove a schema field", async () => {
const addSchemaFieldButton = wrapper.find(
'[data-test="schema-add-fields-title"]',
);
await addSchemaFieldButton.trigger("click");
wrapper.vm.newSchemaFields = [
{ name: "field1", type: "Utf8", index_type: [] },
{ name: "field2", type: "Int64", index_type: [] },
];
wrapper.vm.removeSchemaField(0);
expect(wrapper.vm.newSchemaFields.length).toBe(1);
expect(wrapper.vm.newSchemaFields[0]).toEqual({
name: "field2",
type: "Int64",
index_type: [],
});
});
it("should close dialog and reset newSchemaFields when last field is removed", async () => {
wrapper.vm.newSchemaFields = [
{ name: "field1", type: "Utf8", index_type: [] },
];
wrapper.vm.isDialogOpen = true;
wrapper.vm.removeSchemaField(0);
expect(wrapper.vm.newSchemaFields.length).toBe(0);
expect(wrapper.vm.isDialogOpen).toBe(false);
expect(wrapper.vm.newSchemaFields).toEqual([]);
});
});
});
});
+83 -84
View File
@@ -31,8 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</div>
</div>
<div class="col-auto">
<q-btn v-close-popup="true" round
flat icon="close" />
<q-btn v-close-popup="true" round flat icon="close" />
</div>
</div>
</q-card-section>
@@ -54,8 +53,7 @@ flat icon="close" />
>
No data available.
</div>
<div v-else
class="indexDetailsContainer" style="height: 100vh">
<div v-else class="indexDetailsContainer" style="height: 100vh">
<div
class="titleContainer tw-flex tw-flex-col tw-items-flex-start tw-gap-5"
>
@@ -65,7 +63,7 @@ class="indexDetailsContainer" style="height: 100vh">
>
<div data-test="schema-stream-title-text">
{{ t("alerts.stream_name") }}
<span class="title q-pl-xs" > {{ indexData.name }}</span>
<span class="title q-pl-xs"> {{ indexData.name }}</span>
</div>
<div
v-if="store.state.zoConfig.show_stream_stats_doc_num"
@@ -109,18 +107,15 @@ class="indexDetailsContainer" style="height: 100vh">
v-if="store.state.zoConfig.show_stream_stats_doc_num"
data-test="schema-stream-title-text"
>
<span class="q-px-xs">
Start Time:
<span class="title">{{ indexData.stats.doc_time_min }}</span>
</span>
<span class="q-px-xs">
Start Time:
<span class="title">{{ indexData.stats.doc_time_min }}</span>
</span>
<span class=" q-px-xs">
End Time:
<span class="title">{{ indexData.stats.doc_time_max }}</span>
</span>
</div>
</div>
</div>
@@ -197,7 +192,9 @@ class="indexDetailsContainer" style="height: 100vh">
<q-tabs v-model="activeMainTab" inline-label dense>
<!-- Schema Settings Tab with conditional class -->
<q-tab
:class="{ 'text-primary': activeMainTab === 'schemaSettings' }"
:class="{
'text-primary': activeMainTab === 'schemaSettings',
}"
name="schemaSettings"
icon="settings"
label="Schema Settings"
@@ -205,13 +202,12 @@ class="indexDetailsContainer" style="height: 100vh">
<!-- Red Button Tab -->
<q-tab
:class="{ 'text-primary': activeMainTab === 'redButton' }"
:class="{ 'text-primary': activeMainTab === 'redButton' }"
name="redButton"
icon="backup"
label="Extended Retention"
/>
</q-tabs>
</div>
</div>
<!-- schema settings tab -->
@@ -301,14 +297,17 @@ class="indexDetailsContainer" style="height: 100vh">
<div class="q-mb-md" v-if="isDialogOpen">
<q-card class="add-fields-card">
<!-- Header Section -->
<q-card-section class="q-pa-none" style="padding: 8px 16px 6px 16px">
<q-card-section
class="q-pa-none"
style="padding: 8px 16px 6px 16px"
>
<div class="tw-flex tw-justify-between tw-items-center">
<div class="text-h6">Add Field(s)</div>
<div>
<q-btn
data-test="add-stream-cancel-btn"
icon="close"
class=" text-bold q-mr-md"
class="text-bold q-mr-md"
text-color="light-text"
no-caps
dense
@@ -490,27 +489,24 @@ class="indexDetailsContainer" style="height: 100vh">
<q-icon name="info" class="q-mr-xs" size="16px" />
Additional
{{ store.state.zoConfig.extended_data_retention_days }} days of extension
will be applied to the selected date ranges</span
{{ store.state.zoConfig.extended_data_retention_days }} days of
extension will be applied to the selected date ranges</span
>
</div>
<div class="q-mt-sm">
<div class="text-center q-mt-sm tw-flex items-center ">
<div class="text-center q-mt-sm tw-flex items-center">
<div class="flex items-center">
<span class="text-bold"> Select Date</span>
<date-time
class="q-mx-sm"
@on:date-change="dateChangeValue"
disable-relative
hide-relative-time
hide-relative-timezone
:minDate="minDate"
/>
<date-time
class="q-mx-sm"
@on:date-change="dateChangeValue"
disable-relative
hide-relative-time
hide-relative-timezone
:minDate="minDate"
/>
</div>
<span class="text-bold">
(UTC Timezone)
</span>
<span class="text-bold"> (UTC Timezone) </span>
</div>
<div class="q-mt-sm" style="margin-bottom: 30px">
@@ -549,7 +545,6 @@ class="indexDetailsContainer" style="height: 100vh">
</q-td>
</template>
<template #bottom="scope">
<QTablePagination
:scope="scope"
@@ -581,10 +576,7 @@ class="indexDetailsContainer" style="height: 100vh">
selected</span
>
<q-btn
v-if="
isSchemaUDSEnabled &&
activeMainTab == 'schemaSettings'
"
v-if="isSchemaUDSEnabled && activeMainTab == 'schemaSettings'"
data-test="schema-add-field-button"
class="q-my-sm no-border text-bold q-mr-md"
padding="sm md"
@@ -603,10 +595,9 @@ class="indexDetailsContainer" style="height: 100vh">
: t("logStream.addSchemaField")
}}
</q-btn>
{{ }}
<q-btn
v-bind:disable="
!selectedFields.length && (!selectedDateFields.length)
!selectedFields.length && !selectedDateFields.length
"
data-test="schema-delete-button"
class="q-my-sm text-bold btn-delete"
@@ -616,7 +607,11 @@ class="indexDetailsContainer" style="height: 100vh">
dense
flat
style="border: 1px red solid"
@click="activeMainTab == 'schemaSettings' ? confirmQueryModeChangeDialog = true : confirmDeleteDatesDialog = true"
@click="
activeMainTab == 'schemaSettings'
? (confirmQueryModeChangeDialog = true)
: (confirmDeleteDatesDialog = true)
"
>
<span class="flex items-center tw-gap-1">
<q-icon size="14px" :name="outlinedDelete" />
@@ -769,7 +764,7 @@ export default defineComponent({
const isDialogOpen = ref(false);
const redDaysList = ref([]);
const resultTotal = ref<number>(0);
const perPageOptions : any = [
const perPageOptions: any = [
{ label: "5", value: 5 },
{ label: "10", value: 10 },
{ label: "20", value: 20 },
@@ -997,16 +992,19 @@ export default defineComponent({
}
if (Array.isArray(streamResponse.settings.extended_retention_days)) {
redBtnRows.value = [];
indexData.value.extended_retention_days = streamResponse.settings.extended_retention_days;
streamResponse.settings.extended_retention_days.forEach((field, index) => {
redBtnRows.value.push({
index: index,
original_start: field.start,
original_end: field.end,
start: convertUnixToQuasarFormat(field.start),
end: convertUnixToQuasarFormat(field.end),
});
});
indexData.value.extended_retention_days =
streamResponse.settings.extended_retention_days;
streamResponse.settings.extended_retention_days.forEach(
(field, index) => {
redBtnRows.value.push({
index: index,
original_start: field.start,
original_end: field.end,
start: convertUnixToQuasarFormat(field.start),
end: convertUnixToQuasarFormat(field.end),
});
},
);
}
if (
@@ -1153,12 +1151,12 @@ export default defineComponent({
if (selectedDateFields.value.length > 0) {
selectedDateFields.value.forEach((field) => {
// Filter out the items only if both start and end match
settings.extended_retention_days = settings.extended_retention_days.filter((item) => {
return !(item.start === field.start && item.end === field.end);
settings.extended_retention_days =
settings.extended_retention_days.filter((item) => {
return !(item.start === field.start && item.end === field.end);
});
});
};
}
let added_part_keys = [];
for (var property of indexData.value.schema) {
@@ -1545,44 +1543,46 @@ export default defineComponent({
const dateToFormat = new Date(unixSeconds * 1000);
const formattedDate = dateToFormat.toISOString();
return date.formatDate(formattedDate, "DD-MM-YYYY");
}
function formatDate(dateString) {
const date = new Date(dateString); // Convert to Date object
const day = String(date.getDate()).padStart(2, '0'); // Get day with leading zero
const month = String(date.getMonth() + 1).padStart(2, '0'); // Get month with leading zero
const day = String(date.getDate()).padStart(2, "0"); // Get day with leading zero
const month = String(date.getMonth() + 1).padStart(2, "0"); // Get month with leading zero
const year = date.getFullYear(); // Get the full year
return `${day}-${month}-${year}`; // Return formatted date
}
const dateChangeValue = (value) => {
const selectedFromDate = value.hasOwnProperty('selectedDate') && formatDate(value.selectedDate.from)
const selectedToDate =value.hasOwnProperty('selectedDate') && formatDate(value.selectedDate.to)
const selectedFromDate =
value.hasOwnProperty("selectedDate") &&
formatDate(value.selectedDate.from);
const selectedToDate =
value.hasOwnProperty("selectedDate") &&
formatDate(value.selectedDate.to);
if (value.relativeTimePeriod == null) {
try {
const startTimestamp = convertDateToTimestamp(selectedFromDate, "00:00", 'UTC').timestamp;
const endTimestamp = convertDateToTimestamp(selectedToDate, "00:00", 'UTC').timestamp;
if (startTimestamp && endTimestamp) {
redDaysList.value.push({
start: startTimestamp,
end: endTimestamp,
});
onSubmit();
}
} catch (error) {
console.error('Error processing date selection:', error);
const startTimestamp = convertDateToTimestamp(
selectedFromDate,
"00:00",
"UTC",
).timestamp;
const endTimestamp = convertDateToTimestamp(
selectedToDate,
"00:00",
"UTC",
).timestamp;
if (startTimestamp && endTimestamp) {
redDaysList.value.push({
start: startTimestamp,
end: endTimestamp,
});
onSubmit();
}
} catch (error) {
console.error("Error processing date selection:", error);
}
}
};
const calculateDateRange = () => {
@@ -1603,7 +1603,6 @@ export default defineComponent({
);
};
const deleteDates = () => {
selectedDateFields.value = selectedDateFields.value.map((field) => {
return {
@@ -1613,7 +1612,7 @@ export default defineComponent({
});
formDirtyFlag.value = true;
onSubmit();
};
};
return {
t,
@@ -339,7 +339,10 @@ export const usePanelDataLoader = (
state.isOperationCancelled = true;
if (isWebSocketEnabled() && state.searchRequestTraceIds) {
if (
isWebSocketEnabled(store.state) &&
state.searchRequestTraceIds
) {
try {
// loop on state.searchRequestTraceIds
state.searchRequestTraceIds.forEach((traceId) => {
@@ -1439,7 +1442,7 @@ export const usePanelDataLoader = (
: [];
state.annotations = annotations;
if (isStreamingEnabled()) {
if (isStreamingEnabled(store.state)) {
await getDataThroughStreaming(
query,
it,
@@ -1449,7 +1452,7 @@ export const usePanelDataLoader = (
panelQueryIndex,
abortControllerRef,
);
} else if (isWebSocketEnabled()) {
} else if (isWebSocketEnabled(store.state)) {
await getDataThroughWebSocket(
query,
it,
@@ -1745,7 +1748,7 @@ export const usePanelDataLoader = (
: errorDetailValue;
const errorCode =
isWebSocketEnabled() || isStreamingEnabled()
isWebSocketEnabled(store.state) || isStreamingEnabled(store.state)
? error?.response?.data?.code || error?.code || error?.status || ""
: error?.response?.status ||
error?.status ||
@@ -2166,7 +2169,7 @@ export const usePanelDataLoader = (
}
// for websocket
if (isWebSocketEnabled() && state.searchRequestTraceIds) {
if (isWebSocketEnabled(store.state) && state.searchRequestTraceIds) {
try {
// loop on state.searchRequestTraceIds
state.searchRequestTraceIds.forEach((traceId) => {
+2 -2
View File
@@ -1577,8 +1577,8 @@ const useLogs = () => {
// if window has use_web_socket property then use that
// else use organization settings
searchObj.meta.jobId = "";
const shouldUseWebSocket = isWebSocketEnabled();
const shouldUseStreaming = isStreamingEnabled();
const shouldUseWebSocket = isWebSocketEnabled(store.state);
const shouldUseStreaming = isStreamingEnabled(store.state);
const isMultiStreamSearch =
searchObj.data.stream.selectedStream.length > 1 &&
+1 -1
View File
@@ -184,7 +184,7 @@ const useSearchWebSocket = () => {
socketId: socketId.value as string
}
const url = getWebSocketUrl(socketId.value, org_id);
const url = getWebSocketUrl(socketId.value, org_id, store.state.API_ENDPOINT);
// If needed we can store the socketID in global state
webSocket.connect(socketId.value, url);
@@ -15,13 +15,13 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { flushPromises, mount, shallowMount } from "@vue/test-utils";
import { installQuasar } from "../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import i18n from "@/locales";
import store from "../helpers/store";
import store from "@/test/unit/helpers/store";
import "plotly.js";
import MainLayout from "@/layouts/MainLayout.vue";
import router from "../helpers/router";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
@@ -15,13 +15,13 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises, DOMWrapper } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import DetailTable from "@/plugins/logs/DetailTable.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import router from "../../helpers/router";
import store from "@/test/unit/helpers/store";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
@@ -15,18 +15,18 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "../../test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import Index from "@/plugins/logs/Index.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import store from "@/test/unit/helpers/store";
// @ts-ignore
import { rest } from "msw";
import "plotly.js";
import SearchResult from "@/plugins/logs/SearchResult.vue";
import searchService from "@/services/search";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
+2 -2
View File
@@ -1706,8 +1706,8 @@ export default defineComponent({
// this.handleRunQuery();
this.searchObj.loadingHistogram = true;
const shouldUseWebSocket = this.isWebSocketEnabled();
const shouldUseStreaming = this.isStreamingEnabled();
const shouldUseWebSocket = this.isWebSocketEnabled(this.store.state);
const shouldUseStreaming = this.isStreamingEnabled(this.store.state);
// Generate histogram skeleton before making request
await this.generateHistogramSkeleton();
@@ -15,16 +15,16 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import Index from "@/plugins/logs/Index.vue";
import IndexList from "@/plugins/logs/IndexList.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import store from "@/test/unit/helpers/store";
import "plotly.js";
import SearchResult from "@/plugins/logs/SearchResult.vue";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
+62 -41
View File
@@ -1020,7 +1020,10 @@ export default defineComponent({
query_context = query_context == undefined ? "" : query_context;
// Implement websocket based field values, check getQueryData in useLogs for websocket enabled
if (isWebSocketEnabled() || isStreamingEnabled()) {
if (
isWebSocketEnabled(store.state) ||
isStreamingEnabled(store.state)
) {
fetchValuesWithWebsocket({
fields: [name],
size: 10,
@@ -1042,19 +1045,25 @@ export default defineComponent({
continue;
}
//TODO : add comments for this in future
//TODO : add comments for this in future
//for future reference
//values api using partition based api
let queryToBeSent = searchObj.meta.sqlMode ? searchObj.data.query : query_context.replace("[INDEX_NAME]", selectedStream);
const response = await getValuesPartition(startISOTimestamp,endISOTimestamp,name,queryToBeSent);
let queryToBeSent = searchObj.meta.sqlMode
? searchObj.data.query
: query_context.replace("[INDEX_NAME]", selectedStream);
const response = await getValuesPartition(
startISOTimestamp,
endISOTimestamp,
name,
queryToBeSent,
);
const partitions: any = response?.data.partitions || [];
for (const partition of partitions) {
try {
//check if the field is opened because sometimes
//check if the field is opened because sometimes
// user might close the field before all the subsequent requests are completed
if(!openedFilterFields.value.includes(name)){
if (!openedFilterFields.value.includes(name)) {
return;
}
const res: any = await streamService.fieldValues({
@@ -1085,7 +1094,8 @@ export default defineComponent({
(value: any) => value.key === subItem.zo_sql_key,
);
if (index !== -1) {
fieldValues.value[name]["values"][index].count += parseInt(subItem.zo_sql_num);
fieldValues.value[name]["values"][index].count +=
parseInt(subItem.zo_sql_num);
} else {
fieldValues.value[name]["values"].push({
key: subItem.zo_sql_key,
@@ -1096,8 +1106,12 @@ export default defineComponent({
});
if (fieldValues.value[name]["values"].length > 10) {
fieldValues.value[name]["values"].sort((a, b) => b.count - a.count);
fieldValues.value[name]["values"] = fieldValues.value[name]["values"].slice(0, 10);
fieldValues.value[name]["values"].sort(
(a, b) => b.count - a.count,
);
fieldValues.value[name]["values"] = fieldValues.value[name][
"values"
].slice(0, 10);
}
}
} catch (err) {
@@ -1110,13 +1124,16 @@ export default defineComponent({
}
}
}
openedFilterFields.value = openedFilterFields.value.filter((field:string)=>field !== name);
openedFilterFields.value = openedFilterFields.value.filter(
(field: string) => field !== name,
);
}
}
} catch (err) {
fieldValues.value[name]["isLoading"] = false;
openedFilterFields.value = openedFilterFields.value.filter((field:string)=>field !== name);
openedFilterFields.value = openedFilterFields.value.filter(
(field: string) => field !== name,
);
console.log(err);
$q.notify({
type: "negative",
@@ -1298,7 +1315,7 @@ export default defineComponent({
};
const initializeWebSocketConnection = (payload: any) => {
if (isWebSocketEnabled()) {
if (isWebSocketEnabled(store.state)) {
fetchQueryDataWithWebSocket(payload, {
open: sendSearchMessage,
close: handleSearchClose,
@@ -1309,7 +1326,7 @@ export default defineComponent({
return;
}
if (isStreamingEnabled()) {
if (isStreamingEnabled(store.state)) {
fetchQueryDataWithHttpStream(payload, {
data: handleSearchResponse,
error: handleSearchError,
@@ -1488,17 +1505,15 @@ export default defineComponent({
}
};
const cancelFilterCreator = (row: any) => {
//if it is websocker based then cancel the trace id
//else cancel the further value api calls using the openedFilterFields
if(isWebSocketEnabled()){
if (isWebSocketEnabled(store.state)) {
cancelTraceId(row.name);
}
else{
} else {
cancelValueApi(row.name);
}
}
};
const cancelTraceId = (field: string) => {
const traceIds = traceIdMapper.value[field];
@@ -1513,30 +1528,36 @@ export default defineComponent({
};
const cancelValueApi = (value: string) => {
//remove the field from the openedFilterFields
openedFilterFields.value = openedFilterFields.value.filter((field:string)=>field !== value);
}
const getValuesPartition = async (start: number, end: number,name:string,queryToBeSent:string) => {
try{
const queryReq = {
sql: queryToBeSent,
start_time: start,
end_time: end,
sql_mode: "context",
// streaming_output: true,
}
const res = await searchService.partition({
org_identifier: store.state.selectedOrganization.identifier,
query: queryReq,
page_type: searchObj.data.stream.streamType,
traceparent: generateTraceContext().traceId,
});
return res;
openedFilterFields.value = openedFilterFields.value.filter(
(field: string) => field !== value,
);
};
const getValuesPartition = async (
start: number,
end: number,
name: string,
queryToBeSent: string,
) => {
try {
const queryReq = {
sql: queryToBeSent,
start_time: start,
end_time: end,
sql_mode: "context",
// streaming_output: true,
};
const res = await searchService.partition({
org_identifier: store.state.selectedOrganization.identifier,
query: queryReq,
page_type: searchObj.data.stream.streamType,
traceparent: generateTraceContext().traceId,
});
return res;
} catch (err) {
console.error("Failed to fetch field values:", err);
fieldValues.value[name].errMsg = "Failed to fetch field values";
}
};
return {
@@ -1610,7 +1631,7 @@ export default defineComponent({
sortedStreamFields,
placeHolderText,
cancelTraceId,
cancelFilterCreator
cancelFilterCreator,
};
},
});
@@ -15,14 +15,14 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import QueryEditor from "@/components/QueryEditor.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import store from "@/test/unit/helpers/store";
import "plotly.js";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
@@ -15,17 +15,16 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises, DOMWrapper } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import Index from "@/plugins/logs/Index.vue";
import SearchBar from "@/plugins/logs/SearchBar.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import "plotly.js";
import store from "@/test/unit/helpers/store";
import searchService from "@/services/search";
import SearchResult from "@/plugins/logs/SearchResult.vue";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
@@ -15,19 +15,19 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import Index from "@/plugins/logs/Index.vue";
import SearchResult from "@/plugins/logs/SearchResult.vue";
// import BarChart from "@/components/logBarChart.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import store from "@/test/unit/helpers/store";
// @ts-ignore
import { rest } from "msw";
// import "plotly.js";
import DetailTable from "@/plugins/logs/DetailTable.vue";
import router from "../../helpers/router";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
+15
View File
@@ -1 +1,16 @@
// 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/>.
import "vitest-canvas-mock";
+15
View File
@@ -1,3 +1,18 @@
// 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/>.
import { vi } from "vitest";
vi.stubGlobal("open", vi.fn());
+75
View File
@@ -0,0 +1,75 @@
// 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/>.
import { vi } from "vitest";
import axios from "axios";
import { Notify } from "quasar"; // ✅ Ensure Quasar Notify works in mock
vi.mock("@/services/http.ts", () => ({
default: () => {
const instance = axios.create({
withCredentials: true,
baseURL: "http://localhost:5080", // 🔹 Replacing `store.state.API_ENDPOINT`
});
// Mock request interceptor
instance.interceptors.request.use(
(config) => {
return config;
},
(error) => Promise.reject(error),
);
// Mock response interceptor with error handling
instance.interceptors.response.use(
(response) => response,
(error) => {
console.error("Mock Interceptor Error", error);
if (error?.response?.status === 400) {
return Promise.reject({
response: { status: 400, data: "Bad Request" },
});
}
if (error?.response?.status === 401) {
console.log("Mock 401: Unauthorized - Logging out user...");
sessionStorage.clear(); // Simulate logout
return Promise.reject({
response: { status: 401, data: "Unauthorized" },
});
}
if (error?.response?.status === 403) {
console.log("Mock 403: Forbidden - Showing notification...");
Notify.create({
message: "Unauthorized Access: Please contact your administrator.",
color: "negative",
timeout: 0,
});
return Promise.reject({
response: { status: 403, data: "Forbidden" },
});
}
return Promise.reject({
response: { status: 500, data: "Internal Server Error" },
});
},
);
return instance;
},
}));
+16 -1
View File
@@ -1,4 +1,19 @@
// 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/>.
import "./canvas";
import "./plotly";
import "./global";
import "./rudder-sdk";
import "./http";
-15
View File
@@ -1,15 +0,0 @@
import { vi } from "vitest";
vi.mock("plotly.js", () => {
return {
default: {
newPlot: vi.fn((ref) => {
ref.on = vi.fn();
}),
addTraces: vi.fn(),
restyle: vi.fn(),
relayout: vi.fn(),
redraw: vi.fn(),
},
};
});
+15
View File
@@ -1,3 +1,18 @@
// 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/>.
import { vi } from "vitest";
vi.mock("rudder-sdk-js", () => {
@@ -1,192 +0,0 @@
// 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/>.
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises, DOMWrapper } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import AddAlert from "@/components/alerts/AddAlert.vue";
import i18n from "@/locales";
import store from "../../helpers/store";
import AlertService from "@/services/alerts";
// @ts-ignore
import { rest } from "msw";
import router from "../../helpers/router";
installQuasar({
plugins: [Dialog, Notify],
});
const node = document.createElement("div");
node.setAttribute("id", "app");
document.body.appendChild(node);
describe("Alert List", async () => {
let wrapper: any;
beforeEach(async () => {
vi.useFakeTimers();
wrapper = mount(AddAlert, {
attachTo: "#app",
global: {
provide: {
store: store,
},
plugins: [i18n, router],
},
});
await flushPromises();
});
afterEach(() => {
wrapper.unmount();
});
it("Should render alerts title", () => {
expect(wrapper.find('[data-test="add-alert-title"]').text()).toBe(
"Add Alert"
);
});
it("Should render name input", () => {
expect(
wrapper.find('[data-test="add-alert-name-input"]').exists()
).toBeTruthy();
});
it("Should render stream input", () => {
expect(
wrapper.find('[data-test="add-alert-stream-select"]').exists()
).toBeTruthy();
});
it("Should by default select Scheduled type alerts", () => {
expect(
wrapper
.find('[data-test="add-alert-scheduled-alert-radio"]')
.find(".q-radio__inner--truthy")
.exists()
).toBeTruthy();
});
it("Should reder condition", () => {
expect(wrapper.find('[data-test="add-alert-condition-title"]').text()).toBe(
"Condition:"
);
expect(
wrapper.find('[data-test="add-alert-condition-column-select"]').exists()
).toBeTruthy();
expect(
wrapper.find('[data-test="add-alert-condition-operator-select"]').exists()
).toBeTruthy();
expect(
wrapper.find('[data-test="add-alert-condition-value-input"]').exists()
).toBeTruthy();
});
it("Should reder sql query input", () => {
expect(
wrapper.find('[data-test="add-alert-query-input-title"]').text()
).toBe("Query:");
expect(
wrapper.find('[data-test="add-alert-query-input"]').exists()
).toBeTruthy();
});
it("Should reder Duration input", () => {
expect(wrapper.find('[data-test="add-alert-duration-title"]').text()).toBe(
"Duration for which alert is evaluated:"
);
expect(
wrapper.find('[data-test="add-alert-duration-input"]').exists()
).toBeTruthy();
expect(
wrapper.find('[data-test="add-alert-duration-select"]').exists()
).toBeTruthy();
});
it("Should reder Frequency input", () => {
expect(wrapper.find('[data-test="add-alert-frequency-title"]').text()).toBe(
"Frequency at which alert is evaluated:"
);
expect(
wrapper.find('[data-test="add-alert-frequency-input"]').exists()
).toBeTruthy();
expect(
wrapper.find('[data-test="add-alert-frequency-select"]').exists()
).toBeTruthy();
});
it("Should reder Delay notification input", () => {
expect(wrapper.find('[data-test="add-alert-delay-title"]').text()).toBe(
"Delay notification until, after one notification:"
);
expect(
wrapper.find('[data-test="add-alert-delay-input"]').exists()
).toBeTruthy();
expect(
wrapper.find('[data-test="add-alert-delay-select"]').exists()
).toBeTruthy();
});
it("Should reder destination select input", () => {
expect(
wrapper.find('[data-test="add-alert-destination-select"]').exists()
).toBeTruthy();
});
describe("When user fills form and clicks submit", () => {
const submitBtn = vi.spyOn(AlertService, "create");
const stream_name = "k8s_json";
const alert_name = "alert1";
afterEach(() => {
vi.clearAllMocks();
});
beforeEach(async () => {
global.server.use(
rest.post(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/${stream_name}/alerts/${alert_name}`,
(req: any, res: any, ctx: any) => {
return res(ctx.status(200), ctx.json({ code: 200 }));
}
)
);
await wrapper
.find('[data-test="add-alert-name-input"]')
.setValue(alert_name);
wrapper.vm.formData.stream_name = stream_name;
wrapper.vm.formData.condition.column = "kubernetes.host";
wrapper.vm.formData.condition.operator = "=";
await wrapper
.find('[data-test="add-alert-condition-value-input"]')
.setValue("abc");
await wrapper.find('[data-test="add-alert-duration-input"]').setValue(1);
await wrapper.find('[data-test="add-alert-frequency-input"]').setValue(1);
await wrapper.find('[data-test="add-alert-delay-input"]').setValue(1);
wrapper.vm.formData.destination = "dest1";
});
it("Should create alert on clicking Submit", async () => {
await wrapper.find('[data-test="add-alert-submit-btn"]').trigger("click");
await flushPromises();
expect(submitBtn).toHaveBeenCalledTimes(1);
expect(wrapper.emitted()["update:list"]).toHaveLength(1);
});
it("Should create alert on clicking Submit", async () => {
await wrapper.find('[data-test="add-alert-cancel-btn"]').trigger("click");
expect(wrapper.emitted()["cancel:hideform"]).toHaveLength(1);
});
});
});
@@ -1,37 +0,0 @@
// Vitest Snapshot v1
exports[`Alert List > Should match previous table snapshot 1`] = `
"<table class=\\"q-table\\">
<thead>
<tr>
<th class=\\"text-left\\">#</th>
<th class=\\"text-left sortable\\">Name<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Stream Name<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Query<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Condition<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Destination<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-center\\">Actions</th>
</tr>
</thead>
<tbody>
<tr class=\\"\\">
<td class=\\"text-left\\">01</td>
<td class=\\"text-left\\">alert1</td>
<td class=\\"text-left\\">default</td>
<td class=\\"text-left\\" style=\\"width: 30vw; word-break: break-all;\\">--</td>
<td class=\\"text-left\\" style=\\"width: 10vw; word-break: break-all;\\">code EqualTo 500</td>
<td class=\\"text-left\\">dest1</td>
<td class=\\"q-td text-center\\"><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" data-test=\\"alert-list-alert1-delete-alert\\" title=\\"Edit\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><i class=\\"q-icon notranslate material-icons\\" aria-hidden=\\"true\\" role=\\"img\\">edit</i></span></button><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Delete\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><span class=\\"q-icon\\" aria-hidden=\\"true\\" role=\\"img\\"><img src=\\"/src/assets/images/common/delete_icon.svg\\"></span></span></button></td>
</tr>
<tr class=\\"\\">
<td class=\\"text-left\\">02</td>
<td class=\\"text-left\\">alert2</td>
<td class=\\"text-left\\">default</td>
<td class=\\"text-left\\" style=\\"width: 30vw; word-break: break-all;\\">select * from 'default'</td>
<td class=\\"text-left\\" style=\\"width: 10vw; word-break: break-all;\\">code EqualTo 500</td>
<td class=\\"text-left\\">dest1</td>
<td class=\\"q-td text-center\\"><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" data-test=\\"alert-list-alert2-delete-alert\\" title=\\"Edit\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><i class=\\"q-icon notranslate material-icons\\" aria-hidden=\\"true\\" role=\\"img\\">edit</i></span></button><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Delete\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><span class=\\"q-icon\\" aria-hidden=\\"true\\" role=\\"img\\"><img src=\\"/src/assets/images/common/delete_icon.svg\\"></span></span></button></td>
</tr>
</tbody>
</table>"
`;
@@ -1,37 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Alert List > Should match previous table snapshot 1`] = `
"<table class=\\"q-table\\">
<thead>
<tr>
<th class=\\"text-left\\">#</th>
<th class=\\"text-left sortable\\">Name<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Stream Name<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Query<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Condition<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Destination<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-center\\">Actions</th>
</tr>
</thead>
<tbody>
<tr class=\\"\\">
<td class=\\"text-left\\">01</td>
<td class=\\"text-left\\">alert1</td>
<td class=\\"text-left\\">default</td>
<td class=\\"text-left\\" style=\\"width: 30vw; word-break: break-all;\\">--</td>
<td class=\\"text-left\\" style=\\"width: 10vw; word-break: break-all;\\">code EqualTo 500</td>
<td class=\\"text-left\\">dest1</td>
<td class=\\"q-td text-center\\"><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Edit\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><i class=\\"q-icon notranslate material-icons\\" aria-hidden=\\"true\\" role=\\"img\\">edit</i></span></button><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Delete\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><span class=\\"q-icon\\" aria-hidden=\\"true\\" role=\\"img\\"><img src=\\"/src/assets/images/common/delete_icon.svg\\"></span></span></button></td>
</tr>
<tr class=\\"\\">
<td class=\\"text-left\\">02</td>
<td class=\\"text-left\\">alert2</td>
<td class=\\"text-left\\">default</td>
<td class=\\"text-left\\" style=\\"width: 30vw; word-break: break-all;\\">select * from 'default'</td>
<td class=\\"text-left\\" style=\\"width: 10vw; word-break: break-all;\\">code EqualTo 500</td>
<td class=\\"text-left\\">dest1</td>
<td class=\\"q-td text-center\\"><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Edit\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><i class=\\"q-icon notranslate material-icons\\" aria-hidden=\\"true\\" role=\\"img\\">edit</i></span></button><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Delete\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><span class=\\"q-icon\\" aria-hidden=\\"true\\" role=\\"img\\"><img src=\\"/src/assets/images/common/delete_icon.svg\\"></span></span></button></td>
</tr>
</tbody>
</table>"
`;
@@ -1,3 +0,0 @@
// Vitest Snapshot v1
exports[`Curl > should render content 1`] = `"curl -u example@gmail.com:11 -k http://localhost:8080/api/zinc_next/default/_json -d [JSON-DATA]"`;
@@ -1,16 +0,0 @@
// Vitest Snapshot v1
exports[`FluentBit > should render content 1`] = `
"[OUTPUT]
Name http
Match *
URI /api/zinc_next/default/_json
Host localhost
Port 808
tls Off
Format json
Json_date_key _timestamp
Json_date_format iso8601
HTTP_User tulsiraval2828@gmail.com
HTTP_Passwd 11"
`;
@@ -1,21 +0,0 @@
// Vitest Snapshot v1
exports[`Fluentd > should render content 1`] = `
"<source>
@type forward
port 24224
bind 0.0.0.0
</source>
<match **>
@type http
endpoint http://localhost:808/api/zinc_next/default/_json
content_type json
json_array true
<auth>
method basic
username tulsiraval2828@gmail.com
password 11
</auth>
</match>"
`;
@@ -1,16 +0,0 @@
// Vitest Snapshot v1
exports[`Vector > should render content 1`] = `
"[sinks.openobserve]
type = \\"http\\"
inputs = [ source or transform id ]
uri = \\"http://localhost:808/api/zinc_next/default/_json\\"
method = \\"post\\"
auth.strategy = \\"basic\\"
auth.user = \\"tulsiraval2828@gmail.com\\"
auth.password = \\"11\\"
compression = \\"gzip\\"
encoding.codec = \\"json\\"
encoding.timestamp_format = \\"rfc3339\\"
healthcheck.enabled = false"
`;
@@ -1,211 +0,0 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "../../helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import LogStream from "@/components/logstream/schema.vue";
import i18n from "@/locales";
// @ts-ignore
import { rest } from "msw";
import store from "@/test/unit/helpers/store";
import StreamService from "@/services/stream";
installQuasar({
plugins: [Dialog, Notify],
});
describe("Streams", async () => {
let wrapper: any;
beforeEach(async () => {
vi.useFakeTimers();
wrapper = mount(LogStream, {
props: {
modelValue: {
name: "k8s_json",
schema: [],
stream_type: "logs",
},
},
global: {
provide: {
store: store,
},
plugins: [i18n],
},
});
await flushPromises();
});
afterEach(() => {
wrapper.unmount();
vi.restoreAllMocks();
});
it("should display title", () => {
const pageTitle = wrapper.find('[data-test="schema-title-text"]');
expect(pageTitle.text()).toBe("Stream Detail");
});
it("Should display stream title", () => {
const streamTitle = wrapper.find('[data-test="schema-stream-title-text"]');
expect(streamTitle.text()).toBe("k8s_json");
});
it("Should have cancel button", () => {
const cancelButton = wrapper.find('[data-test="schema-cancel-button"]');
expect(cancelButton.exists()).toBeTruthy();
expect(cancelButton.text()).toBe("Cancel");
});
it("Should have Update Settings button", () => {
const updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
expect(updateSettingsButton.exists()).toBeTruthy();
expect(updateSettingsButton.text()).toBe("Update Settings");
});
it("Should have Cancel button ", () => {
const table = wrapper.find('[data-test="schema-stream-meta-data-table"]');
expect(table.exists()).toBeTruthy();
});
it("Should display stream meta data table header", () => {
store.state.zoConfig.show_stream_stats_doc_num = true;
const tableHeaders = wrapper
.find('[data-test="schema-stream-meta-data-table"]')
.find("thead")
.find("tr")
.findAll("th");
expect(tableHeaders[0].text()).toBe("Docs Count");
expect(tableHeaders[1].text()).toBe("Ingested Data");
expect(tableHeaders[2].text()).toBe("Compressed Size");
expect(tableHeaders[3].text()).toBe("Time");
});
it("Should display stream meta data table data", () => {
const tableHeaders = wrapper
.find('[data-test="schema-stream-meta-data-table"]')
.find("tbody")
.find("tr")
.findAll("td");
expect(tableHeaders[0].text()).toBe("400");
expect(tableHeaders[1].text()).toBe("0.74 MB");
expect(tableHeaders[2].text()).toBe("0.03 MB");
expect(tableHeaders[3].text()).toBe(
"2023-03-10T17:13:48:63+05:30 to 2023-03-10T17:13:48:65+05:30",
);
});
it("Should display stream fields mapping table", () => {
const table = wrapper.find(
'[data-test="schema-log-stream-field-mapping-table"]',
);
expect(table.exists()).toBeTruthy();
});
it("Should display stream fields mapping title", () => {
const table = wrapper.find(
'[data-test="schema-log-stream-mapping-title-text"]',
);
expect(table.text()).toBe(
"Mapping - Using default fts keys, as no fts keys are set for stream.Store Original Data",
);
});
it("Should display stream fields mapping table headers", () => {
const tableHeaders = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find("thead")
.find("tr")
.findAll("th");
expect(tableHeaders[0].text()).toBe("");
expect(tableHeaders[1].text()).toBe("Fieldarrow_upward");
expect(tableHeaders[2].text()).toBe("Typearrow_upward");
expect(tableHeaders[3].text()).toBe("Index Type");
});
it("Should display stream fields mapping table data", () => {
const tableData = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find("tbody")
.findAll("tr")[0]
.findAll("td");
expect(tableData[0].text()).toBe("");
expect(tableData[1].text()).toBe("_timestamp");
expect(tableData[2].text()).toBe("Int64");
});
// TODO : Check if we can update this test case
// - expect(logCheckbox.find(".q-checkbox__inner--truthy").exists()).toBeTruthy();
// + expect(wrapper.vm.ftsKeys.includes('log')).toBeTruthy();
it("Should check if log and message full text search checkbox is active in field mapping table", () => {
const logCheckbox = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find('[data-test="schema-stream-log-field-fts-key-checkbox"]');
const messageCheckbox = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find("tbody")
.find('[data-test="schema-stream-log-field-fts-key-checkbox"]');
expect(
logCheckbox.find(".q-checkbox__inner--truthy").exists(),
).toBeTruthy();
expect(
messageCheckbox.find(".q-checkbox__inner--truthy").exists(),
).toBeTruthy();
});
it("Should check if _timestamp and kubernetes.container_hash full text search checkbox is inactive in field mapping table", () => {
const timeStampCheckbox = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find('[data-test="schema-stream-_timestamp-field-fts-key-checkbox"]');
const KubHashCheckbox = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find("tbody")
.find(
'[data-test="schema-stream-kubernetes.container_hash-field-fts-key-checkbox"]',
);
expect(
timeStampCheckbox.find(".q-checkbox__inner--truthy").exists(),
).toBeFalsy();
expect(
KubHashCheckbox.find(".q-checkbox__inner--truthy").exists(),
).toBeFalsy();
});
describe("When user make changes and update settings", () => {
const updateStream = vi.spyOn(StreamService, "updateSettings");
let logPartition, timeStampCheckbox, updateSettingsButton: any;
beforeEach(async () => {
logPartition = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find('[data-test="schema-stream-log-field-partition-key-checkbox"]');
timeStampCheckbox = wrapper
.find('[data-test="schema-log-stream-field-mapping-table"]')
.find('[data-test="schema-stream-_timestamp-field-fts-key-checkbox"]');
updateSettingsButton = wrapper.find(
'[data-test="schema-update-settings-button"]',
);
await logPartition.trigger("click");
await timeStampCheckbox.trigger("click");
});
it("Should make api call to save updated settings", async () => {
global.server.use(
rest.post(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/k8s_json/settings`,
(req: any, res: any, ctx: any) => {
return res(ctx.status(200), ctx.json({ code: 200 }));
},
),
);
await updateSettingsButton.trigger("submit");
expect(updateStream).toHaveBeenCalledTimes(1);
});
});
});
+176
View File
@@ -0,0 +1,176 @@
// 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/>.
import { http, HttpResponse } from "msw";
import { associate_members } from "../mockData/mockAssociateMembers";
import streams from "../mockData/streams";
import users from "../mockData/users";
import alerts from "../mockData/alerts";
import logs from "../mockData/logs";
import organizations from "../mockData/organizations";
import home from "../mockData/home";
import store from "./store";
// TODO OK: Move below rest handlers to separate file
export const restHandlers = [
http.get(
`${store.state.API_ENDPOINT}/api/organizations/associated_members/${store.state.selectedOrganization.identifier}`,
({ request }) => {
return HttpResponse.json(associate_members);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/functions?page_num=1&page_size=100000&sort_by=name&desc=false&name=`,
({ request }) => {
return HttpResponse.json({
list: [
{
function: ".a=123 \n .",
name: "lskjf",
params: "row",
numArgs: 1,
transType: 0,
},
{
function: ".a=123 \n .",
name: "nginx_function",
params: "row",
numArgs: 1,
transType: 0,
},
],
});
},
),
http.post(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/k8s_json/alerts/?type=logs`,
() => {
return HttpResponse.json({});
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/streams`,
({ request }) => {
return HttpResponse.json(streams.stream_list);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/k8s_json/schema`,
({ request }) => {
return HttpResponse.json(streams.stream_details);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/org_users`,
({ request }) => {
return HttpResponse.json(users.org_users);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/users`,
({ request }) => {
return HttpResponse.json(users.users);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/alerts`,
({ request }) => {
return HttpResponse.json(alerts.alerts.get);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/alerts/templates`,
({ request }) => {
return HttpResponse.json(alerts.templates.get);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/alerts/destinations`,
({ request }) => {
return HttpResponse.json(alerts.destinations.get);
},
),
http.post(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/_search`,
({ request }) => {
return HttpResponse.json(logs.search);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/organizations`,
({ request }) => {
return HttpResponse.json(organizations.list);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/summary`,
({ request }) => {
return HttpResponse.json(home.summary.get);
},
),
http.get(`${store.state.API_ENDPOINT}/config`, ({ request }) => {
return HttpResponse.json({
version: "v0.3.2",
instance: "7049348417797095424",
commit_hash: "3cc381d699e28bcb1b6d74310be16ec060b37e0d",
build_date: "2023-04-05T11:01:23Z",
default_fts_keys: ["log", "message", "msg", "content", "data"],
telemetry_enabled: true,
default_functions: [
{
name: "match_all_raw",
text: "match_all_raw('v')",
},
{
name: "match_all_raw_ignore_case",
text: "match_all_raw_ignore_case('v')",
},
{
name: "match_all",
text: "match_all('v')",
},
{
name: "str_match",
text: "str_match(field, 'v')",
},
{
name: "str_match_ignore_case",
text: "str_match_ignore_case(field, 'v')",
},
{
name: "re_match",
text: "re_match(field, 'pattern')",
},
{
name: "re_not_match",
text: "re_not_match(field, 'pattern')",
},
],
});
}),
];
+15
View File
@@ -1 +1,16 @@
// 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/>.
export * from "./install-quasar-plugin";
@@ -1,3 +1,18 @@
// 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/>.
import { config } from "@vue/test-utils";
import { cloneDeep } from "lodash-es";
import { Quasar } from "quasar";
@@ -1,3 +1,18 @@
// 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/>.
/**
* Injections for Components with a QPage root Element
*/
@@ -1,3 +1,18 @@
// 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/>.
import { config } from "@vue/test-utils";
// locally defined plugin, see "Writing a Plugin"
+15
View File
@@ -1,3 +1,18 @@
// 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/>.
import router from "@/router/routes";
// interface RouterMap {
+53 -132
View File
@@ -1,132 +1,27 @@
import { beforeAll, afterEach, afterAll, vi } from "vitest";
// 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/>.
// @ts-ignore
import { http, HttpResponse } from "msw";
import { beforeAll, afterEach, afterAll, vi } from "vitest";
import { setupServer } from "msw/node";
import { associate_members } from "../mockData/mockAssociateMembers";
import streams from "@/test/unit/mockData/streams";
import users from "../mockData/users";
import alerts from "../mockData/alerts";
import logs from "../mockData/logs";
import organizations from "../mockData/organizations";
import "whatwg-fetch";
import store from "./store";
import "../../__mocks__/index";
import home from "../mockData/home";
import.meta.env.VITE_OPENOBSERVE_ENDPOINT = "http://localhost:8080";
import { restHandlers } from "./handlers";
// TODO OK: Move below rest handlers to separate file
export const restHandlers = [
http.get(
`${store.state.API_ENDPOINT}/api/organizations/associated_members/${store.state.selectedOrganization.identifier}`,
({ request }) => {
return HttpResponse.json(associate_members);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/streams`,
({ request }) => {
return HttpResponse.json(streams.stream_list);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/k8s_json/schema`,
({ request }) => {
return HttpResponse.json(streams.stream_details);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/org_users`,
({ request }) => {
return HttpResponse.json(users.org_users);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/users`,
({ request }) => {
return HttpResponse.json(users.users);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/alerts`,
({ request }) => {
return HttpResponse.json(alerts.alerts.get);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/alerts/templates`,
({ request }) => {
return HttpResponse.json(alerts.templates.get);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/alerts/destinations`,
({ request }) => {
return HttpResponse.json(alerts.destinations.get);
},
),
http.post(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/_search`,
({ request }) => {
return HttpResponse.json(logs.search);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/organizations`,
({ request }) => {
return HttpResponse.json(organizations.list);
},
),
http.get(
`${store.state.API_ENDPOINT}/api/${store.state.selectedOrganization.identifier}/summary`,
({ request }) => {
return HttpResponse.json(home.summary.get);
},
),
http.get(`${store.state.API_ENDPOINT}/config`, ({ request }) => {
return HttpResponse.json({
version: "v0.3.2",
instance: "7049348417797095424",
commit_hash: "3cc381d699e28bcb1b6d74310be16ec060b37e0d",
build_date: "2023-04-05T11:01:23Z",
default_fts_keys: ["log", "message", "msg", "content", "data"],
telemetry_enabled: true,
default_functions: [
{
name: "match_all",
text: "match_all('v')",
},
{
name: "str_match",
text: "str_match(field, 'v')",
},
{
name: "str_match_ignore_case",
text: "str_match_ignore_case(field, 'v')",
},
{
name: "re_match",
text: "re_match(field, 'pattern')",
},
{
name: "re_not_match",
text: "re_not_match(field, 'pattern')",
},
],
});
}),
];
const server = setupServer(...restHandlers);
@@ -137,16 +32,42 @@ const server = setupServer(...restHandlers);
declare global {
// eslint-disable-next-line no-var
var server: any;
// eslint-disable-next-line no-var
var IntersectionObserver: {
new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;
prototype: IntersectionObserver;
};
}
class MockIntersectionObserver implements IntersectionObserver {
readonly root: Element | null = null;
readonly rootMargin: string = '';
readonly thresholds: ReadonlyArray<number> = [];
constructor(
private callback: IntersectionObserverCallback,
private options?: IntersectionObserverInit
) {}
observe = vi.fn();
unobserve = vi.fn();
disconnect = vi.fn();
takeRecords = vi.fn().mockReturnValue([]);
}
// Assign the mock to the global object
global.IntersectionObserver = MockIntersectionObserver;
vi.stubGlobal("server", server);
// Start server before all tests
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
global.document.queryCommandSupported = vi.fn().mockReturnValue(true);
// Close server after all tests
beforeAll(() => server.listen())
// Reset any request handlers after each test (for test isolation)
afterEach(() => server.resetHandlers())
// Stop the server when tests are done
afterAll(() => {
server.close();
});
// Reset handlers after each test `important for test isolation`
afterEach(() => server.resetHandlers());
server.close();
})
+20 -2
View File
@@ -1,12 +1,27 @@
// 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/>.
import { createStore } from "vuex";
const store = createStore({
state: {
API_ENDPOINT: "http://localhost:8080",
API_ENDPOINT: "http://localhost:5080",
selectedOrganization: {
label: "default Organization",
id: 159,
identifier: "default_organization_01",
identifier: "default",
user_email: "example@gmail.com",
subscription_type: "",
},
@@ -33,6 +48,9 @@ const store = createStore({
build_date: "2023-03-11T03:55:28Z",
default_fts_keys: ["log", "message", "msg", "content", "data"],
show_stream_stats_doc_num: true,
data_retention_days: true,
extended_data_retention_days: 45,
user_defined_schemas_enabled: true,
default_functions: [
{
name: "match_all",
@@ -1,3 +1,18 @@
// 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/>.
export const organizations = {
data: [
{
+15
View File
@@ -1,3 +1,18 @@
// 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/>.
export default {
alerts: {
get: {
+15
View File
@@ -1,3 +1,18 @@
// 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/>.
export default {
summary: {
get: {
+15
View File
@@ -1,3 +1,18 @@
// 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/>.
export default {
search: {
took: 75,
@@ -1,3 +1,18 @@
// 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/>.
export const associate_members = {
"data": [
{
@@ -1,3 +1,18 @@
// 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/>.
export default {
list: {
data: [
+15
View File
@@ -1,3 +1,18 @@
// 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/>.
export default {
stream_list: {
list: [
+15
View File
@@ -1,3 +1,18 @@
// 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/>.
export default {
org_users: {
data: [
@@ -1,9 +0,0 @@
// Vitest Snapshot v1
exports[`Alert List > Should render json when user clicks on json tab 1`] = `
"{
\\"_timestamp\\": \\"1680246906650420\\",
\\"kubernetes_container_name\\": \\"ziox\\",
\\"kubernetes_container_hash\\": \\"058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox@sha256:3dbbb0dc1eab2d5a3b3e4a75fd87d194e8095c92d7b2b62e7cdbd07020f54589\\"
}"
`;
@@ -1,42 +0,0 @@
// Vitest Snapshot v1
exports[`Search Result > Should render the q-virtual-scroll component with the correct props and call the 'onScroll' method when the 'virtual-scroll' event is emitted 1`] = `
"<div class=\\"q-markup-table q-table__container q-table__card q-table--horizontal-separator q-table--no-wrap q-virtual-scroll q-virtual-scroll--vertical scroll\\" data-test=\\"logs-search-result-logs-table\\" id=\\"searchGridComponent\\" tabindex=\\"0\\" data-v-ae03aa38=\\"\\" style=\\"height: calc(100vh - 0px);\\">
<table class=\\"q-table\\">
<thead class=\\"thead-sticky text-left\\" data-v-ae03aa38=\\"\\">
<tr data-v-ae03aa38=\\"\\">
<th class=\\"table-header\\" data-v-ae03aa38=\\"\\"><span class=\\"table-head-label\\" data-v-ae03aa38=\\"\\">timestamp</span></th>
<th class=\\"table-header\\" data-v-ae03aa38=\\"\\"><span class=\\"table-head-label\\" data-v-ae03aa38=\\"\\">source</span></th>
</tr>
</thead>
<tbody class=\\"q-virtual-scroll__padding\\">
<tr>
<td style=\\"height: 0px; --q-virtual-scroll-item-height: 25px;\\" colspan=\\"100\\"></td>
</tr>
</tbody>
<tbody class=\\"q-virtual-scroll__content\\" tabindex=\\"-1\\">
<tr class=\\"q-tr\\" data-test=\\"logs-search-result-detail-1680246906644637\\" style=\\"cursor: pointer;\\" data-v-ae03aa38=\\"\\">
<td class=\\"q-td \\" data-v-ae03aa38=\\"\\">
<div data-v-ae03aa38=\\"\\"><span iskeyword=\\"false\\" text=\\"Mar 31, 2023 12:45:06.644 +05:30\\"><span title=\\"\\">Mar 31, 2023 12:45:06.644 +05:30</span></span></div>
</td>
<td class=\\"q-td \\" data-v-ae03aa38=\\"\\">
<div data-v-ae03aa38=\\"\\"><span iskeyword=\\"false\\" text=\\"{&quot;_timestamp&quot;:1680246906644637,&quot;kubernetes_annotations_kubernetes_io_psp&quot;:&quot;eks.privileged&quot;,&quot;kubernetes_container_hash&quot;:&quot;058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox@sha256:3dbbb0dc1eab2d5a3b3e4a75fd87d194e8095c92d7b2b62e7cdbd07020f54589&quot;,&quot;kubernetes_container_image&quot;:&quot;058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox:v0.0.3&quot;,&quot;kubernetes_container_name&quot;:&quot;ziox&quot;,&quot;kubernetes_docker_id&quot;:&quot;56510ea4003f201fa7913c18d7c8dc639c576e5d1c4bbf0ead911d2d68ae8f31&quot;,&quot;kubernetes_host&quot;:&quot;ip-10-2-56-221.us-east-2.compute.internal&quot;,&quot;kubernetes_labels_app&quot;:&quot;ziox&quot;,&quot;kubernetes_labels_name&quot;:&quot;ziox-querier&quot;,&quot;kubernetes_labels_pod_template_hash&quot;:&quot;595748494c&quot;,&quot;kubernetes_labels_role&quot;:&quot;querier&quot;,&quot;kubernetes_namespace_name&quot;:&quot;ziox&quot;,&quot;kubernetes_pod_id&quot;:&quot;810e7dbc-fb09-4a8f-9108-6b8785754af9&quot;,&quot;kubernetes_pod_name&quot;:&quot;ziox-querier-595748494c-7xqlg&quot;,&quot;log&quot;:&quot;[2022-12-27T14:12:29Z INFO zinc_enl::service::search::datafusion] search file: Bhargav_organization_29/logs/default/2022/12/23/12/7012038228660338688.parquet, need add columns: [\\\\&quot;from\\\\&quot;]&quot;,&quot;stream&quot;:&quot;stderr&quot;}\\"><span title=\\"\\">{\\"_timestamp\\":1680246906644637,\\"kubernetes_annotations_kubernetes_io_psp\\":\\"eks.privileged\\",\\"kubernetes_container_hash\\":\\"058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox@sha256:3dbbb0dc1eab2d5a3b3e4a75fd87d194e8095c92d7b2b62e7cdbd07020f54589\\",\\"kubernetes_container_image\\":\\"058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox:v0.0.3\\",\\"kubernetes_container_name\\":\\"ziox\\",\\"kubernetes_docker_id\\":\\"56510ea4003f201fa7913c18d7c8dc639c576e5d1c4bbf0ead911d2d68ae8f31\\",\\"kubernetes_host\\":\\"ip-10-2-56-221.us-east-2.compute.internal\\",\\"kubernetes_labels_app\\":\\"ziox\\",\\"kubernetes_labels_name\\":\\"ziox-querier\\",\\"kubernetes_labels_pod_template_hash\\":\\"595748494c\\",\\"kubernetes_labels_role\\":\\"querier\\",\\"kubernetes_namespace_name\\":\\"ziox\\",\\"kubernetes_pod_id\\":\\"810e7dbc-fb09-4a8f-9108-6b8785754af9\\",\\"kubernetes_pod_name\\":\\"ziox-querier-595748494c-7xqlg\\",\\"log\\":\\"[2022-12-27T14:12:29Z INFO zinc_enl::service::search::datafusion] search file: Bhargav_organization_29/logs/default/2022/12/23/12/7012038228660338688.parquet, need add columns: [\\\\\\"from\\\\\\"]\\",\\"stream\\":\\"stderr\\"}</span></span></div>
</td>
</tr>
<tr class=\\"q-tr\\" data-test=\\"logs-search-result-detail-1680246906644599\\" style=\\"cursor: pointer;\\" data-v-ae03aa38=\\"\\">
<td class=\\"q-td \\" data-v-ae03aa38=\\"\\">
<div data-v-ae03aa38=\\"\\"><span iskeyword=\\"false\\" text=\\"Mar 31, 2023 12:45:06.644 +05:30\\"><span title=\\"\\">Mar 31, 2023 12:45:06.644 +05:30</span></span></div>
</td>
<td class=\\"q-td \\" data-v-ae03aa38=\\"\\">
<div data-v-ae03aa38=\\"\\"><span iskeyword=\\"false\\" text=\\"{&quot;_timestamp&quot;:1680246906644599,&quot;kubernetes_annotations_kubernetes_io_psp&quot;:&quot;eks.privileged&quot;,&quot;kubernetes_container_hash&quot;:&quot;058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox@sha256:3dbbb0dc1eab2d5a3b3e4a75fd87d194e8095c92d7b2b62e7cdbd07020f54589&quot;,&quot;kubernetes_container_image&quot;:&quot;058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox:v0.0.3&quot;,&quot;kubernetes_container_name&quot;:&quot;ziox&quot;,&quot;kubernetes_docker_id&quot;:&quot;56510ea4003f201fa7913c18d7c8dc639c576e5d1c4bbf0ead911d2d68ae8f31&quot;,&quot;kubernetes_host&quot;:&quot;ip-10-2-56-221.us-east-2.compute.internal&quot;,&quot;kubernetes_labels_app&quot;:&quot;ziox&quot;,&quot;kubernetes_labels_name&quot;:&quot;ziox-querier&quot;,&quot;kubernetes_labels_pod_template_hash&quot;:&quot;595748494c&quot;,&quot;kubernetes_labels_role&quot;:&quot;querier&quot;,&quot;kubernetes_namespace_name&quot;:&quot;ziox&quot;,&quot;kubernetes_pod_id&quot;:&quot;810e7dbc-fb09-4a8f-9108-6b8785754af9&quot;,&quot;kubernetes_pod_name&quot;:&quot;ziox-querier-595748494c-7xqlg&quot;,&quot;log&quot;:&quot;[2022-12-27T14:12:29Z INFO aws_config::default_provider::credentials] provide_credentials; provider=default_chain&quot;,&quot;stream&quot;:&quot;stderr&quot;}\\"><span title=\\"\\">{\\"_timestamp\\":1680246906644599,\\"kubernetes_annotations_kubernetes_io_psp\\":\\"eks.privileged\\",\\"kubernetes_container_hash\\":\\"058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox@sha256:3dbbb0dc1eab2d5a3b3e4a75fd87d194e8095c92d7b2b62e7cdbd07020f54589\\",\\"kubernetes_container_image\\":\\"058694856476.dkr.ecr.us-west-2.amazonaws.com/ziox:v0.0.3\\",\\"kubernetes_container_name\\":\\"ziox\\",\\"kubernetes_docker_id\\":\\"56510ea4003f201fa7913c18d7c8dc639c576e5d1c4bbf0ead911d2d68ae8f31\\",\\"kubernetes_host\\":\\"ip-10-2-56-221.us-east-2.compute.internal\\",\\"kubernetes_labels_app\\":\\"ziox\\",\\"kubernetes_labels_name\\":\\"ziox-querier\\",\\"kubernetes_labels_pod_template_hash\\":\\"595748494c\\",\\"kubernetes_labels_role\\":\\"querier\\",\\"kubernetes_namespace_name\\":\\"ziox\\",\\"kubernetes_pod_id\\":\\"810e7dbc-fb09-4a8f-9108-6b8785754af9\\",\\"kubernetes_pod_name\\":\\"ziox-querier-595748494c-7xqlg\\",\\"log\\":\\"[2022-12-27T14:12:29Z INFO aws_config::default_provider::credentials] provide_credentials; provider=default_chain\\",\\"stream\\":\\"stderr\\"}</span></span></div>
</td>
</tr>
</tbody>
<tbody class=\\"q-virtual-scroll__padding\\">
<tr>
<td style=\\"height: 0px; --q-virtual-scroll-item-height: 25px;\\" colspan=\\"100\\"></td>
</tr>
</tbody>
</table>
</div>"
`;
-35
View File
@@ -1,35 +0,0 @@
import { mount } from "@vue/test-utils";
import About from "../../../views/About.vue";
import { Quasar, Dialog, Notify } from "quasar";
import { expect, it } from "vitest";
import i18n from "../../../locales";
import { createStore } from "vuex";
import { installQuasar } from "../helpers/install-quasar-plugin";
installQuasar();
const store = createStore({
state: {
zoConfig: {
version: "1",
commit_hash: "test",
build_date: "01-01-23",
},
API_ENDPOINT: "test",
},
});
it("should mount About view", async () => {
const wrapper = mount(About, {
shallow: false,
components: {
Notify,
Dialog,
},
global: {
plugins: [i18n],
provide: {
store: store,
},
},
});
expect(About).toBeTruthy();
});
@@ -1,28 +0,0 @@
// Vitest Snapshot v1
exports[`Streams > Should match previous table snapshot 1`] = `
"<table class=\\"q-table\\">
<thead>
<tr>
<th class=\\"text-left\\">#</th>
<th class=\\"text-left sortable\\">Name<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Type<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Doc Num<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Ingested Data<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-left sortable\\">Compressed Size<i class=\\"q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left\\" aria-hidden=\\"true\\" role=\\"presentation\\">arrow_upward</i></th>
<th class=\\"text-center\\">Actions</th>
</tr>
</thead>
<tbody>
<tr class=\\"\\">
<td class=\\"text-left\\">01</td>
<td class=\\"text-left\\">k8s_json</td>
<td class=\\"text-left\\">logs</td>
<td class=\\"text-left\\">400</td>
<td class=\\"text-left\\">0.74 MB</td>
<td class=\\"text-left\\">0.03 MB</td>
<td class=\\"q-td text-center\\"><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Delete\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><span class=\\"q-icon\\" aria-hidden=\\"true\\" role=\\"img\\"><img src=\\"/src/assets/images/common/delete_icon.svg\\"></span></span></button><button class=\\"q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs iconHoverBtn\\" style=\\"font-size: 10px; padding: 8px; min-width: 0; min-height: 0;\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Stream Detail\\"><span class=\\"q-focus-helper\\"></span><span class=\\"q-btn__content text-center col items-center q-anchor--skip justify-center row\\"><span class=\\"q-icon\\" aria-hidden=\\"true\\" role=\\"img\\"><img src=\\"/src/assets/images/common/list_icon.svg\\"></span></span></button></td>
</tr>
</tbody>
</table>"
`;
+12 -11
View File
@@ -22,7 +22,6 @@ import { useStore } from "vuex";
import useStreams from "@/composables/useStreams";
import userService from "@/services/users";
import { DateTime as _DateTime } from "luxon";
import store from "../stores";
import cronParser from "cron-parser";
let moment: any;
@@ -965,32 +964,34 @@ export const deepCopy = (value: any) => {
return JSON.parse(JSON.stringify(value));
};
export const getWebSocketUrl = (request_id: string, org_identifier: string) => {
export const getWebSocketUrl = (
request_id: string,
org_identifier: string,
apiEndPoint: string,
) => {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${protocol}//${store.state.API_ENDPOINT.split("//")[1]}/api/${org_identifier}/ws/v2/${request_id}`;
return `${protocol}//${apiEndPoint.split("//")[1]}/api/${org_identifier}/ws/v2/${request_id}`;
};
export const isWebSocketEnabled = () => {
if (!store.state.zoConfig?.websocket_enabled) {
export const isWebSocketEnabled = (data: any) => {
if (!data.zoConfig?.websocket_enabled) {
return false;
}
if ((window as any).use_web_socket === undefined) {
return store?.state?.organizationData?.organizationSettings
?.enable_websocket_search;
return data.organizationData?.organizationSettings?.enable_websocket_search;
} else {
return (window as any).use_web_socket;
}
};
export const isStreamingEnabled = () => {
if (!store.state.zoConfig?.streaming_enabled) {
export const isStreamingEnabled = (data: any) => {
if (!data.zoConfig?.streaming_enabled) {
return false;
}
if ((window as any).use_streaming === undefined) {
return store?.state?.organizationData?.organizationSettings
?.enable_streaming_search;
return data.organizationData?.organizationSettings?.enable_streaming_search;
} else {
return (window as any).use_streaming;
}
+50
View File
@@ -0,0 +1,50 @@
// 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/>.
import { mount } from "@vue/test-utils";
import About from "@/views/About.vue";
import { Quasar, Dialog, Notify } from "quasar";
import { expect, it } from "vitest";
import i18n from "@/locales";
import { createStore } from "vuex";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
installQuasar();
const store = createStore({
state: {
zoConfig: {
version: "1",
commit_hash: "test",
build_date: "01-01-23",
},
API_ENDPOINT: "test",
},
});
it("should mount About view", async () => {
const wrapper = mount(About, {
shallow: false,
components: {
Notify,
Dialog,
},
global: {
plugins: [i18n],
provide: {
store: store,
},
},
});
expect(About).toBeTruthy();
});
@@ -15,12 +15,12 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount } from "@vue/test-utils";
import { installQuasar } from "../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import AppAlerts from "../../../views/AppAlerts.vue";
import i18n from "../../../locales";
import store from "../helpers/store";
import router from "../helpers/router";
import AppAlerts from "@/views/AppAlerts.vue";
import i18n from "@/locales";
import store from "@/test/unit/helpers/store";
import router from "@/test/unit/helpers/router";
installQuasar({
plugins: [Dialog, Notify],
@@ -1,11 +1,26 @@
// 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/>.
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import LogStream from "../../../views/LogStream.vue";
import i18n from "../../../locales";
import store from "../helpers/store";
import router from "../helpers/router";
import LogStream from "@/views/LogStream.vue";
import i18n from "@/locales";
import store from "@/test/unit/helpers/store";
import router from "@/test/unit/helpers/router";
installQuasar({
plugins: [Dialog, Notify],
@@ -15,14 +15,14 @@
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import Logs from "@/views/Logs.vue";
import i18n from "@/locales";
import store from "../helpers/store";
import SearchPlugin from "../helpers/logSearchPlugin";
import router from "../helpers/router";
import store from "@/test/unit/helpers/store";
import SearchPlugin from "@/test/unit/helpers/logSearchPlugin";
import router from "@/test/unit/helpers/router";
const node = document.createElement("div");
node.setAttribute("id", "app");
@@ -1,11 +1,26 @@
// 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/>.
import { describe, expect, it, beforeEach, vi, afterEach } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { installQuasar } from "../helpers/install-quasar-plugin";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import { Dialog, Notify } from "quasar";
import User from "@/views/User.vue";
import i18n from "@/locales";
import store from "../helpers/store";
import router from "../helpers/router";
import store from "@/test/unit/helpers/store";
import router from "@/test/unit/helpers/router";
installQuasar({
plugins: [Dialog, Notify],
@@ -0,0 +1,50 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Streams > Should match previous table snapshot 1`] = `
"<table class="q-table">
<thead>
<tr>
<th class="q-table--col-auto-width">
<div data-v-d611dbca="" class="q-checkbox cursor-pointer no-outline row inline no-wrap items-center" tabindex="0" role="checkbox" aria-checked="false">
<div class="q-checkbox__inner relative-position non-selectable q-checkbox__inner--falsy" style="font-size: 35px;" aria-hidden="true"><input class="hidden q-checkbox__native absolute q-ma-none q-pa-none" type="checkbox">
<div class="q-checkbox__bg absolute"><svg class="q-checkbox__svg fit absolute-full" viewBox="0 0 24 24">
<path class="q-checkbox__truthy" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"></path>
<path class="q-checkbox__indet" d="M4,14H20V10H4"></path>
</svg></div>
</div><span class="no-outline" tabindex="-1"></span>
</div>
</th>
<th class="text-left">#</th>
<th class="text-left sortable sorted ">Name<i class="q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left" aria-hidden="true">arrow_upward</i></th>
<th class="text-left">Type</th>
<th class="text-left sortable">Events<i class="q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left" aria-hidden="true">arrow_upward</i></th>
<th class="text-left sortable">Ingested Data<i class="q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left" aria-hidden="true">arrow_upward</i></th>
<th class="text-left">Compressed Size</th>
<th class="text-left sortable">Index Size<i class="q-icon notranslate material-icons q-table__sort-icon q-table__sort-icon--left" aria-hidden="true">arrow_upward</i></th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<tr class="">
<td class="q-table--col-auto-width">
<div data-v-d611dbca="" class="q-checkbox cursor-pointer no-outline row inline no-wrap items-center" tabindex="0" role="checkbox" aria-checked="false">
<div class="q-checkbox__inner relative-position non-selectable q-checkbox__inner--falsy" style="font-size: 35px;" aria-hidden="true"><input class="hidden q-checkbox__native absolute q-ma-none q-pa-none" type="checkbox">
<div class="q-checkbox__bg absolute"><svg class="q-checkbox__svg fit absolute-full" viewBox="0 0 24 24">
<path class="q-checkbox__truthy" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"></path>
<path class="q-checkbox__indet" d="M4,14H20V10H4"></path>
</svg></div>
</div><span class="no-outline" tabindex="-1"></span>
</div>
</td>
<td class="text-left">01</td>
<td class="text-left">k8s_json</td>
<td class="text-left">logs</td>
<td class="text-left">400</td>
<td class="text-left">0.74 MB</td>
<td class="text-left">0.03 MB</td>
<td class="text-left">NaN MB</td>
<td data-v-d611dbca="" class="q-td text-center"><button data-v-d611dbca="" class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs" style="font-size: 10px; padding: 8px; min-width: 0; min-height: 0;" tabindex="0" type="button" title="Explore"><span class="q-focus-helper"></span><span class="q-btn__content text-center col items-center q-anchor--skip justify-center row"><i class="q-icon notranslate material-icons" aria-hidden="true" role="img">search</i></span></button><button data-v-d611dbca="" class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs" style="font-size: 10px; padding: 8px; min-width: 0; min-height: 0;" tabindex="0" type="button" title="Stream Detail"><span class="q-focus-helper"></span><span class="q-btn__content text-center col items-center q-anchor--skip justify-center row"><i class="q-icon notranslate material-icons" aria-hidden="true" role="img">list_alt</i></span></button><button data-v-d611dbca="" class="q-btn q-btn-item non-selectable no-outline q-btn--flat q-btn--round q-btn--actionable q-focusable q-hoverable q-ml-xs" style="font-size: 10px; padding: 8px; min-width: 0; min-height: 0;" tabindex="0" type="button" title="Delete"><span class="q-focus-helper"></span><span class="q-btn__content text-center col items-center q-anchor--skip justify-center row"><i class="q-icon" aria-hidden="true" role="img"><svg viewBox="0 0 24 24"><path style="fill: none;" d="M0 0h24v24H0V0z"></path><path d="M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z"></path></svg></i></span></button></td>
</tr>
</tbody>
</table>"
`;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.ts", "src/**/*.tsx"],
"exclude": ["src/**/__tests__/*", "node_modules", "dist"],
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.{ts,tsx,js}"],
"exclude": ["src/**/__tests__/*", "node_modules", "dist", "src/**/*.spec.{ts,js}"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"references": [{ "path": "./tsconfig.node.json" }],
"include": ["src/**/*.ts", "vite.config.*", "vitest.config.*", "cypress.config.*"],
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"allowJs": true,
+5 -1
View File
@@ -1,6 +1,7 @@
{
"extends": "./tsconfig.app.json",
"exclude": [],
"include": ["src/**/*.spec.{ts,js}", "vitest.config.*"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"allowJs": true,
"target": "esnext",
@@ -19,5 +20,8 @@
"types": ["node"],
"baseUrl": ".",
"downlevelIteration": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
+12 -50
View File
@@ -21,11 +21,11 @@ import vueJsx from "@vitejs/plugin-vue-jsx";
import { quasar, transformAssetUrls } from "@quasar/vite-plugin";
import nodePolyfills from "rollup-plugin-node-polyfills";
import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfill";
import path from "path";
import dotenv from "dotenv";
import fs from "fs-extra";
import * as path from "path";
import * as dotenv from "dotenv";
import * as fs from "fs-extra";
import monacoEditorPlugin from "vite-plugin-monaco-editor";
import visualizer from "rollup-plugin-visualizer";
import { visualizer } from "rollup-plugin-visualizer";
import "dotenv/config";
import istanbul from "vite-plugin-istanbul";
@@ -43,7 +43,7 @@ const isTesting = process.env.NODE_ENV === "test";
const enterpriseResolverPlugin = {
name: "enterprise-resolver",
async resolveId(source) {
async resolveId(source: string) {
if (source.startsWith("@zo/")) {
const fileName = source.replace("@zo/", "");
@@ -69,7 +69,7 @@ function monacoEditorTestResolver() {
return {
name: "monaco-editor-test-resolver",
enforce: "post",
resolveId(id) {
resolveId(id: string) {
if (id === "monaco-editor") {
return {
id: "monaco-editor/esm/vs/editor/editor.api",
@@ -116,21 +116,21 @@ export default defineConfig({
process.env.VITE_COVERAGE === "true" &&
istanbul({
include: "src/**/*",
exclude: ["node_modules", "test/"],
exclude: ["node_modules", "test/", "src/**/*.spec.{ts,js}"],
extension: [".js", ".ts", ".vue"],
requireEnv: false,
forceBuildInstrument: true,
}),
enterpriseResolverPlugin,
vueJsx(),
monacoEditorPlugin.default({
(monacoEditorPlugin as any).default({
customDistPath: () => path.resolve(__dirname, "dist/monacoeditorwork"),
}),
isTesting && monacoEditorTestResolver(),
].filter(Boolean),
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
"@": fileURLToPath(new URL('./src', import.meta.url)),
"@enterprise": fileURLToPath(
new URL("./src/enterprise", import.meta.url),
),
@@ -148,8 +148,8 @@ export default defineConfig({
chunkSizeWarningLimit: 3000,
rollupOptions: {
plugins: [
nodePolyfills(),
visualizer.default({
nodePolyfills() as any,
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
@@ -185,43 +185,5 @@ export default defineConfig({
plugins: [NodeGlobalsPolyfillPlugin({ buffer: true })],
target: "es2020",
},
},
test: {
enable: true,
global: true,
setupFiles: "test/unit/helpers/setupTests.ts",
deps: {
inline: ["monaco-editor", "vitest-canvas-mock"],
},
coverage: {
reporter: ["text", "json", "html"],
all: true,
exclude: [
"coverage/**",
"dist/**",
"packages/*/test{,s}/**",
"cypress/**",
"src/test/**",
"test{,s}/**",
"test{,-*}.{js,cjs,mjs,ts,tsx,jsx}",
"**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx}",
"**/*{.,-}spec.{js,cjs,mjs,ts,tsx,jsx}",
"**/__tests__/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
"**/.{eslint,mocha,prettier}rc.{js,cjs,yml}",
"quasar.conf.js",
"env.d.ts",
],
},
threads: false,
environment: "jsdom",
cache: false,
maxConcurrency: 20,
update: false,
environmentOptions: {
jsdom: {
resources: "usable",
},
},
},
}
});
+99
View File
@@ -0,0 +1,99 @@
import { fileURLToPath } from 'node:url'
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
import type { ConfigEnv } from 'vitest/config'
import type { UserConfig } from 'vite'
import viteConfig from './vite.config'
// Convert the vite config to a plain object if it's a function
const viteConfigObj = typeof viteConfig === 'function'
? (viteConfig as (options: ConfigEnv) => UserConfig)({ command: 'serve', mode: 'development' })
: viteConfig
export default mergeConfig(
viteConfigObj,
defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/**'],
include: ["src/**/*.spec.{ts,js,vue}"],
root: fileURLToPath(new URL('.', import.meta.url)),
setupFiles: ['src/test/unit/helpers/setupTests.ts'],
deps: {
inline: ["monaco-editor", "vitest-canvas-mock"],
},
coverage: {
reporter: ["text", "json", "html"],
all: true,
exclude: [
"coverage/**",
"dist/**",
"packages/*/test{,s}/**",
"cypress/**",
"src/test/**",
"test{,s}/**",
"test{,-*}.{js,cjs,mjs,ts,tsx,jsx}",
"**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx}",
"**/*{.,-}spec.{js,cjs,mjs,ts,tsx,jsx}",
"**/__tests__/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
"**/.{eslint,mocha,prettier}rc.{js,cjs,yml}",
"quasar.conf.js",
"env.d.ts",
],
},
},
}),
)
// import { defineConfig } from 'vitest/config';
// import path from 'path';
// import vue from '@vitejs/plugin-vue'
// import { quasar } from "@quasar/vite-plugin";
// export default defineConfig({
// plugins: [vue(), quasar()],
// test: {
// globals: true,
// setupFiles: "test/unit/helpers/setupTests.ts",
// deps: {
// inline: ["monaco-editor", "vitest-canvas-mock"],
// },
// coverage: {
// reporter: ["text", "json", "html"],
// all: true,
// exclude: [
// "coverage/**",
// "dist/**",
// "packages/*/test{,s}/**",
// "cypress/**",
// "src/test/**",
// "test{,s}/**",
// "test{,-*}.{js,cjs,mjs,ts,tsx,jsx}",
// "**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx}",
// "**/*{.,-}spec.{js,cjs,mjs,ts,tsx,jsx}",
// "**/__tests__/**",
// "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
// "**/.{eslint,mocha,prettier}rc.{js,cjs,yml}",
// "quasar.conf.js",
// "env.d.ts",
// ],
// },
// environment: "jsdom",
// environmentOptions: {
// jsdom: {
// resources: "usable",
// },
// },
// },
// resolve: {
// alias: {
// '@': path.resolve(__dirname, './src'),
// },
// },
// });