diff --git a/src/handler/http/models/alerts/responses.rs b/src/handler/http/models/alerts/responses.rs index 4432954630..0f7f728f86 100644 --- a/src/handler/http/models/alerts/responses.rs +++ b/src/handler/http/models/alerts/responses.rs @@ -44,6 +44,7 @@ pub struct ListAlertsResponseBodyItem { pub enabled: bool, pub last_triggered_at: Option, pub last_satisfied_at: Option, + pub is_real_time: bool, } /// HTTP response body for `EnableAlert` endpoint. @@ -101,6 +102,7 @@ impl TryFrom<(meta_folders::Folder, meta_alerts::Alert, Option)> enabled: alert.enabled, last_triggered_at, last_satisfied_at, + is_real_time: alert.is_real_time, }) } } diff --git a/web/src/components/alerts/AlertList.vue b/web/src/components/alerts/AlertList.vue index 2a7eff7626..08eab78a0a 100644 --- a/web/src/components/alerts/AlertList.vue +++ b/web/src/components/alerts/AlertList.vue @@ -23,260 +23,359 @@ along with this program. If not, see . style="height: calc(100vh - 57px)" :class="store.state.theme === 'dark' ? 'dark-theme' : 'light-theme'" > -
-
- {{ t("alerts.header") }} -
-
- - - - -
- - - - {{ searchAcrossFolders - ? t("dashboard.searchSelf") - : t("dashboard.searchAll") }} - -
-
- - +
+
+ {{ t("alerts.header") }}
-
- + + -
@@ -513,6 +614,7 @@ import { defineAsyncComponent, onMounted, computed, + reactive, } from "vue"; import type { Ref } from "vue"; import { useStore } from "vuex"; @@ -549,6 +651,7 @@ import FolderList from "../common/sidebar/FolderList.vue"; import MoveAcrossFolders from "../common/sidebar/MoveAcrossFolders.vue"; import { toRaw } from "vue"; import { nextTick } from "vue"; +import AppTabs from "@/components/common/AppTabs.vue"; // import alertList from "./alerts"; export default defineComponent({ @@ -556,13 +659,14 @@ export default defineComponent({ components: { QTablePagination, AddAlert: defineAsyncComponent( - () => import("@/components/alerts/AddAlert.vue") + () => import("@/components/alerts/AddAlert.vue"), ), NoData, ConfirmDialog, ImportAlert, FolderList, MoveAcrossFolders, + AppTabs, }, emits: [ "updated:fields", @@ -574,10 +678,6 @@ export default defineComponent({ const { t } = useI18n(); const $q = useQuasar(); const router = useRouter(); - const alerts: Ref = ref([]); - const alertsRows: Ref = ref([]); - const alertRowsToDisplay: Ref = ref([]); - const formData: Ref = ref({}); const toBeClonedAlert: Ref = ref({}); const showAddAlertDialog: any = ref(false); @@ -621,245 +721,267 @@ export default defineComponent({ } else { expandedRow.value = props.row.alert_id; } - } + }; const activeFolderToMove = ref("default"); - + + const activeTab = ref("scheduled"); + + const tabs = reactive([ + { + label: t("alerts.scheduled"), + value: "scheduled", + }, + { + label: t("alerts.realTime"), + value: "realTime", + }, + ]); const columns = computed(() => { const baseColumns: any = [ - { - name: "#", - label: "#", - field: "#", - align: "left", - }, - { - name: "name", - field: "name", - label: t("alerts.name"), - align: "left", - sortable: true, - }, - { - name: "owner", - field: "owner", - label: t("alerts.owner"), - align: "center", - sortable: true, - style: "width: 150px", - }, - { - name: "period", - field: "period", - label: t("alerts.period"), - align: "center", - sortable: true, - style: "width: 150px", - }, - { - name: "frequency", - field: "frequency", - label: t("alerts.frequency"), - align: "left", - sortable: true, - style: "width: 150px", - }, - { - name: "last_triggered_at", - field: "last_triggered_at", - label: t("alerts.lastTriggered"), - align: "left", - sortable: true, - style: "width: 150px", - }, - { - name: "last_satisfied_at", - field: "last_satisfied_at", - label: t("alerts.lastSatisfied"), - align: "left", - sortable: true, - style: "width: 150px", - }, - { - name: "actions", - field: "actions", - label: t("alerts.actions"), - align: "center", - sortable: false, - style: "width: 150px", - - }, - ]; - if(searchAcrossFolders.value && searchQuery.value != ''){ - baseColumns.splice(2,0,{ - name: "folder_name", - field: "folder_name", - label: 'Folder', - align: "center", - sortable: true, - style: "width: 150px", - }) - } - return baseColumns; - }) - const activeTab: any = ref("alerts"); + { + name: "#", + label: "#", + field: "#", + align: "left", + }, + { + name: "name", + field: "name", + label: t("alerts.name"), + align: "left", + sortable: true, + }, + { + name: "owner", + field: "owner", + label: t("alerts.owner"), + align: "center", + sortable: true, + style: "width: 150px", + }, + { + name: "period", + field: "period", + label: t("alerts.period"), + align: "center", + sortable: true, + style: "width: 150px", + }, + { + name: "frequency", + field: "frequency", + label: t("alerts.frequency"), + align: "left", + sortable: true, + style: "width: 150px", + }, + { + name: "last_triggered_at", + field: "last_triggered_at", + label: t("alerts.lastTriggered"), + align: "left", + sortable: true, + style: "width: 150px", + }, + { + name: "last_satisfied_at", + field: "last_satisfied_at", + label: t("alerts.lastSatisfied"), + align: "left", + sortable: true, + style: "width: 150px", + }, + { + name: "actions", + field: "actions", + label: t("alerts.actions"), + align: "center", + sortable: false, + style: "width: 150px", + }, + ]; + if (searchAcrossFolders.value && searchQuery.value != "") { + baseColumns.splice(2, 0, { + name: "folder_name", + field: "folder_name", + label: "Folder", + align: "center", + sortable: true, + style: "width: 150px", + }); + } + return baseColumns; + }); const destinations = ref([0]); const templates = ref([0]); - const selected: Ref = ref([]); - const allSelected = ref(false); + const selectedAlerts: Ref = ref([]); + const allSelectedAlerts = ref(false); - const searchQuery = ref(""); - const filterQuery = ref(""); - const searchAcrossFolders = ref(false); + const searchQuery = ref(""); + const filterQuery = ref(""); + const searchAcrossFolders = ref(false); const filteredResults: Ref = ref([]); const selectedAlertToMove: Ref = ref({}); const getAlertsByFolderId = async (store: any, folderId: any) => { - try { - if (!store.state.organizationData.allAlertsListByFolderId[folderId]) { - await getAlertsFn(store, folderId); - } - else{ - alertsRows.value = store.state.organizationData.allAlertsListByFolderId[folderId]; - } - } catch (error) { - throw error; + try { + //this is the condition where we are fetching the alerts from the server + // assigning it to the allAlertsListByFolderId in the store + if (!store.state.organizationData.allAlertsListByFolderId[folderId]) { + await getAlertsFn(store, folderId); + } else { + //this is the condition where we are assigning the alerts to the filteredResults so whenever + // we are changing the folder, we are not fetching the alerts again, we are just assigning the alerts to the filteredResults + filteredResults.value = + store.state.organizationData.allAlertsListByFolderId[folderId]; } + } catch (error) { + throw error; + } }; - const getAlertsFn = async (store: any, folderId: string, query = "") => { - selected.value = []; - allSelected.value = false; - toggleAll(); - if(query){ - alertsRows.value = []; - } - const dismiss = $q.notify({ - spinner: true, - message: "Please wait while loading alerts...", + const getAlertsFn = async (store: any, folderId: any, query = "", refreshResults = true) => { + selectedAlerts.value = []; + allSelectedAlerts.value = false; + if (query){ + //here we reset the filteredResults before fetching the filtered alerts + filteredResults.value = []; + } + const dismiss = $q.notify({ + spinner: true, + message: "Please wait while loading alerts...", + }); + if (query) { + folderId = ""; + } + alertsService + .listByFolderId( + 1, + 1000, + "name", + false, + "", + store.state.selectedOrganization.identifier, + folderId, + query, + ) + .then(async (res) => { + var counter = 1; + //this is the alerts that we use to store + let alerts = []; + alerts = res.data.list.map((alert: any) => { + return { + ...alert, + uuid: getUUID(), + }; }); - if(query){ - folderId = "" - } - alertsService - .listByFolderId( - 1, - 1000, - "name", - false, - "", - store.state.selectedOrganization.identifier, - folderId, - query - ) - .then(async (res) => { - var counter = 1; - alerts.value = res.data.list.map((alert: any) => { - return { - ...alert, - uuid: getUUID(), - }; - }); - alertsRows.value = alerts.value.map((data: any) => { - let conditions = "--"; - if (data.condition.conditions?.length) { - conditions = data.condition.conditions - .map((condition: any) => { - return `${condition.column} ${condition.operator} ${condition.value}`; - }) - .join(" AND "); - } else if (data.condition.sql) { - conditions = data.condition.sql; - } else if (data.condition.promql) { - conditions = data.condition.promql; - } - let frequency = ""; - if(data.trigger_condition?.frequency_type == 'cron'){ - frequency = data.trigger_condition.cron; - }else{ - frequency = data.trigger_condition.frequency; - } + //general alerts that we use to display (formatting the alerts into the table format) + alerts = alerts.map((data: any) => { + let conditions = "--"; + if (data.condition.conditions?.length) { + conditions = data.condition.conditions + .map((condition: any) => { + return `${condition.column} ${condition.operator} ${condition.value}`; + }) + .join(" AND "); + } else if (data.condition.sql) { + conditions = data.condition.sql; + } else if (data.condition.promql) { + conditions = data.condition.promql; + } + let frequency = ""; + if (data.trigger_condition?.frequency_type == "cron") { + frequency = data.trigger_condition.cron; + } else { + frequency = data.trigger_condition.frequency; + } - return { - "#": counter <= 9 ? `0${counter++}` : counter++, - alert_id: data.alert_id, - name: data.name, - alert_type: data.is_real_time ? "Real Time" : "Scheduled", - stream_name: data.stream_name ? data.stream_name : "--", - stream_type: data.stream_type, - enabled: data.enabled, - conditions: conditions, - description: data.description, - uuid: data.uuid, - owner: data.owner, - period: data?.trigger_condition?.period, - frequency: frequency, - frequency_type: data?.trigger_condition?.frequency_type, - last_triggered_at: convertUnixToQuasarFormat( - data.last_triggered_at - ), - last_satisfied_at: convertUnixToQuasarFormat( - data.last_satisfied_at - ), - selected: false, - type: data.condition.type, - folder_name: { - name: data.folder_name, - id: data.folder_id - }, - }; - }); - alertsRows.value.forEach((alert: AlertListItem) => { - alertStateLoadingMap.value[alert.uuid as string] = false; - }); - if(query == "" && !searchAcrossFolders.value){ - store.dispatch("setAllAlertsListByFolderId", { - ...store.state.organizationData.allAlertsListByFolderId, - [folderId]: alertsRows.value - }); - }else{ - filteredResults.value = alertsRows.value; - } - if(router.currentRoute.value.query.action == "import"){ - showImportAlertDialog.value = true; - } - if (router.currentRoute.value.query.action == "add") { - showAddUpdateFn({ row: undefined }); - } - if (router.currentRoute.value.query.action == "update") { - const alertId = router.currentRoute.value.query.alert_id as string; - const alert = await getAlertById(alertId); - - showAddUpdateFn( { - row: alert, - }); - } - dismiss(); - }) - .catch((e) => { - console.error(e); - dismiss(); - $q.notify({ - type: "negative", - message: "Error while pulling alerts.", - timeout: 2000, - }); + return { + "#": counter <= 9 ? `0${counter++}` : counter++, + alert_id: data.alert_id, + name: data.name, + alert_type: data.is_real_time ? "Real Time" : "Scheduled", + stream_name: data.stream_name ? data.stream_name : "--", + stream_type: data.stream_type, + enabled: data.enabled, + conditions: conditions, + description: data.description, + uuid: data.uuid, + owner: data.owner, + period: data?.trigger_condition?.period, + frequency: frequency, + frequency_type: data?.trigger_condition?.frequency_type, + last_triggered_at: convertUnixToQuasarFormat( + data.last_triggered_at, + ), + last_satisfied_at: convertUnixToQuasarFormat( + data.last_satisfied_at, + ), + selected: false, + type: data.condition.type, + folder_name: { + name: data.folder_name, + id: data.folder_id, + }, + }; + }); + //this is the condition where we are setting the alertStateLoadingMap + alerts.forEach((alert: any) => { + alertStateLoadingMap.value[alert.uuid as string] = false; + }); + //this is the condition where we are setting the allAlertsListByFolderId in the store + store.dispatch("setAllAlertsListByFolderId", { + ...store.state.organizationData.allAlertsListByFolderId, + [folderId]: alerts, + }); + //this is the condition where we are setting the filteredResults + //1. If it is search across folders then also we are setting the filteredResults(which contains the filtered alerts) + //2. If it is not search across folders then we are setting the filteredResults to the alerts(which contains all the alerts) + if(refreshResults){ + filteredResults.value = alerts; + } + filterAlertsByTab(); + if (router.currentRoute.value.query.action == "import") { + showImportAlertDialog.value = true; + } + if (router.currentRoute.value.query.action == "add") { + showAddUpdateFn({ row: undefined }); + } + if (router.currentRoute.value.query.action == "update") { + const alertId = router.currentRoute.value.query.alert_id as string; + const alert = await getAlertById(alertId); + + showAddUpdateFn({ + row: alert, }); - }; - const getAlertByName = (name: string) => { - return alerts.value.find((alert) => alert.name === name); + } + dismiss(); + }) + .catch((e) => { + console.error(e); + dismiss(); + $q.notify({ + type: "negative", + message: "Error while pulling alerts.", + timeout: 2000, + }); + }); }; const getAlertById = async (id: string) => { const dismiss = $q.notify({ spinner: true, message: "Please wait while loading alert...", }); - try { - const res = await alertsService.get_by_alert_id(store.state.selectedOrganization.identifier, id); + try { + const res = await alertsService.get_by_alert_id( + store.state.selectedOrganization.identifier, + id, + ); dismiss(); return res.data; - } catch (error) { + } catch (error) { dismiss(); throw error; - } + } }; onBeforeMount(async () => { await getTemplates(); @@ -867,12 +989,12 @@ export default defineComponent({ }); onActivated(() => getDestinations()); onMounted(async () => { - if(!store.state.organizationData.foldersByType){ + if (!store.state.organizationData.foldersByType) { await getFoldersListByType(store, "alerts"); } if ( router.currentRoute.value.query.folder && - store.state.organizationData.foldersByType.find( + store.state.organizationData?.foldersByType?.find( (it: any) => it.folderId === router.currentRoute.value.query.folder, ) ) { @@ -880,34 +1002,41 @@ export default defineComponent({ } else { activeFolderId.value = "default"; } - getAlertsFn(store, activeFolderId.value); - + await getAlertsFn(store, router.currentRoute.value.query.folder ?? "default"); }); - watch(()=> activeFolderId.value, async (newVal)=>{ - if(searchAcrossFolders.value) { - searchAcrossFolders.value = false - searchQuery.value = ""; - filteredResults.value = []; - } - await getAlertsByFolderId(store,newVal); - if(router.currentRoute.value.query.folder != newVal){ - router.push({ - name: "alertList", - query: { - org_identifier: store.state.selectedOrganization.identifier, - folder: activeFolderId.value, - }, - }); - } - - - }) + watch( + () => activeFolderId.value, + async (newVal) => { + selectedAlerts.value = []; + allSelectedAlerts.value = false; + if(newVal == router.currentRoute.value.query.folder){ + return; + } + if (searchAcrossFolders.value) { + searchAcrossFolders.value = false; + searchQuery.value = ""; + filteredResults.value = []; + } + await getAlertsByFolderId(store, newVal); + if (router.currentRoute.value.query.folder != newVal) { + router.push({ + name: "alertList", + query: { + org_identifier: store.state.selectedOrganization.identifier, + folder: activeFolderId.value, + }, + }); + } + }, + ); watch(searchQuery, async (newQuery) => { - await debouncedSearch(newQuery); - if(searchQuery.value == ""){ - filteredResults.value = []; + selectedAlerts.value = []; + allSelectedAlerts.value = false; + if(newQuery == ""){ + filteredResults.value = store.state.organizationData.allAlertsListByFolderId[activeFolderId.value]; } + await debouncedSearch(newQuery); }); watch( () => router.currentRoute.value.query.action, @@ -916,7 +1045,7 @@ export default defineComponent({ showAddAlertDialog.value = false; showImportAlertDialog.value = false; } - } + }, ); const getDestinations = async () => { destinationService @@ -932,7 +1061,7 @@ export default defineComponent({ type: "negative", message: "Error while fetching destinations.", timeout: 3000, - }) + }), ); }; @@ -949,7 +1078,7 @@ export default defineComponent({ type: "negative", message: "Error while fetching templates.", timeout: 3000, - }) + }), ); }; const perPageOptions: any = [ @@ -961,16 +1090,8 @@ export default defineComponent({ { label: "All", value: 0 }, ]; const resultTotal = computed(function () { - if(!searchAcrossFolders.value || searchQuery.value == ""){ - return store.state.organizationData?.allAlertsListByFolderId[ - activeFolderId.value - ]?.length; - } - else{ - return filteredResults.value.length; - } - - }); + return filteredResults.value?.length; + }); const maxRecordToReturn = ref(100); const selectedPerPage = ref(20); const pagination: any = ref({ @@ -1001,12 +1122,11 @@ export default defineComponent({ toBeClonedID.value = row.alert_id; toBeCloneAlertName.value = row.name; toBeClonestreamName.value = ""; - toBeClonestreamType.value = "" + toBeClonestreamType.value = ""; showForm.value = true; toBeClonedAlert.value = await getAlertById(row.alert_id); }; const submitForm = async () => { - const dismiss = $q.notify({ spinner: true, message: "Please wait...", @@ -1045,14 +1165,14 @@ export default defineComponent({ toBeClonedAlert.value.folder_id = activeFolderId.value; try { //removed id from the alert payload - if(toBeClonedAlert.value?.id){ + if (toBeClonedAlert.value?.id) { delete toBeClonedAlert.value?.id; } alertsService .create_by_alert_id( store.state.selectedOrganization.identifier, toBeClonedAlert.value, - activeFolderId.value + activeFolderId.value, ) .then((res) => { dismiss(); @@ -1073,7 +1193,7 @@ export default defineComponent({ } }) .catch((e: any) => { - if(e.response?.status == 403){ + if (e.response?.status == 403) { showForm.value = false; isSubmitting.value = false; return; @@ -1137,8 +1257,8 @@ export default defineComponent({ } }; const refreshList = (folderId: string) => { - getAlertsFn(store, folderId); - hideForm(); + getAlertsFn(store, folderId); + hideForm(); }; const hideForm = () => { showAddAlertDialog.value = false; @@ -1151,48 +1271,48 @@ export default defineComponent({ }); }; const deleteAlertByAlertId = () => { - alertsService - .delete_by_alert_id( - store.state.selectedOrganization.identifier, - selectedDelete.value.alert_id, - activeFolderId.value - ) - .then((res: any) => { - if (res.data.code == 200) { - $q.notify({ - type: "positive", - message: res.data.message, - timeout: 2000, - }); - getAlertsFn(store, activeFolderId.value); - } else { - $q.notify({ - type: "negative", - message: res.data.message, - timeout: 2000, - }); - } - }) - .catch((err) => { - if(err.response?.status == 403){ - return; - } - $q.notify({ - type: "negative", - message: err?.data?.message || "Error while deleting alert.", - timeout: 2000, - }); + alertsService + .delete_by_alert_id( + store.state.selectedOrganization.identifier, + selectedDelete.value.alert_id, + activeFolderId.value, + ) + .then((res: any) => { + if (res.data.code == 200) { + $q.notify({ + type: "positive", + message: res.data.message, + timeout: 2000, }); - if (config.enableAnalytics == "true") { - segment.track("Button Click", { - button: "Delete Alert", - user_org: store.state.selectedOrganization.identifier, - user_id: store.state.userInfo.email, - alert_name: selectedDelete.value.name, - page: "Alerts", + getAlertsFn(store, activeFolderId.value); + } else { + $q.notify({ + type: "negative", + message: res.data.message, + timeout: 2000, }); } - }; + }) + .catch((err) => { + if (err.response?.status == 403) { + return; + } + $q.notify({ + type: "negative", + message: err?.data?.message || "Error while deleting alert.", + timeout: 2000, + }); + }); + if (config.enableAnalytics == "true") { + segment.track("Button Click", { + button: "Delete Alert", + user_org: store.state.selectedOrganization.identifier, + user_id: store.state.userInfo.email, + alert_name: selectedDelete.value.name, + page: "Alerts", + }); + } + }; const showDeleteDialogFn = (props: any) => { selectedDelete.value = props.row; confirmDelete.value = true; @@ -1208,7 +1328,7 @@ export default defineComponent({ update(() => { const value = val.toLowerCase(); filteredOptions = options.filter( - (column: any) => column.toLowerCase().indexOf(value) > -1 + (column: any) => column.toLowerCase().indexOf(value) > -1, ); }); return filteredOptions; @@ -1223,7 +1343,7 @@ export default defineComponent({ indexOptions.value = streams.value[toBeClonestreamType.value].map( (data: any) => { return data.name; - } + }, ); updateStreamName(toBeClonestreamName.value); @@ -1251,35 +1371,32 @@ export default defineComponent({ }; const toggleAlertState = (row: any) => { - alertStateLoadingMap.value[row.uuid] = true; - - alertsService - .toggle_state_by_alert_id( - store.state.selectedOrganization.identifier, - row.alert_id, - !row?.enabled, - activeFolderId.value - ) - .then((res: any) => { - const isEnabled = res.data.enabled; - alertsList.value.forEach((alert: any) => { - alert.uuid === row.uuid ? (alert.enabled = isEnabled) : null; - }); - if(searchAcrossFolders.value && searchQuery.value != ""){ - store.state.organizationData.allAlertsListByFolderId[row.folder_name.id].forEach((alert: any) => { - alert?.alert_id === row?.alert_id ? (alert.enabled = isEnabled) : null; - }); - } - $q.notify({ - type: "positive", - message: isEnabled ? "Alert Resumed Successfully" : "Alert Paused Successfully", - timeout: 2000, - }); - }) - .finally(() => { - alertStateLoadingMap.value[row.uuid] = false; - }); - }; + alertStateLoadingMap.value[row.uuid] = true; + + alertsService + .toggle_state_by_alert_id( + store.state.selectedOrganization.identifier, + row.alert_id, + !row?.enabled, + activeFolderId.value, + ) + .then((res: any) => { + const isEnabled = res.data.enabled; + filteredResults.value.forEach((alert: any) => { + alert.uuid === row.uuid ? (alert.enabled = isEnabled) : null; + }); + $q.notify({ + type: "positive", + message: isEnabled + ? "Alert Resumed Successfully" + : "Alert Paused Successfully", + timeout: 2000, + }); + }) + .finally(() => { + alertStateLoadingMap.value[row.uuid] = false; + }); + }; const routeTo = (name: string) => { router.push({ @@ -1292,13 +1409,11 @@ export default defineComponent({ }); }; - const refreshDestination = async () =>{ - + const refreshDestination = async () => { await getDestinations(); + }; - } - - const importAlert = () =>{ + const importAlert = () => { showImportAlertDialog.value = true; router.push({ name: "alertList", @@ -1308,29 +1423,28 @@ export default defineComponent({ folder: activeFolderId.value, }, }); - } + }; const exportAlert = async (row: any) => { // Find the alert based on uuid - const alertToBeExported = await getAlertById(row.alert_id) + const alertToBeExported = await getAlertById(row.alert_id); - if(alertToBeExported.hasOwnProperty('id')){ + if (alertToBeExported.hasOwnProperty("id")) { delete alertToBeExported.id; } // Ensure that the alert exists before proceeding if (alertToBeExported) { - // Convert the alert object to a JSON string const alertJson = JSON.stringify(alertToBeExported, null, 2); // Create a Blob from the JSON string - const blob = new Blob([alertJson], { type: 'application/json' }); + const blob = new Blob([alertJson], { type: "application/json" }); // Create an object URL for the Blob const url = URL.createObjectURL(blob); // Create an anchor element to trigger the download - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = url; // Set the filename of the download @@ -1343,70 +1457,78 @@ export default defineComponent({ URL.revokeObjectURL(url); } else { // Alert not found, handle error or show notification - console.error('Alert not found for UUID:', row.uuid); + console.error("Alert not found for UUID:", row.uuid); } -}; -const updateActiveFolderId = (newVal: any) => { + }; + const updateActiveFolderId = (newVal: any) => { searchQuery.value = ""; activeFolderId.value = newVal; - selected.value = []; - allSelected.value = false; + selectedAlerts.value = []; + allSelectedAlerts.value = false; filterQuery.value = ""; - alertsRows.value.forEach((alert: any) => { + searchAcrossFolders.value = false; + //here we are resetting the selected alerts + filteredResults.value?.forEach((alert: any) => { alert.selected = false; - }); - } + }); + + }; const editAlert = async (row: any) => { const selectedAlert = await getAlertById(row.alert_id); showAddUpdateFn({ row: selectedAlert }); - } + }; const moveAlertToAnotherFolder = (row: any) => { showMoveAlertDialog.value = true; selectedAlertToMove.value = [row.alert_id]; activeFolderToMove.value = activeFolderId.value; - } + }; - const updateAcrossFolders = async (activeFolderId: any, selectedFolderId: any) => { - await getAlertsFn(store, activeFolderId); - await getAlertsFn(store, selectedFolderId); + const updateAcrossFolders = async ( + activeFolderId: any, + selectedFolderId: any, + ) => { + await Promise.all([ + getAlertsFn(store, selectedFolderId, "", false), // Run first and also dont add the results to the filteredResults + ]).then(() => getAlertsFn(store, activeFolderId)); showMoveAlertDialog.value = false; selectedAlertToMove.value = []; activeFolderToMove.value = ""; - selected.value = []; - allSelected.value = false; - toggleAll(); - } + selectedAlerts.value = []; + allSelectedAlerts.value = false; + }; const getSelectedString = () => { - return selected.value.length === 0 + return selectedAlerts.value.length === 0 ? "" - : `${selected.value.length} record${ - selected.value.length > 1 ? "s" : "" + : `${selectedAlerts.value.length} record${ + selectedAlerts.value.length > 1 ? "s" : "" } selected`; }; const moveMultipleAlerts = () => { showMoveAlertDialog.value = true; - const selectedAlerts = selected.value.map((alert: any) => { - return alert.alert_id; + const selectedAlertsToMove = selectedAlerts.value.map((alert: any) => { + return alert.alert_id; }); - selectedAlertToMove.value = selectedAlerts; + selectedAlertToMove.value = selectedAlertsToMove; activeFolderToMove.value = activeFolderId.value; - } + }; - const dynamicQueryModel = computed ({ + const dynamicQueryModel = computed({ get() { - return searchAcrossFolders.value ? searchQuery.value : filterQuery.value; + return searchAcrossFolders.value + ? searchQuery.value + : filterQuery.value; }, set(value) { if (searchAcrossFolders.value) { searchQuery.value = value; - filterQuery.value = value; + // filterQuery.value = value; } else { filterQuery.value = value; - searchQuery.value = value; + // searchQuery.value = value; } }, }); @@ -1414,71 +1536,67 @@ const updateActiveFolderId = (newVal: any) => { const clearSearchHistory = () => { searchQuery.value = ""; filteredResults.value = []; - } + }; - const debouncedSearch = debounce (async (query) => { - if(!query) return; - const dismiss = $q.notify({ - spinner: true, - message: "Please wait while searching for dashboards...", - }); - dismiss(); - await getAlertsFn(store, activeFolderId.value, query); - filteredResults.value = alertsRows.value; + const debouncedSearch = debounce(async (query) => { + if (!query) return; + const dismiss = $q.notify({ + spinner: true, + message: "Please wait while searching for dashboards...", + }); + dismiss(); + await getAlertsFn(store, activeFolderId.value, query); }, 600); - const alertsList = computed(() => { - if(searchAcrossFolders.value && searchQuery.value != ""){ - return filteredResults.value; + watch(filterQuery, (newVal) => { + if(newVal == ""){ + filteredResults.value = store.state.organizationData.allAlertsListByFolderId[activeFolderId.value]; + } + if (newVal) { + filteredResults.value = store.state.organizationData.allAlertsListByFolderId[activeFolderId.value].filter((alert: any) => + alert.name.toLowerCase().includes(newVal.toLowerCase()), + ); + } + }); + watch(searchAcrossFolders, (newVal) => { + selectedAlerts.value = []; + allSelectedAlerts.value = false; + if(newVal){ + //here we are setting the searchQuery to null and then setting the filterQuery to the searchQuery + //this is done because we want to clear the searchQuery and then set the filterQuery to the searchQuery + searchQuery.value = null; + searchQuery.value = filterQuery.value; + filterQuery.value = null; + } + if(!newVal){ + //here we are setting the filterQuery to null and then setting the searchQuery to the filterQuery + //here we are also setting the filteredResults to the allAlertsListByFolderId as we are not searching across folders + //this is done because we want to clear the filterQuery and then set the searchQuery to the filterQuery + filteredResults.value = store.state.organizationData.allAlertsListByFolderId[activeFolderId.value]; + filterQuery.value = null; + filterQuery.value = searchQuery.value; + searchQuery.value = null; } - return store.state.organizationData.allAlertsListByFolderId[activeFolderId.value] ?? []; }); - const toggleAll = () => { - if (allSelected.value) { - // Select all rows - selected.value = [...alertsList.value]; - alertsRows.value.forEach((alert: any) => { - alert.selected = true; - }); - } else { - // Deselect all rows - selected.value = []; - alertsRows.value.forEach((alert: any) => { - alert.selected = false; - }); - } - }; - - // Add watcher to update allSelected when selected items change - watch(selected, (newVal) => { - allSelected.value = newVal.length === alertsList.value.length && alertsList.value.length > 0; - }); - - const handleRowSelection = (row: any, isSelected: boolean) => { - row.selected = isSelected; - if (isSelected) { - selected.value = [...selected.value, row]; - } else { - selected.value = selected.value.filter((item: any) => item.alert_id !== row.alert_id); - } - }; - - const copyToClipboard = (text: string,type: string) => { - navigator.clipboard.writeText(text).then(() => { - $q.notify({ + const copyToClipboard = (text: string, type: string) => { + navigator.clipboard + .writeText(text) + .then(() => { + $q.notify({ type: "positive", message: `${type} Copied Successfully!`, timeout: 5000, }); - }).catch(() => { + }) + .catch(() => { $q.notify({ type: "negative", message: "Error while copy content.", timeout: 5000, }); - }); - } + }); + }; const openMenu = (event: Event, row: any) => { event.stopPropagation(); @@ -1489,72 +1607,80 @@ const updateActiveFolderId = (newVal: any) => { const dismiss = $q.notify({ spinner: true, message: "Exporting alerts...", - timeout: 0 // Set timeout to 0 to keep it showing until dismissed + timeout: 0, // Set timeout to 0 to keep it showing until dismissed }); const alertToBeExported = []; - const selectedAlerts = selected.value.map((alert: any) => alert.alert_id); - + const selectedAlertsToExport = selectedAlerts.value.map( + (alert: any) => alert.alert_id, + ); + const alertsData = await Promise.all( - selectedAlerts.map(async (alertId: string) => { + selectedAlertsToExport.map(async (alertId: string) => { const alertData = await getAlertById(alertId); - if(alertData.hasOwnProperty('id')){ + if (alertData.hasOwnProperty("id")) { delete alertData.id; } return alertData; - }) + }), ); alertToBeExported.push(...alertsData); // Create and download the JSON file const jsonData = JSON.stringify(alertToBeExported, null, 2); - const blob = new Blob([jsonData], { type: 'application/json' }); + const blob = new Blob([jsonData], { type: "application/json" }); const url = URL.createObjectURL(blob); - const a = document.createElement('a'); + const a = document.createElement("a"); a.href = url; - a.download = `alerts-${new Date().toISOString().split('T')[0]}-${activeFolderId.value}.json`; + a.download = `alerts-${new Date().toISOString().split("T")[0]}-${activeFolderId.value}.json`; a.click(); - + URL.revokeObjectURL(url); - + dismiss(); $q.notify({ - type: 'positive', - message: `Successfully exported ${selectedAlerts.length} alert${selectedAlerts.length > 1 ? 's' : ''}`, - timeout: 2000 + type: "positive", + message: `Successfully exported ${selectedAlertsToExport.length} alert${selectedAlertsToExport.length > 1 ? "s" : ""}`, + timeout: 2000, }); - selected.value = []; - allSelected.value = false; - toggleAll(); - + selectedAlerts.value = []; + allSelectedAlerts.value = false; } catch (error) { - console.error('Error exporting alerts:', error); + console.error("Error exporting alerts:", error); $q.notify({ - type: 'negative', - message: 'Error exporting alerts. Please try again.', - timeout: 2000 + type: "negative", + message: "Error exporting alerts. Please try again.", + timeout: 2000, }); } - - }; const computedName = (name: string) => { - if(!name){ - return '--' - } - return name.length >30 ? name.substring(0, 30) + "..." : name; - }; + if (!name) { + return "--"; + } + return name.length > 30 ? name.substring(0, 30) + "..." : name; + }; const computedOwner = (owner: string) => { - if(!owner){ - return '--' - } - if (owner.length > 15) { - const firstTen = owner.substring(0, 7); - const lastFour = owner.substring(owner.length - 4); - return firstTen + "****" + lastFour; - } - return owner; - }; + if (!owner) { + return "--"; + } + if (owner.length > 15) { + const firstTen = owner.substring(0, 7); + const lastFour = owner.substring(owner.length - 4); + return firstTen + "****" + lastFour; + } + return owner; + }; + + const filterAlertsByTab = () => { + // getAlertsByFolderId(store, activeFolderId.value); + // filteredResults.value = filteredResults.value + // .filter((alert: any) => alert.type === "custom" && activeTab.value === "scheduled") + // .map((alert: any, index: number) => ({ + // ...alert, + // "#": index + 1, + // })); + }; return { @@ -1562,7 +1688,6 @@ const updateActiveFolderId = (newVal: any) => { qTable, store, router, - alerts, columns, formData, hideForm, @@ -1604,10 +1729,10 @@ const updateActiveFolderId = (newVal: any) => { var filtered = []; terms = terms.toLowerCase(); for (var i = 0; i < rows.length; i++) { - if (rows[i]["name"].toLowerCase().includes(terms)) { - filtered.push(rows[i]); + if (rows[i]["name"].toLowerCase().includes(terms)) { + filtered.push(rows[i]); + } } - } return filtered; }, getImageURL, @@ -1618,7 +1743,6 @@ const updateActiveFolderId = (newVal: any) => { splitterModel, outlinedPause, outlinedPlayArrow, - alertsRows, toggleAlertState, alertStateLoadingMap, templates, @@ -1638,7 +1762,7 @@ const updateActiveFolderId = (newVal: any) => { moveAlertToAnotherFolder, activeFolderToMove, updateAcrossFolders, - selected, + selectedAlerts, getSelectedString, moveMultipleAlerts, dynamicQueryModel, @@ -1646,21 +1770,19 @@ const updateActiveFolderId = (newVal: any) => { searchQuery, clearSearchHistory, filteredResults, - alertsList, expandedRow, triggerExpand, - allSelected, - toggleAll, - handleRowSelection, + allSelectedAlerts, copyToClipboard, openMenu, outlinedMoreVert, getAlertsFn, multipleExportAlert, computedName, - computedOwner + computedOwner, + tabs, + filterAlertsByTab, }; - }, }); @@ -1671,7 +1793,6 @@ const updateActiveFolderId = (newVal: any) => { width: 100%; justify-content: space-between; align-items: center; - } .move-btn { @@ -1692,8 +1813,8 @@ const updateActiveFolderId = (newVal: any) => { .alert-list-table { th:last-child, td:last-child { - //the fixed width as we have moved some actions to the menu so lot of space is occupied by actions section so remvoed that - padding: 10px !important; + //the fixed width as we have moved some actions to the menu so lot of space is occupied by actions section so remvoed that + padding: 10px !important; position: sticky; right: 0; z-index: 1; @@ -1748,7 +1869,7 @@ const updateActiveFolderId = (newVal: any) => { width: 400px; } .expand-content { - padding: 0 3rem; + padding: 0 3rem; max-height: 100vh; /* Set a fixed height for the container */ overflow: hidden; /* Hide overflow by default */ } @@ -1760,18 +1881,69 @@ const updateActiveFolderId = (newVal: any) => { border: 1px solid #ddd; /* Optional: border for visibility */ height: 100%; max-height: 200px; - /* Use the full height of the parent */ + /* Use the full height of the parent */ text-wrap: normal; background-color: #e8e8e8; color: black; } -.expanded-sql{ - border-left: #7A54A2 3px solid; +.expanded-sql { + border-left: #7a54a2 3px solid; +} +.alert-name-tooltip { + max-width: 400px; + white-space: normal; + word-wrap: break-word; + font-size: 12px; +} + + + diff --git a/web/src/components/alerts/ImportAlert.vue b/web/src/components/alerts/ImportAlert.vue index bee6ef13c7..9a1f261726 100644 --- a/web/src/components/alerts/ImportAlert.vue +++ b/web/src/components/alerts/ImportAlert.vue @@ -708,6 +708,7 @@ export default defineComponent({ name: "alertList", query: { org_identifier: store.state.selectedOrganization.identifier, + folder: selectedFolderId.value, }, }); } diff --git a/web/src/components/common/sidebar/SelectFolderDropDown.vue b/web/src/components/common/sidebar/SelectFolderDropDown.vue index 4744ab56c8..1df6f402fd 100644 --- a/web/src/components/common/sidebar/SelectFolderDropDown.vue +++ b/web/src/components/common/sidebar/SelectFolderDropDown.vue @@ -20,7 +20,7 @@ along with this program. If not, see . . import AddFolder from "./AddFolder.vue"; import { useRoute } from "vue-router"; import { computed } from "vue"; +import { getFoldersListByType } from "@/utils/commons"; export default defineComponent({ name: "SelectedFolderDropdown", @@ -109,7 +110,7 @@ import { computed } from "vue"; // use activeFolderId if available // else use router query if available // else use default - const activeFolderData = store.state.organizationData.foldersByType[props.type].find( + const activeFolderData = store.state.organizationData.foldersByType[props.type]?.find( (item: any) => item.folderId === (props.activeFolderId ?? route.query.folder ?? "default") ); @@ -136,9 +137,10 @@ import { computed } from "vue"; return props.style ? props.style : 'height: 42px'; }); - onActivated(() => { + onActivated(async () => { // refresh selected folder selectedFolder.value = getInitialFolderValue(); + await getFoldersListByType(store, props.type); }); watch( diff --git a/web/src/locales/languages/en.json b/web/src/locales/languages/en.json index cec9870695..123f7184fa 100644 --- a/web/src/locales/languages/en.json +++ b/web/src/locales/languages/en.json @@ -932,7 +932,7 @@ "add": "Add alert", "edit": "Edit", "clone": "Clone alert", - "search": "Search alert", + "search": "Search Alert", "move": "Move alert", "name": "Name", "save": "Save", @@ -943,7 +943,7 @@ "alertType": "Alert Type", "streamType": "Stream Type", "state": "State", - "realTime": "Real Time", + "realTime": "Real-Time", "scheduled": "Scheduled", "minutes": "Minutes", "hours": "Hours",