From 3c4adce9f62bbd6e007ae424d4f84da8f8229839 Mon Sep 17 00:00:00 2001 From: Alexey Oplachko Date: Wed, 23 Feb 2022 19:36:23 +0200 Subject: [PATCH 1/3] Log label filters --- .babelrc | 3 +- package-lock.json | 1 + package.json | 1 + src/actions/LoadLabels.js | 8 +- src/actions/index.js | 1 + src/actions/loadLabelValues.js | 15 ++- src/actions/setLabelValues.js | 5 +- src/actions/setLabels.js | 4 +- .../LabelBrowser/helpers/querybuilder.js | 18 +++- src/components/LogView.js | 40 +++++++- src/components/MainView.js | 1 + src/components/UpdateStateFromQueryParams.js | 93 ++++++++++++++++++- src/scss/modules/log-view.scss | 4 +- 13 files changed, 167 insertions(+), 27 deletions(-) diff --git a/.babelrc b/.babelrc index e708c7a2..e6ff02c9 100644 --- a/.babelrc +++ b/.babelrc @@ -2,7 +2,8 @@ "plugins": [ "transform-class-properties", "react-hot-loader/babel", - "syntax-dynamic-import" + "syntax-dynamic-import", + "@babel/plugin-transform-runtime" ], "presets": [ diff --git a/package-lock.json b/package-lock.json index c7420c95..4191b8b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "@babel/core": "^7.16.12", "@babel/plugin-proposal-class-properties": "^7.16.7", "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-transform-runtime": "^7.17.0", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "babel-loader": "^8.2.3", diff --git a/package.json b/package.json index db9c8f69..59235ad8 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@babel/core": "^7.16.12", "@babel/plugin-proposal-class-properties": "^7.16.7", "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-transform-runtime": "^7.17.0", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "babel-loader": "^8.2.3", diff --git a/src/actions/LoadLabels.js b/src/actions/LoadLabels.js index 0df074e6..aa975a75 100644 --- a/src/actions/LoadLabels.js +++ b/src/actions/LoadLabels.js @@ -1,5 +1,5 @@ import axios from "axios"; -import setLabels from "./setLabels"; +import { setLabels } from "./setLabels"; import setLoading from "./setLoading"; import { setApiError } from "./setApiError"; import { errorHandler } from "./errorHandler"; @@ -21,11 +21,11 @@ export default function loadLabels(apiUrl) { mode: "cors", }; - return function (dispatch) { + return async (dispatch) => { dispatch(setLoading(true)) - axios.get(`${url.trim()}/loki/api/v1/labels`, options) + await axios.get(`${url.trim()}/loki/api/v1/labels`, options) ?.then((response) => { if(response){ dispatch(setLoading(false)) @@ -40,7 +40,7 @@ export default function loadLabels(apiUrl) { hidden: false, facets: 0, })); - + console.log(labels) dispatch(setLabels(labels || [])); dispatch(setApiError('')) diff --git a/src/actions/index.js b/src/actions/index.js index 1aacdb72..9fe5c0eb 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -8,3 +8,4 @@ export * from "./setTimeRangeLabel" export * from "./setApiUrl"; export * from "./setQuery"; export * from "./setIsSubmit"; +export * from "./setLabels" \ No newline at end of file diff --git a/src/actions/loadLabelValues.js b/src/actions/loadLabelValues.js index 9e914398..50c2648b 100644 --- a/src/actions/loadLabelValues.js +++ b/src/actions/loadLabelValues.js @@ -1,14 +1,14 @@ import axios from "axios"; import { errorHandler } from "./errorHandler"; import { setApiError } from "./setApiError"; -import setLabels from "./setLabels"; +import { setLabels } from "./setLabels"; import setLabelValues from "./setLabelValues"; import setLoading from "./setLoading"; -export default function loadLaebelValues(label, labelList, apiUrl) { - if (label?.length <= 0 && label.lsList.length <= 0) { - return; +export default function loadLabelValues(label, labelList, apiUrl) { + if (!label || (label?.length <= 0 && label.lsList.length <= 0)) { + return () => {}; }; const url = apiUrl; @@ -28,11 +28,10 @@ export default function loadLaebelValues(label, labelList, apiUrl) { }; - return function (dispatch) { - + return async (dispatch) => { dispatch(setLoading(true)) - axios.get(`${url}/loki/api/v1/label/${label.name}/values`, options) + await axios.get(`${url}/loki/api/v1/label/${label.name}/values`, options) ?.then(response => { if (response?.data?.data) { const values = response?.data?.data?.map?.((value) => ({ @@ -62,7 +61,7 @@ export default function loadLaebelValues(label, labelList, apiUrl) { const { message } = errorHandler(url, error) dispatch(setApiError(message)) - console.log(error) + console.err(error) }) } diff --git a/src/actions/setLabelValues.js b/src/actions/setLabelValues.js index c80745b4..4e82dd8d 100644 --- a/src/actions/setLabelValues.js +++ b/src/actions/setLabelValues.js @@ -1,6 +1,7 @@ -export default (labelValues) => (dispatch) => { +const setLabelValues = (labelValues) => (dispatch) => { dispatch({ type: 'SET_LABEL_VALUES', labelValues }); -} \ No newline at end of file +} +export default setLabelValues; diff --git a/src/actions/setLabels.js b/src/actions/setLabels.js index f649c03f..0c8a8437 100644 --- a/src/actions/setLabels.js +++ b/src/actions/setLabels.js @@ -1,6 +1,6 @@ -export default (labels) => (dispatch) => { +export const setLabels = (labels) => (dispatch) => { dispatch({ type: 'SET_LABELS', labels: labels }); -} \ No newline at end of file +}; diff --git a/src/components/LabelBrowser/helpers/querybuilder.js b/src/components/LabelBrowser/helpers/querybuilder.js index eee88732..81cf2d77 100644 --- a/src/components/LabelBrowser/helpers/querybuilder.js +++ b/src/components/LabelBrowser/helpers/querybuilder.js @@ -1,11 +1,17 @@ +import { setQuery } from "../../../actions"; +import store from "../../../store/store"; export function queryBuilder(labels) { const selectedLabels = []; for (const label of labels) { if (label.selected && label.values && label.values.length > 0) { const selectedValues = label.values - .filter((value) => value.selected) + .filter((value) => value.selected && !value.inverted) .map((value) => value.name); + const invertedSelectedValues = label.values + .filter((value) => value.selected && value.inverted) + .map((value) => value.name); + if (selectedValues.length > 1) { selectedLabels.push( `${label.name}=~"${selectedValues.join("|")}"` @@ -13,7 +19,17 @@ export function queryBuilder(labels) { } else if (selectedValues.length === 1) { selectedLabels.push(`${label.name}="${selectedValues[0]}"`); } + invertedSelectedValues.forEach(value => { + selectedLabels.push(`${label.name}!="${value}"`) + }); + } } return ["{", selectedLabels.join(","), "}"].join(""); } +export function queryBuilderWithLabels() { + const labels = store.getState().labels; + console.log(labels) + const query = queryBuilder(labels) + store.dispatch(setQuery(query)); +} \ No newline at end of file diff --git a/src/components/LogView.js b/src/components/LogView.js index aa0434cc..e3a9f8cf 100644 --- a/src/components/LogView.js +++ b/src/components/LogView.js @@ -2,7 +2,12 @@ import React, { Component } from "react"; import { connect } from "react-redux"; import { CircularProgress } from "@mui/material"; import * as moment from "moment"; - +import store from "../store/store" +import { IconButton } from "@mui/material"; +import { ZoomIn, ZoomOut } from "@mui/icons-material/"; +import { setLabels } from "../actions"; +import { queryBuilderWithLabels } from "./LabelBrowser/helpers/querybuilder"; +import loadLabelValues from '../actions/loadLabelValues'; const TAGS_LEVEL = { critical: ['emerg', 'fatal', 'alert', 'crit', 'critical'], error: ['err', 'eror', 'error', 'warning'], @@ -12,11 +17,42 @@ const TAGS_LEVEL = { trace: ['trace'] } export const ValueTags = (props) => { - + const addLabel = async (e, key, value, isInverted = false) => { + e.preventDefault(); + e.stopPropagation(); + const {labels, apiUrl} = store.getState(); + const label = labels.find(label => label.name === key); + if (label) { + const labelValue = label.values.find(tag => tag.name === value); + if (labelValue) { + labelValue.selected = !labelValue.selected || (labelValue.inverted !== isInverted); + labelValue.inverted = !labelValue.inverted && isInverted; + label.selected = label.values.some(value => value.selected); + store.dispatch(setLabels(labels)); + queryBuilderWithLabels() + } else { + await store.dispatch(loadLabelValues(label,labels,apiUrl)); + const updatedLabels = store.getState().labels; + const updatedLabel = updatedLabels.find(label => label.name === key); + const labelValue = updatedLabel.values.find(tag => tag.name === value); + labelValue.selected = !labelValue.selected || (labelValue.inverted !== isInverted); + labelValue.inverted = !labelValue.inverted && isInverted; + updatedLabel.selected = updatedLabel.values.some(value => value.selected); + store.dispatch(setLabels(updatedLabels)); + queryBuilderWithLabels() + } + } + } const getTags = (tags) => { return Object.entries(tags).map( ([key, value], k) => (
+ addLabel(e, key, value)} aria-label="Filter for value" size="small" color="primary"> + + + addLabel(e, key, value, true)} aria-label="Filter out value" size="small" color="primary"> + + {key} {value}
diff --git a/src/components/MainView.js b/src/components/MainView.js index abece764..72b9ffa1 100644 --- a/src/components/MainView.js +++ b/src/components/MainView.js @@ -4,6 +4,7 @@ import StatusBar from "./StatusBar/StatusBar"; import LabelBrowser from "./LabelBrowser/LabelBrowser" import { UpdateStateFromQueryParams } from "./UpdateStateFromQueryParams"; export default function MainView() { + UpdateStateFromQueryParams() return (
diff --git a/src/components/UpdateStateFromQueryParams.js b/src/components/UpdateStateFromQueryParams.js index 0ffe0457..7ab84de6 100644 --- a/src/components/UpdateStateFromQueryParams.js +++ b/src/components/UpdateStateFromQueryParams.js @@ -1,11 +1,14 @@ -import * as moment from 'moment' -import { useDispatch, useSelector } from 'react-redux'; +import * as moment from 'moment'; import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; -import { setUrlQueryParams } from '../actions/setUrlQueryParams'; +import { setApiUrl, setIsSubmit, setLabels, setQuery, setQueryLimit, setQueryStep, setStartTime, setStopTime } from '../actions'; +import loadLabels from '../actions/LoadLabels'; +import loadLabelValues from '../actions/loadLabelValues'; import { setUrlLocation } from '../actions/setUrlLocation'; -import { setQuery, setStopTime, setStartTime, setQueryLimit, setQueryStep, setApiUrl, setIsSubmit} from '../actions'; +import { setUrlQueryParams } from '../actions/setUrlQueryParams'; import { environment } from '../environment/env.dev'; +import store from '../store/store'; export function UpdateStateFromQueryParams() { const { hash } = useLocation() @@ -79,6 +82,7 @@ export function UpdateStateFromQueryParams() { dispatch(STORE_ACTIONS[param](startParams[param])) } else if (QUERY_VALUE === param && startParams[param] !== '') { const parsedQuery = decodeURIComponent(startParams[param]) + decodeQuery(parsedQuery, apiUrl) dispatch(STORE_ACTIONS[param](parsedQuery)) } else if (TIME_VALUES.includes(param) && startParams[param] !== '') { const croppedTime = ((startParams[param])) / 1000000 @@ -87,7 +91,9 @@ export function UpdateStateFromQueryParams() { } else if (BOOLEAN_VALUES.includes(param) && typeof param === 'boolean') { dispatch(STORE_ACTIONS[param](startParams[param])) } + if (QUERY_VALUE === param) { + } }) } @@ -109,7 +115,8 @@ export function UpdateStateFromQueryParams() { urlFromHash.set(param, time_value.toString()) } else if (QUERY_VALUE === param) { - const parsed = encodeURIComponent(STORE_KEYS[param]).toString() + const parsed = encodeURIComponent(STORE_KEYS[param]).toString(); + decodeQuery(parsed, apiUrl) urlFromHash.set(param, parsed.toString()) } else if(BOOLEAN_VALUES === param && typeof param === 'boolean') { urlFromHash.set(param,param.toString()) @@ -168,3 +175,79 @@ export function UpdateStateFromQueryParams() { }, [STORE_KEYS]) } +async function decodeQuery(query, apiUrl) { + await store.dispatch(loadLabels(apiUrl)) + const queryArr = query.replaceAll(/[{}]/g,'').split(','); + const labelsFromQuery = []; + queryArr.forEach(label => { + const regexQuery = label.match(/([^{}=,~!]+)/gm); + if (!regexQuery) { + return; + } + if (label.includes("!=")) { + const labelObj = { + name: regexQuery[0], + values: [] + } + const valueObj = { + name: regexQuery[1].replaceAll('"', ''), + selected: true, + inverted: true + } + labelObj.values.push(valueObj); + labelsFromQuery.push(labelObj); + } else if(label.includes("=~")) { + const values = regexQuery[1].split('|') + console.log(values) + const labelObj = { + name: regexQuery[0], + values: [] + } + values.forEach(value => { + const valueObj = { + name: value.replaceAll('"', ''), + selected: true, + inverted: false + } + labelObj.values.push(valueObj); + + }); + labelsFromQuery.push(labelObj); + } else { + const labelObj = { + name: regexQuery[0], + values: [] + } + const valueObj = { + name: regexQuery[1].replaceAll('"', ''), + selected: true, + inverted: false + } + labelObj.values.push(valueObj); + labelsFromQuery.push(labelObj); + } + }); + const newLabels = store.getState().labels; + labelsFromQuery.forEach(async (label) => { + + const cleanLabel = newLabels?.find(item => item?.name === label?.name); + if (!cleanLabel) { + return + } + + await store.dispatch(loadLabelValues(cleanLabel,newLabels,apiUrl)); + const labelsWithValues = store.getState().labels; + const labelWithValues = labelsWithValues.find(item => item?.name === label?.name); + let values = labelWithValues.values; + values = label.values.concat(values); + values = values + .sort((a, b) => a.name.localeCompare(b.name)) + .filter((i, k, a) => { + console.log(a[k-1]) + return i.name !== a[k - 1]?.name}) + .filter((i) => !!i); + labelWithValues.values = values; + store.dispatch(setLabels(labelsWithValues)) + }) + +} \ No newline at end of file diff --git a/src/scss/modules/log-view.scss b/src/scss/modules/log-view.scss index 42891fe8..abd5bad1 100644 --- a/src/scss/modules/log-view.scss +++ b/src/scss/modules/log-view.scss @@ -84,10 +84,10 @@ justify-content: space-between; span { - padding: 4px; + padding: 0px 4px; display: flex; flex: 1; - + line-height: 34px; color: #cccccd; } &:hover { From 372351971c1b9d8b330e06671f371187a99be58c Mon Sep 17 00:00:00 2001 From: Alexey Oplachko Date: Tue, 1 Mar 2022 19:10:42 +0200 Subject: [PATCH 2/3] Fix import --- src/actions/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/actions/index.js b/src/actions/index.js index bbc9d601..2b3e08d9 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -9,3 +9,4 @@ export * from "./setApiUrl"; export * from "./setQuery"; export * from "./setIsSubmit"; export * from "./setMatrixData"; +export * from "./setLabels"; \ No newline at end of file From 60718163be01aec395b2721cb5da720a367d0f66 Mon Sep 17 00:00:00 2001 From: Alexey Oplachko Date: Tue, 1 Mar 2022 19:50:29 +0200 Subject: [PATCH 3/3] Added auto-update search + fixed minor bug with label selection --- src/actions/LoadLabels.js | 1 - src/actions/loadLabelValues.js | 1 + src/components/LabelBrowser/ValuesList.js | 1 + src/components/LogView.js | 16 ++++++++-------- src/components/UpdateStateFromQueryParams.js | 1 + 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/actions/LoadLabels.js b/src/actions/LoadLabels.js index cc55e097..ae6e295e 100644 --- a/src/actions/LoadLabels.js +++ b/src/actions/LoadLabels.js @@ -39,7 +39,6 @@ export default function loadLabels(apiUrl) { hidden: false, facets: 0, })); - console.log(labels) dispatch(setLabels(labels || [])); dispatch(setApiError('')) diff --git a/src/actions/loadLabelValues.js b/src/actions/loadLabelValues.js index 50c2648b..9b816b67 100644 --- a/src/actions/loadLabelValues.js +++ b/src/actions/loadLabelValues.js @@ -39,6 +39,7 @@ export default function loadLabelValues(label, labelList, apiUrl) { selected: false, loading: false, hidden: false, + inverted: false })); const lsList = [...labelList]; diff --git a/src/components/LabelBrowser/ValuesList.js b/src/components/LabelBrowser/ValuesList.js index fb1b26d1..6e568144 100644 --- a/src/components/LabelBrowser/ValuesList.js +++ b/src/components/LabelBrowser/ValuesList.js @@ -94,6 +94,7 @@ export const ValuesList = (props) => { const onLabelValueClick = (e, value) => { e.preventDefault() value.selected = !value.selected; + value.inverted = false; onLabelValueChange(); }; diff --git a/src/components/LogView.js b/src/components/LogView.js index d8f93b88..9cca7ae0 100644 --- a/src/components/LogView.js +++ b/src/components/LogView.js @@ -1,15 +1,15 @@ +import { ZoomIn, ZoomOut } from "@mui/icons-material/"; +import { CircularProgress, IconButton } from "@mui/material"; +import * as moment from "moment"; import React, { Component } from "react"; import { connect } from "react-redux"; -import { CircularProgress } from "@mui/material"; -import * as moment from "moment"; -import store from "../store/store" -import { IconButton } from "@mui/material"; -import { ZoomIn, ZoomOut } from "@mui/icons-material/"; import { setLabels } from "../actions"; -import { queryBuilderWithLabels } from "./LabelBrowser/helpers/querybuilder"; import loadLabelValues from '../actions/loadLabelValues'; import ClokiChart from "../plugins/charts"; +import store from "../store/store"; +import { queryBuilderWithLabels } from "./LabelBrowser/helpers/querybuilder"; +import loadLogs from "../actions/loadLogs" const TAGS_LEVEL = { critical: ['emerg', 'fatal', 'alert', 'crit', 'critical'], error: ['err', 'eror', 'error', 'warning'], @@ -31,7 +31,6 @@ export const ValueTags = (props) => { labelValue.inverted = !labelValue.inverted && isInverted; label.selected = label.values.some(value => value.selected); store.dispatch(setLabels(labels)); - queryBuilderWithLabels() } else { await store.dispatch(loadLabelValues(label,labels,apiUrl)); const updatedLabels = store.getState().labels; @@ -41,8 +40,9 @@ export const ValueTags = (props) => { labelValue.inverted = !labelValue.inverted && isInverted; updatedLabel.selected = updatedLabel.values.some(value => value.selected); store.dispatch(setLabels(updatedLabels)); - queryBuilderWithLabels() } + queryBuilderWithLabels() + store.dispatch(loadLogs()) } } const getTags = (tags) => { diff --git a/src/components/UpdateStateFromQueryParams.js b/src/components/UpdateStateFromQueryParams.js index 7ab84de6..8ad705b9 100644 --- a/src/components/UpdateStateFromQueryParams.js +++ b/src/components/UpdateStateFromQueryParams.js @@ -247,6 +247,7 @@ async function decodeQuery(query, apiUrl) { return i.name !== a[k - 1]?.name}) .filter((i) => !!i); labelWithValues.values = values; + labelWithValues.selected = true; store.dispatch(setLabels(labelsWithValues)) })