From 92ba46b2f55b2a8cad73eb3af2137b5695ced89a Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 28 Sep 2023 09:15:12 +0530 Subject: [PATCH 01/34] fix: copy to clipboard without quotes (#3605) --- frontend/src/container/LogDetailedView/TableView.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/container/LogDetailedView/TableView.tsx b/frontend/src/container/LogDetailedView/TableView.tsx index 28910afbde..92840bde45 100644 --- a/frontend/src/container/LogDetailedView/TableView.tsx +++ b/frontend/src/container/LogDetailedView/TableView.tsx @@ -164,6 +164,8 @@ function TableView({ width: 70, ellipsis: false, render: (field, record): JSX.Element => { + const textToCopy = field.slice(1, -1); + if (record.field === 'body') { const parsedBody = recursiveParseJSON(field); if (!isEmpty(parsedBody)) { @@ -174,7 +176,7 @@ function TableView({ } return ( - + {field} ); From 05ea814c615116d074b82d6df2654234bda5ffc1 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 28 Sep 2023 13:32:55 +0530 Subject: [PATCH 02/34] refactor: wrap tooltip text and remain file for tooltips (#3647) --- .../TextToolTip/TextToolTip.style.scss | 3 + .../components/TextToolTip/TextToolTip.tsx | 89 +++++++++++++++++++ .../TextToolTip/{styles.ts => constant.ts} | 0 frontend/src/components/TextToolTip/index.tsx | 86 +----------------- 4 files changed, 93 insertions(+), 85 deletions(-) create mode 100644 frontend/src/components/TextToolTip/TextToolTip.style.scss create mode 100644 frontend/src/components/TextToolTip/TextToolTip.tsx rename frontend/src/components/TextToolTip/{styles.ts => constant.ts} (100%) diff --git a/frontend/src/components/TextToolTip/TextToolTip.style.scss b/frontend/src/components/TextToolTip/TextToolTip.style.scss new file mode 100644 index 0000000000..192d98264c --- /dev/null +++ b/frontend/src/components/TextToolTip/TextToolTip.style.scss @@ -0,0 +1,3 @@ +.overlay--text-wrap { + white-space: pre-wrap; +} \ No newline at end of file diff --git a/frontend/src/components/TextToolTip/TextToolTip.tsx b/frontend/src/components/TextToolTip/TextToolTip.tsx new file mode 100644 index 0000000000..6c8fad783e --- /dev/null +++ b/frontend/src/components/TextToolTip/TextToolTip.tsx @@ -0,0 +1,89 @@ +import './TextToolTip.style.scss'; + +import { blue, grey } from '@ant-design/colors'; +import { + QuestionCircleFilled, + QuestionCircleOutlined, +} from '@ant-design/icons'; +import { Tooltip } from 'antd'; +import { themeColors } from 'constants/theme'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import { useMemo } from 'react'; +import { popupContainer } from 'utils/selectPopupContainer'; + +import { style } from './constant'; + +function TextToolTip({ + text, + url, + useFilledIcon = true, + urlText, +}: TextToolTipProps): JSX.Element { + const isDarkMode = useIsDarkMode(); + + const onClickHandler = ( + event: React.MouseEvent, + ): void => { + event.stopPropagation(); + }; + + const overlay = useMemo( + () => ( +
+ {`${text} `} + {url && ( + + {urlText || 'here'} + + )} +
+ ), + [text, url, urlText], + ); + + const iconStyle = useMemo( + () => ({ + ...style, + color: isDarkMode ? themeColors.whiteCream : grey[0], + }), + [isDarkMode], + ); + + const iconOutlinedStyle = useMemo( + () => ({ + ...style, + color: isDarkMode ? themeColors.navyBlue : blue[6], + }), + [isDarkMode], + ); + + return ( + + {useFilledIcon ? ( + + ) : ( + + )} + + ); +} + +TextToolTip.defaultProps = { + url: '', + urlText: '', + useFilledIcon: true, +}; +interface TextToolTipProps { + url?: string; + text: string; + useFilledIcon?: boolean; + urlText?: string; +} + +export default TextToolTip; diff --git a/frontend/src/components/TextToolTip/styles.ts b/frontend/src/components/TextToolTip/constant.ts similarity index 100% rename from frontend/src/components/TextToolTip/styles.ts rename to frontend/src/components/TextToolTip/constant.ts diff --git a/frontend/src/components/TextToolTip/index.tsx b/frontend/src/components/TextToolTip/index.tsx index 72f631a872..c40a841fd0 100644 --- a/frontend/src/components/TextToolTip/index.tsx +++ b/frontend/src/components/TextToolTip/index.tsx @@ -1,87 +1,3 @@ -import { blue, grey } from '@ant-design/colors'; -import { - QuestionCircleFilled, - QuestionCircleOutlined, -} from '@ant-design/icons'; -import { Tooltip } from 'antd'; -import { themeColors } from 'constants/theme'; -import { useIsDarkMode } from 'hooks/useDarkMode'; -import { useMemo } from 'react'; -import { popupContainer } from 'utils/selectPopupContainer'; - -import { style } from './styles'; - -function TextToolTip({ - text, - url, - useFilledIcon = true, - urlText, -}: TextToolTipProps): JSX.Element { - const isDarkMode = useIsDarkMode(); - - const onClickHandler = ( - event: React.MouseEvent, - ): void => { - event.stopPropagation(); - }; - - const overlay = useMemo( - () => ( -
- {`${text} `} - {url && ( - - {urlText || 'here'} - - )} -
- ), - [text, url, urlText], - ); - - const iconStyle = useMemo( - () => ({ - ...style, - color: isDarkMode ? themeColors.whiteCream : grey[0], - }), - [isDarkMode], - ); - - const iconOutlinedStyle = useMemo( - () => ({ - ...style, - color: isDarkMode ? themeColors.navyBlue : blue[6], - }), - [isDarkMode], - ); - - return ( - - {useFilledIcon ? ( - - ) : ( - - )} - - ); -} - -TextToolTip.defaultProps = { - url: '', - urlText: '', - useFilledIcon: true, -}; -interface TextToolTipProps { - url?: string; - text: string; - useFilledIcon?: boolean; - urlText?: string; -} +import TextToolTip from './TextToolTip'; export default TextToolTip; From 3d0fbd0065e6ae775fa8ecf380fb69393aff5145 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Fri, 29 Sep 2023 17:10:14 +0530 Subject: [PATCH 03/34] perf(frontend): :hammer: improve frontend build time (#3653) Signed-off-by: Prashant Shahi --- Makefile | 16 ++++++++++++++-- frontend/.dockerignore | 1 - frontend/Dockerfile | 31 +++++-------------------------- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index 7d976341c1..d4df55c063 100644 --- a/Makefile +++ b/Makefile @@ -37,10 +37,22 @@ LD_FLAGS=-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildV DEV_LD_FLAGS=-X ${licenseSignozIo}=${DEV_LICENSE_SIGNOZ_IO} all: build-push-frontend build-push-query-service + +# Steps to build static files of frontend +build-frontend-static: + @echo "------------------" + @echo "--> Building frontend static files" + @echo "------------------" + @cd $(FRONTEND_DIRECTORY) && \ + rm -rf build && \ + CI=1 yarn install && \ + yarn build && \ + ls -l build + # Steps to build and push docker image of frontend .PHONY: build-frontend-amd64 build-push-frontend # Step to build docker image of frontend in amd64 (used in build pipeline) -build-frontend-amd64: +build-frontend-amd64: build-frontend-static @echo "------------------" @echo "--> Building frontend docker image for amd64" @echo "------------------" @@ -49,7 +61,7 @@ build-frontend-amd64: --build-arg TARGETPLATFORM="linux/amd64" . # Step to build and push docker image of frontend(used in push pipeline) -build-push-frontend: +build-push-frontend: build-frontend-static @echo "------------------" @echo "--> Building and pushing frontend docker image" @echo "------------------" diff --git a/frontend/.dockerignore b/frontend/.dockerignore index a106e6a11e..840adcb19f 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,4 +1,3 @@ node_modules .vscode -build .git diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 3209052799..ddbf9edc19 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,38 +1,17 @@ -# Builder stage -FROM node:16.15.0 as builder +FROM nginx:1.25.2-alpine # Add Maintainer Info LABEL maintainer="signoz" -ARG TARGETOS=linux -ARG TARGETARCH - +# Set working directory WORKDIR /frontend -# Copy the package.json and .yarnrc files prior to install dependencies -COPY package.json ./ -# Copy lock file -COPY yarn.lock ./ -COPY .yarnrc ./ - -# Install the dependencies and make the folder -RUN CI=1 yarn install - -COPY . . - -# Build the project and copy the files -RUN yarn build - - -FROM nginx:1.25.2-alpine - -COPY conf/default.conf /etc/nginx/conf.d/default.conf - # Remove default nginx index page RUN rm -rf /usr/share/nginx/html/* -# Copy from the stahg 1 -COPY --from=builder /frontend/build /usr/share/nginx/html +# Copy custom nginx config and static files +COPY conf/default.conf /etc/nginx/conf.d/default.conf +COPY build /usr/share/nginx/html EXPOSE 3301 From 9f751688cc039af92d721ded426f2b28ce004010 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Fri, 29 Sep 2023 18:20:40 +0530 Subject: [PATCH 04/34] fix: limit issue fixed when using contains (#3649) --- pkg/query-service/app/logs/v3/query_builder.go | 2 +- pkg/query-service/app/logs/v3/query_builder_test.go | 4 ++-- pkg/query-service/app/querier/helper.go | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/query-service/app/logs/v3/query_builder.go b/pkg/query-service/app/logs/v3/query_builder.go index 5deb2c26f3..ff989fa61d 100644 --- a/pkg/query-service/app/logs/v3/query_builder.go +++ b/pkg/query-service/app/logs/v3/query_builder.go @@ -280,7 +280,7 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build } if graphLimitQtype == constants.SecondQueryGraphLimit { - filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", getSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)" + filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", getSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "#LIMIT_PLACEHOLDER)" } aggregationKey := "" diff --git a/pkg/query-service/app/logs/v3/query_builder_test.go b/pkg/query-service/app/logs/v3/query_builder_test.go index 79f103a0a3..4c23b5f6a9 100644 --- a/pkg/query-service/app/logs/v3/query_builder_test.go +++ b/pkg/query-service/app/logs/v3/query_builder_test.go @@ -1193,7 +1193,7 @@ var testPrepLogsQueryData = []struct { Limit: 2, }, TableName: "logs", - ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360000000000 AND timestamp <= 1680066420000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 AND has(attributes_string_key, 'name') AND (method) GLOBAL IN (%s) group by method,ts order by value DESC", + ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360000000000 AND timestamp <= 1680066420000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 AND has(attributes_string_key, 'name') AND (method) GLOBAL IN (#LIMIT_PLACEHOLDER) group by method,ts order by value DESC", Options: Options{GraphLimitQtype: constants.SecondQueryGraphLimit}, }, { @@ -1216,7 +1216,7 @@ var testPrepLogsQueryData = []struct { Limit: 2, }, TableName: "logs", - ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360000000000 AND timestamp <= 1680066420000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 AND has(attributes_string_key, 'name') AND (method) GLOBAL IN (%s) group by method,ts order by method ASC", + ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360000000000 AND timestamp <= 1680066420000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 AND has(attributes_string_key, 'name') AND (method) GLOBAL IN (#LIMIT_PLACEHOLDER) group by method,ts order by method ASC", Options: Options{GraphLimitQtype: constants.SecondQueryGraphLimit}, }, // Live tail diff --git a/pkg/query-service/app/querier/helper.go b/pkg/query-service/app/querier/helper.go index 4d809a18f3..82bf2cc464 100644 --- a/pkg/query-service/app/querier/helper.go +++ b/pkg/query-service/app/querier/helper.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "sync" "time" @@ -64,7 +65,7 @@ func (q *querier) runBuilderQuery( ch <- channelResult{Err: err, Name: queryName, Query: placeholderQuery, Series: nil} return } - query = fmt.Sprintf(placeholderQuery, limitQuery) + query = strings.Replace(placeholderQuery, "#LIMIT_PLACEHOLDER", limitQuery, 1) } else { query, err = logsV3.PrepareLogsQuery( params.Start, From 81b10d126a6d51b3381df1f187b819225f2b3473 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Mon, 2 Oct 2023 05:04:04 +0000 Subject: [PATCH 05/34] feat: has and nhas filters is now enabled (#3567) --- .../src/container/LogDetailedView/config.ts | 13 ++++ .../container/LogDetailedView/util.test.ts | 4 +- .../src/container/LogDetailedView/utils.tsx | 77 +++++++++++-------- .../filters/QueryBuilderSearch/index.tsx | 5 +- 4 files changed, 59 insertions(+), 40 deletions(-) create mode 100644 frontend/src/container/LogDetailedView/config.ts diff --git a/frontend/src/container/LogDetailedView/config.ts b/frontend/src/container/LogDetailedView/config.ts new file mode 100644 index 0000000000..cd34023699 --- /dev/null +++ b/frontend/src/container/LogDetailedView/config.ts @@ -0,0 +1,13 @@ +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; + +export const typeToArrayTypeMapper: { [key in DataTypes]: DataTypes } = { + [DataTypes.String]: DataTypes.ArrayString, + [DataTypes.Float64]: DataTypes.ArrayFloat64, + [DataTypes.Int64]: DataTypes.ArrayInt64, + [DataTypes.bool]: DataTypes.ArrayBool, + [DataTypes.EMPTY]: DataTypes.EMPTY, + [DataTypes.ArrayFloat64]: DataTypes.ArrayFloat64, + [DataTypes.ArrayInt64]: DataTypes.ArrayInt64, + [DataTypes.ArrayString]: DataTypes.ArrayString, + [DataTypes.ArrayBool]: DataTypes.ArrayBool, +}; diff --git a/frontend/src/container/LogDetailedView/util.test.ts b/frontend/src/container/LogDetailedView/util.test.ts index 4f080e23c1..d5918f2bca 100644 --- a/frontend/src/container/LogDetailedView/util.test.ts +++ b/frontend/src/container/LogDetailedView/util.test.ts @@ -176,8 +176,8 @@ describe('Get Data Types utils', () => { }); // Edge cases - it('should return Int64 for empty array input', () => { - expect(getDataTypes([])).toBe(DataTypes.Int64); + it('should return Empty for empty array input', () => { + expect(getDataTypes([])).toBe(DataTypes.EMPTY); }); it('should handle mixed array (return based on first element)', () => { diff --git a/frontend/src/container/LogDetailedView/utils.tsx b/frontend/src/container/LogDetailedView/utils.tsx index 02890e7dc9..5530c970d3 100644 --- a/frontend/src/container/LogDetailedView/utils.tsx +++ b/frontend/src/container/LogDetailedView/utils.tsx @@ -5,6 +5,7 @@ import { ILog, ILogAggregateAttributesResources } from 'types/api/logs/log'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import BodyTitleRenderer from './BodyTitleRenderer'; +import { typeToArrayTypeMapper } from './config'; import { AnyObject, IFieldAttributes } from './LogDetailedView.types'; export const recursiveParseJSON = (obj: string): Record => { @@ -107,40 +108,6 @@ export function flattenObject(obj: AnyObject, prefix = ''): AnyObject { }, {}); } -const isFloat = (num: number): boolean => num % 1 !== 0; - -export const getDataTypes = (value: unknown): DataTypes => { - if (typeof value === 'string') { - return DataTypes.String; - } - - if (typeof value === 'number') { - return isFloat(value) ? DataTypes.Float64 : DataTypes.Int64; - } - - if (typeof value === 'boolean') { - return DataTypes.bool; - } - - if (Array.isArray(value)) { - const firstElement = value[0]; - - if (typeof firstElement === 'string') { - return DataTypes.ArrayString; - } - - if (typeof firstElement === 'boolean') { - return DataTypes.ArrayBool; - } - - if (typeof firstElement === 'number') { - return isFloat(firstElement) ? DataTypes.ArrayFloat64 : DataTypes.ArrayInt64; - } - } - - return DataTypes.Int64; -}; - export const generateFieldKeyForArray = ( fieldKey: string, dataType: DataTypes, @@ -217,3 +184,45 @@ export const aggregateAttributesResourcesToString = (logData: ILog): string => { return JSON.stringify(outputJson, null, 2); }; + +const isFloat = (num: number): boolean => num % 1 !== 0; + +const isBooleanString = (str: string): boolean => + str.toLowerCase() === 'true' || str.toLowerCase() === 'false'; + +const determineType = (val: unknown): DataTypes => { + if (typeof val === 'string') { + if (isBooleanString(val)) { + return DataTypes.bool; + } + + const numberValue = parseFloat(val); + + if (!Number.isNaN(numberValue)) { + return isFloat(numberValue) ? DataTypes.Float64 : DataTypes.Int64; + } + + return DataTypes.String; + } + + if (typeof val === 'number') { + return isFloat(val) ? DataTypes.Float64 : DataTypes.Int64; + } + + if (typeof val === 'boolean') { + return DataTypes.bool; + } + + return DataTypes.EMPTY; +}; + +export const getDataTypes = (value: unknown): DataTypes => { + const getArrayType = (elementType: DataTypes): DataTypes => + typeToArrayTypeMapper[elementType] || DataTypes.EMPTY; + + if (Array.isArray(value)) { + return getArrayType(determineType(value[0])); + } + + return determineType(value); +}; diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index 4b2c4fe6b3..59c693b6e6 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -87,10 +87,7 @@ function QueryBuilderSearch({ handleSearch(value); }; - const isDisabled = - !!searchValue || - OPERATORS.HAS === tagOperator || - OPERATORS.NHAS === tagOperator; + const isDisabled = !!searchValue; return ( From 11863040bba95dc239cdbe61045a1d130ef4a1ac Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Thu, 5 Oct 2023 10:34:17 +0530 Subject: [PATCH 06/34] fix: alerts is now migrated to new alerts page (#3669) --- frontend/src/container/CreateAlertRule/defaults.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/container/CreateAlertRule/defaults.ts b/frontend/src/container/CreateAlertRule/defaults.ts index 2ac2f3a7b8..8517d9b18c 100644 --- a/frontend/src/container/CreateAlertRule/defaults.ts +++ b/frontend/src/container/CreateAlertRule/defaults.ts @@ -3,6 +3,7 @@ import { initialQueryPromQLData, PANEL_TYPES, } from 'constants/queryBuilder'; +import ROUTES from 'constants/routes'; import { AlertTypes } from 'types/api/alerts/alertTypes'; import { AlertDef, @@ -77,7 +78,7 @@ export const logAlertDefaults: AlertDef = { }, labels: { severity: 'warning', - details: `${window.location.protocol}//${window.location.host}/logs`, + details: `${window.location.protocol}//${window.location.host}${ROUTES.LOGS_EXPLORER}`, }, annotations: defaultAnnotations, evalWindow: defaultEvalWindow, From 0ad5d671405ae7e98d61cd68ea4d45d654ef0509 Mon Sep 17 00:00:00 2001 From: Raj Kamal Singh <1133322+rkssisodiya@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:27:41 +0530 Subject: [PATCH 07/34] QS: Collector simulator (#3656) * feat: get collectorsimulator started and add inmemoryreceiver * feat: add collectorsimulator/inmemoryexporter * feat: add collectorsimulator.SimulateLogsProcessing * chore: clean up collector simulator code a little * chore: update go.sum entries for cors * chore: add collectorsimulator tests to make cmd * chore: move to latest dependency version for collectorsimulator * chore: revert to dependency versions matching signoz-otel-col * chore: cleanup: reorganize collectorsimulator logic * chore: some more cleanup * chore: some more cleanup * chore: some more cleanup * chore: redo go.mod --- Makefile | 1 + go.mod | 38 ++- go.sum | 90 +++++++ .../collectorsimulator/collectorsimulator.go | 234 ++++++++++++++++++ .../inmemoryexporter/config.go | 16 ++ .../inmemoryexporter/config_test.go | 48 ++++ .../inmemoryexporter/exporter.go | 86 +++++++ .../inmemoryexporter/exporter_test.go | 67 +++++ .../inmemoryexporter/factory.go | 34 +++ .../inmemoryexporter/factory_test.go | 28 +++ .../inmemoryreceiver/config.go | 16 ++ .../inmemoryreceiver/config_test.go | 48 ++++ .../inmemoryreceiver/factory.go | 41 +++ .../inmemoryreceiver/factory_test.go | 29 +++ .../inmemoryreceiver/receiver.go | 64 +++++ .../inmemoryreceiver/receiver_test.go | 68 +++++ pkg/query-service/collectorsimulator/logs.go | 122 +++++++++ .../collectorsimulator/logs_test.go | 113 +++++++++ 18 files changed, 1139 insertions(+), 4 deletions(-) create mode 100644 pkg/query-service/collectorsimulator/collectorsimulator.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryexporter/config.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryexporter/config_test.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryexporter/exporter.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryexporter/exporter_test.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryexporter/factory.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryexporter/factory_test.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryreceiver/config.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryreceiver/config_test.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryreceiver/factory.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryreceiver/factory_test.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryreceiver/receiver.go create mode 100644 pkg/query-service/collectorsimulator/inmemoryreceiver/receiver_test.go create mode 100644 pkg/query-service/collectorsimulator/logs.go create mode 100644 pkg/query-service/collectorsimulator/logs_test.go diff --git a/Makefile b/Makefile index d4df55c063..0d46bfa161 100644 --- a/Makefile +++ b/Makefile @@ -165,3 +165,4 @@ test: go test ./pkg/query-service/formatter/... go test ./pkg/query-service/tests/integration/... go test ./pkg/query-service/rules/... + go test ./pkg/query-service/collectorsimulator/... diff --git a/go.mod b/go.mod index 2402e92498..9d22d922c6 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974 - github.com/antonmedv/expr v1.12.4 + github.com/antonmedv/expr v1.12.5 github.com/auth0/go-jwt-middleware v1.0.1 github.com/cespare/xxhash v1.1.0 github.com/coreos/go-oidc/v3 v3.4.0 @@ -37,7 +37,7 @@ require ( github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f github.com/prometheus/common v0.44.0 github.com/prometheus/prometheus v2.5.0+incompatible - github.com/rs/cors v1.8.2 + github.com/rs/cors v1.9.0 github.com/russellhaering/gosaml2 v0.9.0 github.com/russellhaering/goxmldsig v1.2.0 github.com/samber/lo v1.38.1 @@ -47,7 +47,7 @@ require ( github.com/soheilhy/cmux v0.1.5 github.com/srikanthccv/ClickHouse-go-mock v0.4.0 github.com/stretchr/testify v1.8.4 - go.opentelemetry.io/collector/confmap v0.70.0 + go.opentelemetry.io/collector/confmap v0.79.0 go.opentelemetry.io/otel v1.17.0 go.opentelemetry.io/otel/sdk v1.16.0 go.uber.org/multierr v1.11.0 @@ -65,6 +65,7 @@ require ( ) require ( + contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect @@ -77,6 +78,7 @@ require ( github.com/beevik/etree v1.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect @@ -89,8 +91,10 @@ require ( github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect @@ -98,6 +102,7 @@ require ( github.com/gosimple/unidecode v1.0.0 // indirect github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -106,6 +111,7 @@ require ( github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid v1.2.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/minio/md5-simd v1.1.0 // indirect @@ -116,28 +122,51 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/observiq/ctimefmt v1.0.0 // indirect github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.79.0 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.79.0 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstransformprocessor v0.79.0 // indirect github.com/paulmach/orb v0.10.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/procfs v0.11.0 // indirect + github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/backo-go v1.0.1 // indirect + github.com/shirou/gopsutil/v3 v3.23.4 // indirect + github.com/shoenig/go-m1cpu v0.1.5 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/smarty/assertions v1.15.0 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tklauser/go-sysconf v0.3.11 // indirect + github.com/tklauser/numcpus v0.6.0 // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect - go.opentelemetry.io/collector/featuregate v0.70.0 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/collector v0.79.0 // indirect + go.opentelemetry.io/collector/component v0.79.0 // indirect + go.opentelemetry.io/collector/consumer v0.79.0 // indirect + go.opentelemetry.io/collector/exporter v0.79.0 // indirect + go.opentelemetry.io/collector/featuregate v1.0.0-rcv0012 // indirect go.opentelemetry.io/collector/pdata v1.0.0-rcv0014 // indirect + go.opentelemetry.io/collector/receiver v0.79.0 // indirect go.opentelemetry.io/collector/semconv v0.81.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect + go.opentelemetry.io/otel/bridge/opencensus v0.39.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect go.opentelemetry.io/otel/metric v1.17.0 // indirect + go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect go.opentelemetry.io/otel/trace v1.17.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.11.0 // indirect @@ -146,6 +175,7 @@ require ( golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect + gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 // indirect diff --git a/go.sum b/go.sum index 6283ae2486..e4fb091cf3 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= +contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -90,6 +92,7 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Mottl/ctimefmt v0.0.0-20190803144728-fd2ac23a585a/go.mod h1:eyj2WSIdoPMPs2eNTLpSmM6Nzqo4V80/d6jHpnJ1SAI= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb h1:bneLSKPf9YUSFmafKx32bynV6QrzViL/s+ZDvQxH1E4= @@ -114,6 +117,8 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antonmedv/expr v1.12.4 h1:YRkeF7r0cejMS47bDYe3Jyes7L9t1AhpunC+Duq+R9k= github.com/antonmedv/expr v1.12.4/go.mod h1:FPC8iWArxls7axbVLsW+kpg1mz29A1b2M6jt+hZfDkU= +github.com/antonmedv/expr v1.12.5 h1:Fq4okale9swwL3OeLLs9WD9H6GbgBLJyN/NUHRv+n0E= +github.com/antonmedv/expr v1.12.5/go.mod h1:FPC8iWArxls7axbVLsW+kpg1mz29A1b2M6jt+hZfDkU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= @@ -146,6 +151,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -173,6 +180,7 @@ github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -244,12 +252,14 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -258,6 +268,8 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= @@ -291,6 +303,8 @@ github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -469,6 +483,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ionos-cloud/sdk-go/v6 v6.1.8 h1:493wE/BkZxJf7x79UCE0cYGPZoqQcPiEBALvt7uVGY0= github.com/ionos-cloud/sdk-go/v6 v6.1.8/go.mod h1:EzEgRIDxBELvfoa/uBN0kOQaqovLjUWEB7iW4/Q+t4k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -527,6 +543,8 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linode/linodego v1.19.0 h1:n4WJrcr9+30e9JGZ6DI0nZbm5SdAj1kSwvvt/998YUw= github.com/linode/linodego v1.19.0/go.mod h1:XZFR+yJ9mm2kwf6itZ6SCpu+6w3KnIevV0Uu5HNWJgQ= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= @@ -593,6 +611,8 @@ github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnu github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/observiq/ctimefmt v1.0.0 h1:r7vTJ+Slkrt9fZ67mkf+mA6zAdR5nGIJRMTzkUyvilk= +github.com/observiq/ctimefmt v1.0.0/go.mod h1:mxi62//WbSpG/roCO1c6MqZ7zQTvjVtYheqHN3eOjvc= github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -614,6 +634,12 @@ github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= github.com/open-telemetry/opamp-go v0.5.0 h1:2YFbb6G4qBkq3yTRdVb5Nfz9hKHW/ldUyex352e1J7g= github.com/open-telemetry/opamp-go v0.5.0/go.mod h1:IMdeuHGVc5CjKSu5/oNV0o+UmiXuahoHvoZ4GOmAI9M= +github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.79.0 h1:OZPeakqoSZ1yRlmGBlWi9kISx/9PJzlNLGLutFPOQY0= +github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.79.0/go.mod h1:VOHKYi1wm+/c2wZA3mY1Grd4eYP8uS//EV0yHBbGfGw= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.79.0 h1:o1aUgN0pA5Sc0s2bOUy7vDoNyJ6D6qdHihXk3BKyf58= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.79.0/go.mod h1:t8I2umZdg81AQmncs7fVHw1YMzSol3A7ecsc2lfqgaM= +github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstransformprocessor v0.79.0 h1:EpuwiWvq1hqS4PAp/+kMvWVkM4o+PRGtTGSDLpmIeME= +github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstransformprocessor v0.79.0/go.mod h1:0dccj1BrKVG00hvt2f70tu7Re1YjAl5Jpy2lduSrLnI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= @@ -648,11 +674,16 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f h1:h0p1aZ9F5d6IXOygysob3g4B07b+HuVUQC0VJKD8wA4= github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -665,6 +696,9 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= @@ -673,8 +707,12 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk= github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= +github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= @@ -687,11 +725,14 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= +github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russellhaering/gosaml2 v0.9.0 h1:CNMnH42z/GirrKjdmNrSS6bAAs47F9bPdl4PfRmVOIk= github.com/russellhaering/gosaml2 v0.9.0/go.mod h1:byViER/1YPUa0Puj9ROZblpoq2jsE7h/CJmitzX0geU= github.com/russellhaering/goxmldsig v1.2.0 h1:Y6GTTc9Un5hCxSzVz4UIWQ/zuVwDvzJk80guqzwx6Vg= github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= @@ -706,6 +747,12 @@ github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N+ github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o= +github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8= +github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ= +github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ= +github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -729,6 +776,8 @@ github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/srikanthccv/ClickHouse-go-mock v0.4.0 h1:tLk7qoDLg7Z5YD5mOmNqjRDbsm6ehJVXOFvSnG+gQAg= @@ -746,10 +795,16 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= +github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= @@ -767,6 +822,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= @@ -778,22 +835,46 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/collector v0.79.0 h1:Lra7U0ilMor1g5WVkO3YZ0kZYsvzAtGN+Uq+CmC96JY= +go.opentelemetry.io/collector v0.79.0/go.mod h1:O2Vfwykphq9VqdATZiAypjnJMS3WFBXwFSe/0ujo38Q= +go.opentelemetry.io/collector/component v0.79.0 h1:ZKLJ4qa0AngmyGp1RQBJgl6OIP6mxdfrVpbz09h/W34= +go.opentelemetry.io/collector/component v0.79.0/go.mod h1:rX0gixMemcXZTZaML5zUiT+5txZUYkWnACscJkFVj18= go.opentelemetry.io/collector/confmap v0.70.0 h1:GJDaM7c3yFyT7Zv6l2/5ahwaqPCvtC92Ii8Bg2AVdjU= go.opentelemetry.io/collector/confmap v0.70.0/go.mod h1:8//JWR2TMChLH35Az0mGFrCskEIP6POgZJK6iRRhzeM= +go.opentelemetry.io/collector/confmap v0.79.0 h1:a4XVde3lLP81BiSbt8AzVD6pvQBX8YkrB9ZtMSHKv1A= +go.opentelemetry.io/collector/confmap v0.79.0/go.mod h1:cKr2c7lVtEJCuMOncUPlcROJBbTFaHiPjYp1Y8RbL+Q= +go.opentelemetry.io/collector/consumer v0.79.0 h1:V/4PCvbTw2Bt+lYb/ogac0g/nCCb3oKnmz+jM3t5Dyk= +go.opentelemetry.io/collector/consumer v0.79.0/go.mod h1:VfqIyUI5K20zXx3mfVN+skmA+V3sV5fNorJ5TaIOj/U= +go.opentelemetry.io/collector/exporter v0.79.0 h1:PxhKgWf1AkZvN1PjiJT5xiO+pKZA9Y4fyuMs5aNFuEA= +go.opentelemetry.io/collector/exporter v0.79.0/go.mod h1:qlXiqnOUeHelpAwk03f8nB5+91UIqlA7udSBsj9bJ3M= go.opentelemetry.io/collector/featuregate v0.70.0 h1:Xr6hrMT/++SjTm06nreex8WlpgFhYJ7S0yRVn1OvVf8= go.opentelemetry.io/collector/featuregate v0.70.0/go.mod h1:ih+oCwrHW3bLac/qnPUzes28yDCDmh8WzsAKKauwCYI= +go.opentelemetry.io/collector/featuregate v1.0.0-rcv0012 h1:pSO81lfikGEgRXHepmOGy2o6WWCly427UJCgMJC5c8g= +go.opentelemetry.io/collector/featuregate v1.0.0-rcv0012/go.mod h1:/kVAsGUCyJXIDSgHftCN63QiwAEVHRLX2Kh/S+dqgHY= go.opentelemetry.io/collector/pdata v1.0.0-rcv0014 h1:iT5qH0NLmkGeIdDtnBogYDx7L58t6CaWGL378DEo2QY= go.opentelemetry.io/collector/pdata v1.0.0-rcv0014/go.mod h1:BRvDrx43kiSoUx3mr7SoA7h9B8+OY99mUK+CZSQFWW4= +go.opentelemetry.io/collector/receiver v0.79.0 h1:Ag4hciAYklQWDpKbnmqhfh9zJlUskWvThpCpphp12b4= +go.opentelemetry.io/collector/receiver v0.79.0/go.mod h1:+/xe0VoYl6Mli+KQTZWBR2apqFsbioAAqu7abzKDskI= go.opentelemetry.io/collector/semconv v0.81.0 h1:lCYNNo3powDvFIaTPP2jDKIrBiV1T92NK4QgL/aHYXw= go.opentelemetry.io/collector/semconv v0.81.0/go.mod h1:TlYPtzvsXyHOgr5eATi43qEMqwSmIziivJB2uctKswo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8= +go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo= +go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc= go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= +go.opentelemetry.io/otel/bridge/opencensus v0.39.0 h1:YHivttTaDhbZIHuPlg1sWsy2P5gj57vzqPfkHItgbwQ= +go.opentelemetry.io/otel/bridge/opencensus v0.39.0/go.mod h1:vZ4537pNjFDXEx//WldAR6Ro2LC8wwmFC76njAXwNPE= +go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA= +go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y= go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc= go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= +go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -977,6 +1058,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1008,6 +1090,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1034,6 +1117,7 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1043,10 +1127,13 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1138,6 +1225,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= +gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1320,6 +1409,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/pkg/query-service/collectorsimulator/collectorsimulator.go b/pkg/query-service/collectorsimulator/collectorsimulator.go new file mode 100644 index 0000000000..c4537cf3ee --- /dev/null +++ b/pkg/query-service/collectorsimulator/collectorsimulator.go @@ -0,0 +1,234 @@ +package collectorsimulator + +import ( + "bytes" + "context" + "fmt" + "strings" + + "github.com/google/uuid" + "github.com/knadh/koanf/parsers/yaml" + "github.com/pkg/errors" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/confmap/provider/yamlprovider" + "go.opentelemetry.io/collector/connector" + "go.opentelemetry.io/collector/exporter" + "go.opentelemetry.io/collector/extension" + "go.opentelemetry.io/collector/otelcol" + "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/service" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "go.signoz.io/signoz/pkg/query-service/collectorsimulator/inmemoryexporter" + "go.signoz.io/signoz/pkg/query-service/collectorsimulator/inmemoryreceiver" + "go.signoz.io/signoz/pkg/query-service/model" +) + +// Puts together a collector service with inmemory receiver and exporter +// for simulating processing of signal data through an otel collector +type CollectorSimulator struct { + // collector service to be used for the simulation + collectorSvc *service.Service + + // Buffer where collectorSvc will log errors. + collectorErrorLogsBuffer *bytes.Buffer + + // error channel where collector components will report fatal errors + // Gets passed in as AsyncErrorChannel in service.Settings when creating a collector service. + collectorErrorChannel chan error + + // Unique ids of inmemory receiver and exporter instances that + // will be created by collectorSvc + inMemoryReceiverId string + inMemoryExporterId string +} + +func NewCollectorSimulator( + ctx context.Context, + signalType component.DataType, + processorFactories map[component.Type]processor.Factory, + processorConfigs []ProcessorConfig, +) (*CollectorSimulator, *model.ApiError) { + // Put together collector component factories for use in the simulation + receiverFactories, err := receiver.MakeFactoryMap(inmemoryreceiver.NewFactory()) + if err != nil { + return nil, model.InternalError(errors.Wrap(err, "could not create receiver factories.")) + } + exporterFactories, err := exporter.MakeFactoryMap(inmemoryexporter.NewFactory()) + if err != nil { + return nil, model.InternalError(errors.Wrap(err, "could not create processor factories.")) + } + factories := otelcol.Factories{ + Receivers: receiverFactories, + Processors: processorFactories, + Exporters: exporterFactories, + } + + // Prepare collector config yaml for simulation + inMemoryReceiverId := uuid.NewString() + inMemoryExporterId := uuid.NewString() + + collectorConfYaml, err := generateSimulationConfig( + signalType, inMemoryReceiverId, processorConfigs, inMemoryExporterId, + ) + if err != nil { + return nil, model.BadRequest(errors.Wrap(err, "could not generate collector config")) + } + + // Parse and validate collector config + yamlP := yamlprovider.New() + confProvider, err := otelcol.NewConfigProvider(otelcol.ConfigProviderSettings{ + ResolverSettings: confmap.ResolverSettings{ + URIs: []string{"yaml:" + string(collectorConfYaml)}, + Providers: map[string]confmap.Provider{yamlP.Scheme(): yamlP}, + }, + }) + if err != nil { + return nil, model.BadRequest(errors.Wrap(err, "could not create config provider.")) + } + collectorCfg, err := confProvider.Get(ctx, factories) + if err != nil { + return nil, model.BadRequest(errors.Wrap(err, "failed to parse collector config")) + } + + if err = collectorCfg.Validate(); err != nil { + return nil, model.BadRequest(errors.Wrap(err, "invalid collector config")) + } + + // Build and start collector service. + collectorErrChan := make(chan error) + var collectorErrBuf bytes.Buffer + svcSettings := service.Settings{ + Receivers: receiver.NewBuilder(collectorCfg.Receivers, factories.Receivers), + Processors: processor.NewBuilder(collectorCfg.Processors, factories.Processors), + Exporters: exporter.NewBuilder(collectorCfg.Exporters, factories.Exporters), + Connectors: connector.NewBuilder(collectorCfg.Connectors, factories.Connectors), + Extensions: extension.NewBuilder(collectorCfg.Extensions, factories.Extensions), + AsyncErrorChannel: collectorErrChan, + LoggingOptions: []zap.Option{ + zap.ErrorOutput(zapcore.AddSync(&collectorErrBuf)), + }, + } + + collectorSvc, err := service.New(ctx, svcSettings, collectorCfg.Service) + if err != nil { + return nil, model.InternalError(errors.Wrap(err, "could not instantiate collector service")) + } + + return &CollectorSimulator{ + inMemoryReceiverId: inMemoryReceiverId, + inMemoryExporterId: inMemoryExporterId, + collectorSvc: collectorSvc, + collectorErrorLogsBuffer: &collectorErrBuf, + collectorErrorChannel: collectorErrChan, + }, nil +} + +func (l *CollectorSimulator) Start(ctx context.Context) ( + func(), *model.ApiError, +) { + // Calling collectorSvc.Start below will in turn call Start on + // inmemory receiver and exporter instances created by collectorSvc + // + // inmemory components are indexed in a global map after Start is called + // on them and will have to be cleaned up to ensure there is no memory leak + cleanupFn := func() { + inmemoryreceiver.CleanupInstance(l.inMemoryReceiverId) + inmemoryexporter.CleanupInstance(l.inMemoryExporterId) + } + + err := l.collectorSvc.Start(ctx) + if err != nil { + return cleanupFn, model.InternalError(errors.Wrap(err, "could not start collector service for simulation")) + } + + return cleanupFn, nil +} + +func (l *CollectorSimulator) GetReceiver() *inmemoryreceiver.InMemoryReceiver { + return inmemoryreceiver.GetReceiverInstance(l.inMemoryReceiverId) +} + +func (l *CollectorSimulator) GetExporter() *inmemoryexporter.InMemoryExporter { + return inmemoryexporter.GetExporterInstance(l.inMemoryExporterId) +} + +func (l *CollectorSimulator) Shutdown(ctx context.Context) ( + simulationErrs []string, apiErr *model.ApiError, +) { + shutdownErr := l.collectorSvc.Shutdown(ctx) + + // Collect all errors logged or reported by collectorSvc + simulationErrs = []string{} + close(l.collectorErrorChannel) + for reportedErr := range l.collectorErrorChannel { + simulationErrs = append(simulationErrs, reportedErr.Error()) + } + + if l.collectorErrorLogsBuffer.Len() > 0 { + errBufText := strings.TrimSpace(l.collectorErrorLogsBuffer.String()) + errBufLines := strings.Split(errBufText, "\n") + simulationErrs = append(simulationErrs, errBufLines...) + } + + if shutdownErr != nil { + return simulationErrs, model.InternalError(errors.Wrap( + shutdownErr, "could not shutdown the collector service", + )) + } + return simulationErrs, nil +} + +func generateSimulationConfig( + signalType component.DataType, + receiverId string, + processorConfigs []ProcessorConfig, + exporterId string, +) ([]byte, error) { + baseConf := fmt.Sprintf(` + receivers: + memory: + id: %s + exporters: + memory: + id: %s + service: + telemetry: + metrics: + level: none + logs: + level: error + `, receiverId, exporterId) + + simulationConf, err := yaml.Parser().Unmarshal([]byte(baseConf)) + if err != nil { + return nil, err + } + + processors := map[string]interface{}{} + procNamesInOrder := []string{} + for _, processorConf := range processorConfigs { + processors[processorConf.Name] = processorConf.Config + procNamesInOrder = append(procNamesInOrder, processorConf.Name) + } + simulationConf["processors"] = processors + + svc := simulationConf["service"].(map[string]interface{}) + svc["pipelines"] = map[string]interface{}{ + string(signalType): map[string]interface{}{ + "receivers": []string{"memory"}, + "processors": procNamesInOrder, + "exporters": []string{"memory"}, + }, + } + + simulationConfYaml, err := yaml.Parser().Marshal(simulationConf) + if err != nil { + return nil, err + } + + return simulationConfYaml, nil +} diff --git a/pkg/query-service/collectorsimulator/inmemoryexporter/config.go b/pkg/query-service/collectorsimulator/inmemoryexporter/config.go new file mode 100644 index 0000000000..5b23b041ce --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryexporter/config.go @@ -0,0 +1,16 @@ +package inmemoryexporter + +import "fmt" + +type Config struct { + // Unique id for the exporter. + // Useful for getting a hold of the exporter in code that doesn't control its instantiation. + Id string `mapstructure:"id"` +} + +func (c *Config) Validate() error { + if len(c.Id) < 1 { + return fmt.Errorf("inmemory exporter: id is required") + } + return nil +} diff --git a/pkg/query-service/collectorsimulator/inmemoryexporter/config_test.go b/pkg/query-service/collectorsimulator/inmemoryexporter/config_test.go new file mode 100644 index 0000000000..29749757dc --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryexporter/config_test.go @@ -0,0 +1,48 @@ +package inmemoryexporter + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" +) + +func TestValidate(t *testing.T) { + tests := []struct { + name string + rawConf *confmap.Conf + errorExpected bool + }{ + { + name: "with id", + rawConf: confmap.NewFromStringMap(map[string]interface{}{ + "id": "test_exporter", + }), + errorExpected: false, + }, + { + name: "empty id", + rawConf: confmap.NewFromStringMap(map[string]interface{}{ + "id": "", + }), + errorExpected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + err := component.UnmarshalConfig(tt.rawConf, cfg) + require.NoError(t, err, "could not UnmarshalConfig") + + err = component.ValidateConfig(cfg) + if tt.errorExpected { + require.NotNilf(t, err, "Invalid config did not return validation error: %v", cfg) + } else { + require.NoErrorf(t, err, "Valid config returned validation error: %v", cfg) + } + }) + } +} diff --git a/pkg/query-service/collectorsimulator/inmemoryexporter/exporter.go b/pkg/query-service/collectorsimulator/inmemoryexporter/exporter.go new file mode 100644 index 0000000000..3cff186016 --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryexporter/exporter.go @@ -0,0 +1,86 @@ +package inmemoryexporter + +import ( + "context" + "fmt" + "sync" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/pdata/plog" +) + +// An in-memory exporter for testing and generating previews. +type InMemoryExporter struct { + // Unique identifier for the exporter. + id string + // mu protects the data below + mu sync.Mutex + // slice of pdata.Logs that were received by this exporter. + logs []plog.Logs +} + +// ConsumeLogs implements component.LogsExporter. +func (e *InMemoryExporter) ConsumeLogs(ctx context.Context, ld plog.Logs) error { + e.mu.Lock() + defer e.mu.Unlock() + + e.logs = append(e.logs, ld) + return nil +} + +func (e *InMemoryExporter) GetLogs() []plog.Logs { + e.mu.Lock() + defer e.mu.Unlock() + + return e.logs +} + +func (e *InMemoryExporter) ResetLogs() { + e.mu.Lock() + defer e.mu.Unlock() + + e.logs = nil +} + +func (e *InMemoryExporter) Capabilities() consumer.Capabilities { + return consumer.Capabilities{MutatesData: false} +} + +// Keep track of all exporter instances in the process. +// Useful for getting a hold of the exporter in scenarios where one doesn't +// create the instances. Eg: bringing up a collector service from collector config +var allExporterInstances map[string]*InMemoryExporter +var allExportersLock sync.Mutex + +func init() { + allExporterInstances = make(map[string]*InMemoryExporter) +} + +func GetExporterInstance(id string) *InMemoryExporter { + return allExporterInstances[id] +} + +func CleanupInstance(exporterId string) { + allExportersLock.Lock() + defer allExportersLock.Unlock() + + delete(allExporterInstances, exporterId) +} + +func (e *InMemoryExporter) Start(ctx context.Context, host component.Host) error { + allExportersLock.Lock() + defer allExportersLock.Unlock() + + if allExporterInstances[e.id] != nil { + return fmt.Errorf("exporter with id %s is already running", e.id) + } + + allExporterInstances[e.id] = e + return nil +} + +func (e *InMemoryExporter) Shutdown(ctx context.Context) error { + CleanupInstance(e.id) + return nil +} diff --git a/pkg/query-service/collectorsimulator/inmemoryexporter/exporter_test.go b/pkg/query-service/collectorsimulator/inmemoryexporter/exporter_test.go new file mode 100644 index 0000000000..4fe4753d72 --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryexporter/exporter_test.go @@ -0,0 +1,67 @@ +package inmemoryexporter + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/exporter" +) + +func TestExporterLifecycle(t *testing.T) { + require := require.New(t) + testExporterId := uuid.NewString() + + // Should be able to get a hold of the exporter after starting it. + require.Nil(GetExporterInstance(testExporterId)) + + constructed, err := makeTestExporter(testExporterId) + require.Nil(err, "could not make test exporter") + + err = constructed.Start(context.Background(), componenttest.NewNopHost()) + require.Nil(err, "could not start test exporter") + + testExporter := GetExporterInstance(testExporterId) + require.NotNil(testExporter, "could not get exporter instance by Id") + + // Should not be able to start 2 exporters with the same id + constructed2, err := makeTestExporter(testExporterId) + require.Nil(err, "could not create second exporter with same id") + + err = constructed2.Start(context.Background(), componenttest.NewNopHost()) + require.NotNil(err, "should not be able to start another exporter with same id before shutting down the previous one") + + // Should not be able to get a hold of an exporter after shutdown + testExporter.Shutdown(context.Background()) + require.Nil(GetExporterInstance(testExporterId), "should not be able to find exporter instance after shutdown") + + // Should be able to start a new exporter with same id after shutting down + constructed3, err := makeTestExporter(testExporterId) + require.Nil(err, "could not make exporter with same Id after shutting down previous one") + + err = constructed3.Start(context.Background(), componenttest.NewNopHost()) + require.Nil(err, "should be able to start another exporter with same id after shutting down the previous one") + + testExporter3 := GetExporterInstance(testExporterId) + require.NotNil(testExporter3, "could not get exporter instance by Id") + + testExporter3.Shutdown(context.Background()) + require.Nil(GetExporterInstance(testExporterId)) +} + +func makeTestExporter(exporterId string) (exporter.Logs, error) { + factory := NewFactory() + + cfg := factory.CreateDefaultConfig() + component.UnmarshalConfig(confmap.NewFromStringMap( + map[string]interface{}{"id": exporterId}), cfg, + ) + + return factory.CreateLogsExporter( + context.Background(), exporter.CreateSettings{}, cfg, + ) +} diff --git a/pkg/query-service/collectorsimulator/inmemoryexporter/factory.go b/pkg/query-service/collectorsimulator/inmemoryexporter/factory.go new file mode 100644 index 0000000000..7752693060 --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryexporter/factory.go @@ -0,0 +1,34 @@ +package inmemoryexporter + +import ( + "context" + + "github.com/google/uuid" + "github.com/pkg/errors" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/exporter" +) + +func createDefaultConfig() component.Config { + return &Config{ + Id: uuid.NewString(), + } +} + +func createLogsExporter( + _ context.Context, _ exporter.CreateSettings, config component.Config, +) (exporter.Logs, error) { + if err := component.ValidateConfig(config); err != nil { + return nil, errors.Wrap(err, "invalid inmemory exporter config") + } + return &InMemoryExporter{ + id: config.(*Config).Id, + }, nil +} + +func NewFactory() exporter.Factory { + return exporter.NewFactory( + "memory", + createDefaultConfig, + exporter.WithLogs(createLogsExporter, component.StabilityLevelBeta)) +} diff --git a/pkg/query-service/collectorsimulator/inmemoryexporter/factory_test.go b/pkg/query-service/collectorsimulator/inmemoryexporter/factory_test.go new file mode 100644 index 0000000000..1a9481169a --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryexporter/factory_test.go @@ -0,0 +1,28 @@ +package inmemoryexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/exporter" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, componenttest.CheckConfigStruct(cfg)) +} + +func TestCreateLogsExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + te, err := factory.CreateLogsExporter( + context.Background(), exporter.CreateSettings{}, cfg, + ) + assert.NoError(t, err) + assert.NotNil(t, te) +} diff --git a/pkg/query-service/collectorsimulator/inmemoryreceiver/config.go b/pkg/query-service/collectorsimulator/inmemoryreceiver/config.go new file mode 100644 index 0000000000..6df842ce3e --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryreceiver/config.go @@ -0,0 +1,16 @@ +package inmemoryreceiver + +import "fmt" + +type Config struct { + // Unique id for the receiver. + // Useful for getting a hold of the receiver in code that doesn't control its instantiation. + Id string `mapstructure:"id"` +} + +func (c *Config) Validate() error { + if len(c.Id) < 1 { + return fmt.Errorf("inmemory receiver: id is required") + } + return nil +} diff --git a/pkg/query-service/collectorsimulator/inmemoryreceiver/config_test.go b/pkg/query-service/collectorsimulator/inmemoryreceiver/config_test.go new file mode 100644 index 0000000000..a0daf71c45 --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryreceiver/config_test.go @@ -0,0 +1,48 @@ +package inmemoryreceiver + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" +) + +func TestValidate(t *testing.T) { + tests := []struct { + name string + rawConf *confmap.Conf + errorExpected bool + }{ + { + name: "with id", + rawConf: confmap.NewFromStringMap(map[string]interface{}{ + "id": "test_receiver", + }), + errorExpected: false, + }, + { + name: "empty id", + rawConf: confmap.NewFromStringMap(map[string]interface{}{ + "id": "", + }), + errorExpected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + err := component.UnmarshalConfig(tt.rawConf, cfg) + require.NoError(t, err, "could not UnmarshalConfig") + + err = component.ValidateConfig(cfg) + if tt.errorExpected { + require.NotNilf(t, err, "Invalid config did not return validation error: %v", cfg) + } else { + require.NoErrorf(t, err, "Valid config returned validation error: %v", cfg) + } + }) + } +} diff --git a/pkg/query-service/collectorsimulator/inmemoryreceiver/factory.go b/pkg/query-service/collectorsimulator/inmemoryreceiver/factory.go new file mode 100644 index 0000000000..9db222cc43 --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryreceiver/factory.go @@ -0,0 +1,41 @@ +package inmemoryreceiver + +import ( + "context" + + "github.com/google/uuid" + "github.com/pkg/errors" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" +) + +func createDefaultConfig() component.Config { + return &Config{ + Id: uuid.NewString(), + } +} + +func createLogsReceiver( + _ context.Context, + _ receiver.CreateSettings, + config component.Config, + consumer consumer.Logs, +) (receiver.Logs, error) { + if err := component.ValidateConfig(config); err != nil { + return nil, errors.Wrap(err, "invalid inmemory receiver config") + } + return &InMemoryReceiver{ + id: config.(*Config).Id, + nextConsumer: consumer, + }, nil + +} + +// NewFactory creates a new OTLP receiver factory. +func NewFactory() receiver.Factory { + return receiver.NewFactory( + "memory", + createDefaultConfig, + receiver.WithLogs(createLogsReceiver, component.StabilityLevelBeta)) +} diff --git a/pkg/query-service/collectorsimulator/inmemoryreceiver/factory_test.go b/pkg/query-service/collectorsimulator/inmemoryreceiver/factory_test.go new file mode 100644 index 0000000000..7bdcd80bee --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryreceiver/factory_test.go @@ -0,0 +1,29 @@ +package inmemoryreceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, componenttest.CheckConfigStruct(cfg)) +} + +func TestCreateLogsReceiver(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + te, err := factory.CreateLogsReceiver( + context.Background(), receiver.CreateSettings{}, cfg, consumertest.NewNop(), + ) + assert.NoError(t, err) + assert.NotNil(t, te) +} diff --git a/pkg/query-service/collectorsimulator/inmemoryreceiver/receiver.go b/pkg/query-service/collectorsimulator/inmemoryreceiver/receiver.go new file mode 100644 index 0000000000..d4b0a2abfe --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryreceiver/receiver.go @@ -0,0 +1,64 @@ +package inmemoryreceiver + +import ( + "context" + "fmt" + "sync" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/pdata/plog" +) + +// In memory receiver for testing and simulation +type InMemoryReceiver struct { + // Unique identifier for the receiver. + id string + + nextConsumer consumer.Logs +} + +func (r *InMemoryReceiver) ConsumeLogs(ctx context.Context, ld plog.Logs) error { + return r.nextConsumer.ConsumeLogs(ctx, ld) +} + +func (r *InMemoryReceiver) Capabilities() consumer.Capabilities { + return consumer.Capabilities{MutatesData: false} +} + +// Keep track of all receiver instances in the process. +// Useful for getting a hold of the receiver in scenarios where one doesn't +// create the instances. Eg: bringing up a collector service from collector config +var allReceiverInstances map[string]*InMemoryReceiver +var allReceiversLock sync.Mutex + +func init() { + allReceiverInstances = make(map[string]*InMemoryReceiver) +} + +func CleanupInstance(receiverId string) { + allReceiversLock.Lock() + defer allReceiversLock.Unlock() + delete(allReceiverInstances, receiverId) +} + +func (r *InMemoryReceiver) Start(ctx context.Context, host component.Host) error { + allReceiversLock.Lock() + defer allReceiversLock.Unlock() + + if allReceiverInstances[r.id] != nil { + return fmt.Errorf("receiver with id %s is already running", r.id) + } + + allReceiverInstances[r.id] = r + return nil +} + +func (r *InMemoryReceiver) Shutdown(ctx context.Context) error { + CleanupInstance(r.id) + return nil +} + +func GetReceiverInstance(id string) *InMemoryReceiver { + return allReceiverInstances[id] +} diff --git a/pkg/query-service/collectorsimulator/inmemoryreceiver/receiver_test.go b/pkg/query-service/collectorsimulator/inmemoryreceiver/receiver_test.go new file mode 100644 index 0000000000..4fe7169cc7 --- /dev/null +++ b/pkg/query-service/collectorsimulator/inmemoryreceiver/receiver_test.go @@ -0,0 +1,68 @@ +package inmemoryreceiver + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver" +) + +func TestReceiverLifecycle(t *testing.T) { + require := require.New(t) + testReceiverId := uuid.NewString() + + // Should be able to get a hold of the receiver after starting it. + require.Nil(GetReceiverInstance(testReceiverId), "receiver instance should not exist before Start()") + + constructed, err := makeTestLogReceiver(testReceiverId) + require.Nil(err, "could not make test receiver") + + err = constructed.Start(context.Background(), componenttest.NewNopHost()) + require.Nil(err, "could not start test receiver") + + testReceiver := GetReceiverInstance(testReceiverId) + require.NotNil(testReceiver, "could not get receiver instance by Id") + + // Should not be able to start 2 receivers with the same id + constructed2, err := makeTestLogReceiver(testReceiverId) + require.Nil(err, "could not create second receiver with same id") + + err = constructed2.Start(context.Background(), componenttest.NewNopHost()) + require.NotNil(err, "should not be able to start another receiver with same id before shutting down the previous one") + + // Should not be able to get a hold of an receiver after shutdown + testReceiver.Shutdown(context.Background()) + require.Nil(GetReceiverInstance(testReceiverId), "should not be able to find inmemory receiver after shutdown") + + // Should be able to start a new receiver with same id after shutting down + constructed3, err := makeTestLogReceiver(testReceiverId) + require.Nil(err, "could not make receiver with same Id after shutting down old one") + + err = constructed3.Start(context.Background(), componenttest.NewNopHost()) + require.Nil(err, "should be able to start another receiver with same id after shutting down the previous one") + + testReceiver3 := GetReceiverInstance(testReceiverId) + require.NotNil(testReceiver3, "could not get receiver instance by Id") + + testReceiver3.Shutdown(context.Background()) + require.Nil(GetReceiverInstance(testReceiverId)) +} + +func makeTestLogReceiver(receiverId string) (receiver.Logs, error) { + factory := NewFactory() + + cfg := factory.CreateDefaultConfig() + component.UnmarshalConfig(confmap.NewFromStringMap( + map[string]interface{}{"id": receiverId}), cfg, + ) + + return factory.CreateLogsReceiver( + context.Background(), receiver.CreateSettings{}, cfg, consumertest.NewNop(), + ) +} diff --git a/pkg/query-service/collectorsimulator/logs.go b/pkg/query-service/collectorsimulator/logs.go new file mode 100644 index 0000000000..ab445f79eb --- /dev/null +++ b/pkg/query-service/collectorsimulator/logs.go @@ -0,0 +1,122 @@ +package collectorsimulator + +import ( + "context" + "fmt" + "time" + + "github.com/pkg/errors" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/processor" + "go.signoz.io/signoz/pkg/query-service/model" +) + +type ProcessorConfig struct { + Name string + Config map[string]interface{} +} + +// Simulate processing of logs through the otel collector. +// Useful for testing, validation and generating previews. +func SimulateLogsProcessing( + ctx context.Context, + processorFactories map[component.Type]processor.Factory, + processorConfigs []ProcessorConfig, + logs []plog.Logs, + timeout time.Duration, +) ( + outputLogs []plog.Logs, collectorErrs []string, apiErr *model.ApiError, +) { + // Construct and start a simulator (wraps a collector service) + simulator, apiErr := NewCollectorSimulator( + ctx, component.DataTypeLogs, processorFactories, processorConfigs, + ) + if apiErr != nil { + return nil, nil, model.WrapApiError(apiErr, "could not create logs processing simulator") + } + + simulatorCleanup, apiErr := simulator.Start(ctx) + // We can not rely on collector service to shutdown successfully and cleanup refs to inmemory components. + defer simulatorCleanup() + if apiErr != nil { + return nil, nil, apiErr + } + + // Do the simulation + for _, plog := range logs { + apiErr = SendLogsToSimulator(ctx, simulator, plog) + if apiErr != nil { + return nil, nil, model.WrapApiError(apiErr, "could not consume logs for simulation") + } + } + + result, apiErr := GetProcessedLogsFromSimulator( + simulator, len(logs), timeout, + ) + if apiErr != nil { + return nil, nil, model.InternalError(model.WrapApiError(apiErr, + "could not get processed logs from simulator", + )) + } + + // Shut down the simulator + simulationErrs, apiErr := simulator.Shutdown(ctx) + if apiErr != nil { + return nil, simulationErrs, model.WrapApiError(apiErr, + "could not shutdown logs processing simulator", + ) + } + + return result, simulationErrs, nil +} + +func SendLogsToSimulator( + ctx context.Context, + simulator *CollectorSimulator, + plog plog.Logs, +) *model.ApiError { + receiver := simulator.GetReceiver() + if receiver == nil { + return model.InternalError(fmt.Errorf("could not find in memory receiver for simulator")) + } + if err := receiver.ConsumeLogs(ctx, plog); err != nil { + return model.InternalError(errors.Wrap(err, + "inmemory receiver could not consume logs for simulation", + )) + } + return nil +} + +func GetProcessedLogsFromSimulator( + simulator *CollectorSimulator, + minLogCount int, + timeout time.Duration, +) ( + []plog.Logs, *model.ApiError, +) { + exporter := simulator.GetExporter() + if exporter == nil { + return nil, model.InternalError(fmt.Errorf("could not find in memory exporter for simulator")) + } + + // Must do a time based wait to ensure all logs come through. + // For example, logstransformprocessor does internal batching and it + // takes (processorCount * batchTime) for logs to get through. + startTsMillis := time.Now().UnixMilli() + for { + elapsedMillis := time.Now().UnixMilli() - startTsMillis + if elapsedMillis > timeout.Milliseconds() { + break + } + + exportedLogs := exporter.GetLogs() + if len(exportedLogs) >= minLogCount { + return exportedLogs, nil + } + + time.Sleep(50 * time.Millisecond) + } + + return exporter.GetLogs(), nil +} diff --git a/pkg/query-service/collectorsimulator/logs_test.go b/pkg/query-service/collectorsimulator/logs_test.go new file mode 100644 index 0000000000..796d19f00f --- /dev/null +++ b/pkg/query-service/collectorsimulator/logs_test.go @@ -0,0 +1,113 @@ +package collectorsimulator + +import ( + "context" + "testing" + "time" + + "github.com/knadh/koanf/parsers/yaml" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstransformprocessor" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/processor" +) + +func TestLogsProcessingSimulation(t *testing.T) { + require := require.New(t) + + inputLogs := []plog.Logs{ + makeTestPlog("test log 1", map[string]string{ + "method": "GET", + }), + makeTestPlog("test log 2", map[string]string{ + "method": "POST", + }), + } + + testLogstransformConf1, err := yaml.Parser().Unmarshal([]byte(` + operators: + - type: router + id: router_signoz + routes: + - output: add + expr: attributes.method == "GET" + default: noop + - type: add + id: add + field: attributes.test + value: test-value-get + - type: noop + id: noop + `)) + require.Nil(err, "could not unmarshal test logstransform op config") + testProcessor1 := ProcessorConfig{ + Name: "logstransform/test", + Config: testLogstransformConf1, + } + + testLogstransformConf2, err := yaml.Parser().Unmarshal([]byte(` + operators: + - type: router + id: router_signoz + routes: + - output: add + expr: attributes.method == "POST" + default: noop + - type: add + id: add + field: attributes.test + value: test-value-post + - type: noop + id: noop + `)) + require.Nil(err, "could not unmarshal test logstransform op config") + testProcessor2 := ProcessorConfig{ + Name: "logstransform/test2", + Config: testLogstransformConf2, + } + + processorFactories, err := processor.MakeFactoryMap( + logstransformprocessor.NewFactory(), + ) + require.Nil(err, "could not create processors factory map") + + outputLogs, collectorErrs, apiErr := SimulateLogsProcessing( + context.Background(), + processorFactories, + []ProcessorConfig{testProcessor1, testProcessor2}, + inputLogs, + 300*time.Millisecond, + ) + require.Nil(apiErr, apiErr.ToError().Error()) + require.Equal(len(collectorErrs), 0) + + for _, l := range outputLogs { + rl := l.ResourceLogs().At(0) + sl := rl.ScopeLogs().At(0) + record := sl.LogRecords().At(0) + method, exists := record.Attributes().Get("method") + require.True(exists) + testVal, exists := record.Attributes().Get("test") + require.True(exists) + if method.Str() == "GET" { + require.Equal(testVal.Str(), "test-value-get") + } else { + require.Equal(testVal.Str(), "test-value-post") + } + } +} + +func makeTestPlog(body string, attrsStr map[string]string) plog.Logs { + pl := plog.NewLogs() + rl := pl.ResourceLogs().AppendEmpty() + + scopeLog := rl.ScopeLogs().AppendEmpty() + slRecord := scopeLog.LogRecords().AppendEmpty() + slRecord.Body().SetStr(body) + slAttribs := slRecord.Attributes() + for k, v := range attrsStr { + slAttribs.PutStr(k, v) + } + + return pl +} From a306fb64cbf79f1a1f1a4d64d5a5df4580ef74b3 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Thu, 5 Oct 2023 21:59:40 +0530 Subject: [PATCH 08/34] feat: update analytics endpoints (#3674) Co-authored-by: Palash Gupta --- frontend/src/index.html.ejs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/index.html.ejs b/frontend/src/index.html.ejs index abfd8258bb..227506006b 100644 --- a/frontend/src/index.html.ejs +++ b/frontend/src/index.html.ejs @@ -173,14 +173,20 @@ t.type = 'text/javascript'; t.async = !0; t.src = - 'https://cdn.segment.com/analytics.js/v1/' + key + '/analytics.min.js'; + 'https://analytics-cdn.signoz.io/analytics.js/v1/' + + key + + '/analytics.min.js'; var n = document.getElementsByTagName('script')[0]; n.parentNode.insertBefore(t, n); analytics._loadOptions = i; }; analytics._writeKey = SEGMENT_ID; analytics.SNIPPET_VERSION = '4.16.1'; - analytics.load(SEGMENT_ID); + analytics.load(SEGMENT_ID, { + integrations: { + 'Segment.io': { apiHost: 'analytics-api.signoz.io/v1' }, + }, + }); analytics.page(); } })(); From abed60bdfa558c7b35f1d104339b18322bc8828f Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Fri, 6 Oct 2023 09:26:37 +0530 Subject: [PATCH 09/34] fix: exists check for json filters added (#3675) * fix: exists check for json filters added * fix: comment updated --- pkg/query-service/app/logs/v3/json_filter.go | 58 +++++++++++++------ .../app/logs/v3/json_filter_test.go | 30 +++++++--- .../app/logs/v3/query_builder_test.go | 2 +- 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/pkg/query-service/app/logs/v3/json_filter.go b/pkg/query-service/app/logs/v3/json_filter.go index 345da5a013..abb0fe6712 100644 --- a/pkg/query-service/app/logs/v3/json_filter.go +++ b/pkg/query-service/app/logs/v3/json_filter.go @@ -52,11 +52,13 @@ var jsonLogOperators = map[v3.FilterOperator]string{ v3.FilterOperatorNotRegex: "NOT match(%s, %s)", v3.FilterOperatorIn: "IN", v3.FilterOperatorNotIn: "NOT IN", + v3.FilterOperatorExists: "JSON_EXISTS(%s, '$.%s')", + v3.FilterOperatorNotExists: "NOT JSON_EXISTS(%s, '$.%s')", v3.FilterOperatorHas: "has(%s, %s)", v3.FilterOperatorNotHas: "NOT has(%s, %s)", } -func getJSONFilterKey(key v3.AttributeKey, isArray bool) (string, error) { +func getJSONFilterKey(key v3.AttributeKey, op v3.FilterOperator, isArray bool) (string, error) { keyArr := strings.Split(key.Key, ".") if len(keyArr) < 2 { return "", fmt.Errorf("incorrect key, should contain at least 2 parts") @@ -67,6 +69,10 @@ func getJSONFilterKey(key v3.AttributeKey, isArray bool) (string, error) { return "", fmt.Errorf("only body can be the root key") } + if op == v3.FilterOperatorExists || op == v3.FilterOperatorNotExists { + return keyArr[0], nil + } + var dataType string var ok bool if dataType, ok = dataTypeMapping[string(key.DataType)]; !ok { @@ -99,29 +105,45 @@ func GetJSONFilter(item v3.FilterItem) (string, error) { dataType = v3.AttributeKeyDataType(val) } - key, err := getJSONFilterKey(item.Key, isArray) + key, err := getJSONFilterKey(item.Key, item.Operator, isArray) if err != nil { return "", err } // non array - value, err := utils.ValidateAndCastValue(item.Value, dataType) - if err != nil { - return "", fmt.Errorf("failed to validate and cast value for %s: %v", item.Key.Key, err) - } - op := v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator)))) - if logsOp, ok := jsonLogOperators[op]; ok { - switch op { - case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex, v3.FilterOperatorHas, v3.FilterOperatorNotHas: - fmtVal := utils.ClickHouseFormattedValue(value) - return fmt.Sprintf(logsOp, key, fmtVal), nil - case v3.FilterOperatorContains, v3.FilterOperatorNotContains: - return fmt.Sprintf("%s %s '%%%s%%'", key, logsOp, item.Value), nil - default: - fmtVal := utils.ClickHouseFormattedValue(value) - return fmt.Sprintf("%s %s %s", key, logsOp, fmtVal), nil + + var value interface{} + if op != v3.FilterOperatorExists && op != v3.FilterOperatorNotExists { + value, err = utils.ValidateAndCastValue(item.Value, dataType) + if err != nil { + return "", fmt.Errorf("failed to validate and cast value for %s: %v", item.Key.Key, err) } } - return "", fmt.Errorf("unsupported operator: %s", op) + + var filter string + if logsOp, ok := jsonLogOperators[op]; ok { + switch op { + case v3.FilterOperatorExists, v3.FilterOperatorNotExists: + filter = fmt.Sprintf(logsOp, key, strings.Join(strings.Split(item.Key.Key, ".")[1:], ".")) + case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex, v3.FilterOperatorHas, v3.FilterOperatorNotHas: + fmtVal := utils.ClickHouseFormattedValue(value) + filter = fmt.Sprintf(logsOp, key, fmtVal) + case v3.FilterOperatorContains, v3.FilterOperatorNotContains: + filter = fmt.Sprintf("%s %s '%%%s%%'", key, logsOp, item.Value) + default: + fmtVal := utils.ClickHouseFormattedValue(value) + filter = fmt.Sprintf("%s %s %s", key, logsOp, fmtVal) + } + } else { + return "", fmt.Errorf("unsupported operator: %s", op) + } + + // add exists check for non array items as default values of int/float/bool will corrupt the results + if !isArray && !(item.Operator == v3.FilterOperatorExists || item.Operator == v3.FilterOperatorNotExists) { + existsFilter := fmt.Sprintf("JSON_EXISTS(body, '$.%s')", strings.Join(strings.Split(item.Key.Key, ".")[1:], ".")) + filter = fmt.Sprintf("%s AND %s", existsFilter, filter) + } + + return filter, nil } diff --git a/pkg/query-service/app/logs/v3/json_filter_test.go b/pkg/query-service/app/logs/v3/json_filter_test.go index 455d705b1d..2f9a7f22da 100644 --- a/pkg/query-service/app/logs/v3/json_filter_test.go +++ b/pkg/query-service/app/logs/v3/json_filter_test.go @@ -12,6 +12,7 @@ var testGetJSONFilterKeyData = []struct { Key v3.AttributeKey IsArray bool ClickhouseKey string + Operator v3.FilterOperator Error bool }{ { @@ -129,7 +130,7 @@ var testGetJSONFilterKeyData = []struct { func TestGetJSONFilterKey(t *testing.T) { for _, tt := range testGetJSONFilterKeyData { Convey("testgetKey", t, func() { - columnName, err := getJSONFilterKey(tt.Key, tt.IsArray) + columnName, err := getJSONFilterKey(tt.Key, tt.Operator, tt.IsArray) if tt.Error { So(err, ShouldNotBeNil) } else { @@ -209,7 +210,7 @@ var testGetJSONFilterData = []struct { Operator: "=", Value: "hello", }, - Filter: "JSON_VALUE(body, '$.message') = 'hello'", + Filter: "JSON_EXISTS(body, '$.message') AND JSON_VALUE(body, '$.message') = 'hello'", }, { Name: "eq operator number", @@ -222,7 +223,7 @@ var testGetJSONFilterData = []struct { Operator: "=", Value: 1, }, - Filter: "JSONExtract(JSON_VALUE(body, '$.status'), '" + INT64 + "') = 1", + Filter: "JSON_EXISTS(body, '$.status') AND JSONExtract(JSON_VALUE(body, '$.status'), '" + INT64 + "') = 1", }, { Name: "neq operator number", @@ -235,7 +236,7 @@ var testGetJSONFilterData = []struct { Operator: "=", Value: 1.1, }, - Filter: "JSONExtract(JSON_VALUE(body, '$.status'), '" + FLOAT64 + "') = 1.100000", + Filter: "JSON_EXISTS(body, '$.status') AND JSONExtract(JSON_VALUE(body, '$.status'), '" + FLOAT64 + "') = 1.100000", }, { Name: "eq operator bool", @@ -248,7 +249,7 @@ var testGetJSONFilterData = []struct { Operator: "=", Value: true, }, - Filter: "JSONExtract(JSON_VALUE(body, '$.boolkey'), '" + BOOL + "') = true", + Filter: "JSON_EXISTS(body, '$.boolkey') AND JSONExtract(JSON_VALUE(body, '$.boolkey'), '" + BOOL + "') = true", }, { Name: "greater than operator", @@ -261,7 +262,7 @@ var testGetJSONFilterData = []struct { Operator: ">", Value: 1, }, - Filter: "JSONExtract(JSON_VALUE(body, '$.status'), '" + INT64 + "') > 1", + Filter: "JSON_EXISTS(body, '$.status') AND JSONExtract(JSON_VALUE(body, '$.status'), '" + INT64 + "') > 1", }, { Name: "regex operator", @@ -274,7 +275,7 @@ var testGetJSONFilterData = []struct { Operator: "regex", Value: "a*", }, - Filter: "match(JSON_VALUE(body, '$.message'), 'a*')", + Filter: "JSON_EXISTS(body, '$.message') AND match(JSON_VALUE(body, '$.message'), 'a*')", }, { Name: "contains operator", @@ -287,7 +288,20 @@ var testGetJSONFilterData = []struct { Operator: "contains", Value: "a", }, - Filter: "JSON_VALUE(body, '$.message') ILIKE '%a%'", + Filter: "JSON_EXISTS(body, '$.message') AND JSON_VALUE(body, '$.message') ILIKE '%a%'", + }, + { + Name: "exists", + FilterItem: v3.FilterItem{ + Key: v3.AttributeKey{ + Key: "body.message", + DataType: "string", + IsJSON: true, + }, + Operator: "exists", + Value: "", + }, + Filter: "JSON_EXISTS(body, '$.message')", }, } diff --git a/pkg/query-service/app/logs/v3/query_builder_test.go b/pkg/query-service/app/logs/v3/query_builder_test.go index 4c23b5f6a9..8ae90e2e0f 100644 --- a/pkg/query-service/app/logs/v3/query_builder_test.go +++ b/pkg/query-service/app/logs/v3/query_builder_test.go @@ -908,7 +908,7 @@ var testBuildLogsQueryData = []struct { }, }, TableName: "logs", - ExpectedQuery: "SELECT now() as ts, attributes_string_value[indexOf(attributes_string_key, 'name')] as name, toFloat64(count(*)) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND JSON_VALUE(body, '$.message') ILIKE '%a%' AND indexOf(attributes_string_key, 'name') > 0 group by name order by name DESC", + ExpectedQuery: "SELECT now() as ts, attributes_string_value[indexOf(attributes_string_key, 'name')] as name, toFloat64(count(*)) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND JSON_EXISTS(body, '$.message') AND JSON_VALUE(body, '$.message') ILIKE '%a%' AND indexOf(attributes_string_key, 'name') > 0 group by name order by name DESC", }, { Name: "TABLE: Test count with JSON Filter Array, groupBy, orderBy", From 321cba2af5c8b9ee568753def939c440cddd79e7 Mon Sep 17 00:00:00 2001 From: Wayne Zhou <170492961@qq.com> Date: Fri, 6 Oct 2023 12:00:52 +0800 Subject: [PATCH 10/34] docs: update the chinese readme to latest (#3670) --- README.zh-cn.md | 203 ++++++++++++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 74 deletions(-) diff --git a/README.zh-cn.md b/README.zh-cn.md index aaa89551bf..32b6328fcb 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -1,170 +1,225 @@ -

- SigNoz-logo +SigNoz-logo -

监视你的应用,并可排查已部署应用中的问题,这是一个开源的可替代DataDog、NewRelic的方案

+

监控你的应用,并且可排查已部署应用的问题,这是一个可替代 DataDog、NewRelic 的开源方案

- Downloads + Downloads GitHub issues tweet

-## +

+ 文档 • + 中文ReadMe • + 德文ReadMe • + 葡萄牙语ReadMe • + Slack 社区 • + Twitter +

-SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNoz使用分布式追踪来增加软件技术栈的可见性。 +## -👉 你能看到一些性能指标,服务、外部api调用、每个终端(endpoint)的p99延迟和错误率。 +SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可以使用 SigNoz 实现如下能力: -👉 通过准确的追踪来确定是什么引起了问题,并且可以看到每个独立请求的帧图(framegraph),这样你就能找到根本原因。 +👉 在同一块面板上,可视化 Metrics, Traces 和 Logs 内容。 -👉 聚合trace数据来获得业务相关指标。 +👉 你可以关注服务的 p99 延迟和错误率, 包括外部 API 调用和个别的端点。 -![screenzy-1644432902955](https://user-images.githubusercontent.com/504541/153270713-1b2156e6-ec03-42de-975b-3c02b8ec1836.png) -
-![screenzy-1644432986784](https://user-images.githubusercontent.com/504541/153270725-0efb73b3-06ed-4207-bf13-9b7e2e17c4b8.png) -
-![screenzy-1647005040573](https://user-images.githubusercontent.com/504541/157875938-a3d57904-ea6d-4278-b929-bd1408d7f94c.png) +👉 你可以找到问题的根因,通过提取相关问题的 traces 日志、单独查看请求 traces 的火焰图详情。 + +👉 执行 trace 数据聚合,以获取业务相关的 metrics + +👉 对日志过滤和查询,通过日志的属性建立看板和告警 + +👉 通过 Python,java,Ruby 和 Javascript 自动记录异常 + +👉 轻松的自定义查询和设置告警 + +### 应用 Metrics 展示 + +![application_metrics](https://user-images.githubusercontent.com/83692067/226637410-900dbc5e-6705-4b11-a10c-bd0faeb2a92f.png) + +### 分布式追踪 + +distributed_tracing_2 2 + +distributed_tracing_1 + +### 日志管理 + +logs_management + +### 基础设施监控 + +infrastructure_monitoring + +### 异常监控 + +![exceptions_light](https://user-images.githubusercontent.com/83692067/226637967-4188d024-3ac9-4799-be95-f5ea9c45436f.png) + +### 告警 + +alerts_management

- +## 加入我们 Slack 社区 -## 加入我们的Slack社区 - -来[Slack](https://signoz.io/slack) 跟我们打声招呼👋 +来 [Slack](https://signoz.io/slack) 和我们打招呼吧 👋

- +## 特性: -## 功能: +- 为 metrics, traces and logs 制定统一的 UI。 无需切换 Prometheus 到 Jaeger 去查找问题,也无需使用想 Elastic 这样的日志工具分开你的 metrics 和 traces -- 应用概览指标(metrics),如RPS, p50/p90/p99延迟率分位值,错误率等。 -- 应用中最慢的终端(endpoint) -- 查看特定请求的trace数据来分析下游服务问题、慢数据库查询问题 及调用第三方服务如支付网关的问题 -- 通过服务名称、操作、延迟、错误、标签来过滤traces。 -- 聚合trace数据(events/spans)来得到业务相关指标。比如,你可以通过过滤条件`customer_type: gold` or `deployment_version: v2` or `external_call: paypal` 来获取指定业务的错误率和p99延迟 -- 为metrics和trace提供统一的UI。排查问题不需要在Prometheus和Jaeger之间切换。 +- 默认统计应用的 metrics 数据,像 RPS (每秒请求数), 50th/90th/99th 的分位数延迟数据,还有相关的错误率 + +- 找到应用中最慢的端点 + +- 查看准确的请求跟踪数据,找到下游服务的问题了,比如 DB 慢查询,或者调用第三方的支付网关等 + +- 通过 服务名、操作方式、延迟、错误、标签/注释 过滤 traces 数据 + +- 通过聚合 trace 数据而获得业务相关的 metrics。 比如你可以通过 `customer_type: gold` 或者 `deployment_version: v2` 或者 `external_call: paypal` 获取错误率和 P99 延迟数据 + +- 原生支持 OpenTelemetry 日志,高级日志查询,自动收集 k8s 相关日志 + +- 快如闪电的日志分析 ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/)) + +- 可视化点到点的基础设施性能,提取有所有类型机器的 metrics 数据 + +- 轻易自定义告警查询

- +## 为什么使用 SigNoz? -## 为何选择SigNoz? +作为开发者, 我们发现 SaaS 厂商对一些大家想要的小功能都是闭源的,这种行为真的让人有点恼火。 闭源厂商还会在月底给你一张没有明细的巨额账单。 -作为开发人员,我们发现依赖闭源的SaaS厂商提供的每个小功能有些麻烦,闭源厂商通常会给你一份巨额月付账单,但不提供足够的透明度,你不知道你为哪些功能付费。 +我们想做一个自托管并且可开源的工具,像 DataDog 和 NewRelic 那样, 为那些担心数据隐私和安全的公司提供第三方服务。 -我们想做一个自服务的开源版本的工具,类似于DataDog和NewRelic,用于那些对客户数据流入第三方有隐私和安全担忧的厂商。 +作为开源的项目,你完全可以自己掌控你的配置、样本和更新。你同样可以基于 SigNoz 拓展特定的业务模块。 -开源也让你对配置、采样和正常运行时间有完整的控制,你可以在SigNoz基础上构建模块来满足特定的商业需求。 +### 支持的编程语言: -### 语言支持 - -我们支持[OpenTelemetry](https://opentelemetry.io)库,你可以使用它来装备应用。也就是说SigNoz支持任何支持OpenTelemetry库的框架和语言。 主要支持语言包括: +我们支持 [OpenTelemetry](https://opentelemetry.io)。作为一个观测你应用的库文件。所以任何 OpenTelemetry 支持的框架和语言,对于 SigNoz 也同样支持。 一些主要支持的语言如下: - Java - Python - NodeJS - Go +- PHP +- .NET +- Ruby +- Elixir +- Rust -你可以在这个文档里找到完整的语言列表 - https://opentelemetry.io/docs/ +你可以在这里找到全部支持的语言列表 - https://opentelemetry.io/docs/

- +## 让我们开始吧 -## 入门 +### 使用 Docker 部署 +请一步步跟随 [这里](https://signoz.io/docs/install/docker/) 通过 docker 来安装。 -### 使用Docker部署 - -请按照[这里](https://signoz.io/docs/install/docker/)列出的步骤使用Docker来安装 - -如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/install/troubleshooting/)会对你有帮助。 +这个 [排障说明书](https://signoz.io/docs/install/troubleshooting/) 可以帮助你解决碰到的问题。

 

+### 使用 Helm 在 Kubernetes 部署 -### 使用Helm在Kubernetes上部署 - -请跟着[这里](https://signoz.io/docs/deployment/helm_chart)的步骤使用helm charts安装 +请一步步跟随 [这里](https://signoz.io/docs/deployment/helm_chart) 通过 helm 来安装

- - -## 与其他方案的比较 +## 比较相似的工具 ### SigNoz vs Prometheus -如果你只是需要监控指标(metrics),那Prometheus是不错的,但如果你要无缝的在metrics和traces之间切换,那目前把Prometheus & Jaeger串起来的体验并不好。 +Prometheus 是一个针对 metrics 监控的强大工具。但是如果你想无缝的切换 metrics 和 traces 查询,你当前大概率需要在 Prometheus 和 Jaeger 之间切换。 -我们的目标是为metrics和traces提供统一的UI - 类似于Datadog这样的Saas厂提供的方案。并且能够对trace进行过滤和聚合,这是目前Jaeger缺失的功能。 +我们的目标是提供一个客户观测 metrics 和 traces 整合的 UI。就像 SaaS 供应商 DataDog,它提供很多 jaeger 缺失的功能,比如针对 traces 过滤功能和聚合功能。

 

### SigNoz vs Jaeger -Jaeger只做分布式追踪(distributed tracing),SigNoz则支持metrics,traces,logs ,即可视化的三大支柱。 +Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metrics, traces 和 logs 所有的观测。 -并且SigNoz有一些Jaeger没有的高级功能: +而且, SigNoz 相较于 Jaeger 拥有更对的高级功能: -- Jaegar UI无法在traces或过滤的traces上展示metrics。 -- Jaeger不能对过滤的traces做聚合操作。例如,拥有tag为customer_type='premium'的所有请求的p99延迟。而这个功能在SigNoz这儿是很容易实现。 +- Jaegar UI 不能提供任何基于 traces 的 metrics 查询和过滤。 + +- Jaeger 不能针对过滤的 traces 做聚合。 比如, p99 延迟的请求有个标签是 customer_type='premium'。 而这些在 SigNoz 可以轻松做到。 + +

 

+ +### SigNoz vs Elastic + +- SigNoz 的日志管理是基于 ClickHouse 实现的,可以使日志的聚合更加高效,因为它是基于 OLAP 的数据仓储。 + +- 与 Elastic 相比,可以节省 50% 的资源成本 + +我们已经公布了 Elastic 和 SigNoz 的性能对比。 请点击 [这里](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark) + +

 

+ +### SigNoz vs Loki + +- SigNoz 支持大容量高基数的聚合,但是 loki 是不支持的。 + +- SigNoz 支持索引的高基数查询,并且对索引没有数量限制,而 Loki 会在添加部分索引后到达最大上限。 + +- 相较于 SigNoz,Loki 在搜索大量数据下既困难又缓慢。 + +我们已经发布了基准测试对比 Loki 和 SigNoz 性能。请点击 [这里](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)

- - ## 贡献 +我们 ❤️ 你的贡献,无论大小。 请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 再开始给 SigNoz 做贡献。 -我们 ❤️ 任何贡献无论大小。 请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 然后开始给Signoz做贡献。 +如果你不知道如何开始? 只需要在 [slack 社区](https://signoz.io/slack) 通过 `#contributing` 频道联系我们。 -还不清楚怎么开始? 只需在[slack社区](https://signoz.io/slack)的`#contributing`频道里ping我们。 +### 项目维护人员 -### Project maintainers - -#### Backend +#### 后端 - [Ankit Nayan](https://github.com/ankitnayan) - [Nityananda Gohain](https://github.com/nityanandagohain) - [Srikanth Chekuri](https://github.com/srikanthccv) - [Vishal Sharma](https://github.com/makeavish) -#### Frontend +#### 前端 - [Palash Gupta](https://github.com/palashgdev) -#### DevOps +#### 运维开发 - [Prashant Shahi](https://github.com/prashant-shahi)

- - ## 文档 -文档在这里:https://signoz.io/docs/. 如果你觉得有任何不清楚或者有文档缺失,请在Github里发一个问题,并使用标签 `documentation` 或者在社区stack频道里告诉我们。 +你可以通过 https://signoz.io/docs/ 找到相关文档。如果你需要阐述问题或者发现一些确实的事件, 通过标签为 `documentation` 提交 Github 问题。或者通过 slack 社区频道。

- - ## 社区 -加入[slack community](https://signoz.io/slack),了解更多关于分布式跟踪、可观察性(observability),以及SigNoz。同时与其他用户和贡献者一起交流。 +加入 [slack 社区](https://signoz.io/slack) 去了解更多关于分布式追踪、可观测性系统 。或者与 SigNoz 其他用户和贡献者交流。 -如果你有任何想法、问题或者反馈,请在[Github Discussions](https://github.com/SigNoz/signoz/discussions)分享给我们。 +如果你有任何想法、问题、或者任何反馈, 请通过 [Github Discussions](https://github.com/SigNoz/signoz/discussions) 分享。 -最后,感谢我们这些优秀的贡献者们。 +不管怎么样,感谢这个项目的所有贡献者! - - - From 587034f573d7a8e47b98dfa9b3a885c7135dd3b7 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 6 Oct 2023 15:10:13 +0530 Subject: [PATCH 11/34] [Refactor]: graph manager to scss and fix the height issue (#3671) * refactor: graph manager to scss and fix the height issue * refactor: updated scss --- .../Graph/FullView/GraphManager.styles.scss | 21 +++++++++++++ .../Graph/FullView/GraphManager.tsx | 30 ++++++++----------- .../GridGraphLayout/Graph/FullView/styles.ts | 20 ------------- 3 files changed, 34 insertions(+), 37 deletions(-) create mode 100644 frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.styles.scss diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.styles.scss b/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.styles.scss new file mode 100644 index 0000000000..2d594aa8a9 --- /dev/null +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.styles.scss @@ -0,0 +1,21 @@ +.graph-manager-container { + margin-top: 1.25rem; + display: flex; + align-items: flex-end; + overflow-x: scroll; + + .filter-table-container { + flex-basis: 80%; + } + + .save-cancel-container { + flex-basis: 20%; + display: flex; + justify-content: flex-end; + } + + .save-cancel-button { + margin: 0 0.313rem; + } + +} \ No newline at end of file diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx index 61abbf2aa6..31f528a410 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx @@ -1,3 +1,5 @@ +import './GraphManager.styles.scss'; + import { Button, Input } from 'antd'; import { CheckboxChangeEvent } from 'antd/es/checkbox'; import { ResizeTable } from 'components/ResizeTable'; @@ -8,12 +10,6 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { eventEmitter } from 'utils/getEventEmitter'; import { getGraphVisibilityStateOnDataChange } from '../utils'; -import { - FilterTableAndSaveContainer, - FilterTableContainer, - SaveCancelButtonContainer, - SaveContainer, -} from './styles'; import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns'; import { ExtendedChartDataset, GraphManagerProps } from './types'; import { @@ -169,30 +165,30 @@ function GraphManager({ const dataSource = tableDataSet.filter((item) => item.show); return ( - - +
+
- - - +
+
+ - - + + - - - + +
+
); } diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts b/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts index 9e5bd09541..b73a2e9112 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts @@ -31,26 +31,6 @@ export const GraphContainer = styled.div` isGraphLegendToggleAvailable ? '50%' : '100%'}; `; -export const FilterTableAndSaveContainer = styled.div` - margin-top: 1.875rem; - display: flex; - align-items: flex-end; -`; - -export const FilterTableContainer = styled.div` - flex-basis: 80%; -`; - -export const SaveContainer = styled.div` - flex-basis: 20%; - display: flex; - justify-content: flex-end; -`; - -export const SaveCancelButtonContainer = styled.span` - margin: 0 0.313rem; -`; - export const LabelContainer = styled.button` max-width: 18.75rem; cursor: pointer; From 0e04b779a94740e741fc0e49800c3633b76e8030 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Fri, 6 Oct 2023 17:32:17 +0530 Subject: [PATCH 12/34] feat: lowercase operators support in the where clause is updated (#3657) * feat: lowercase operators suuport in the where clause is updated * feat: options is now updated * chore: log message is updated * chore: auto completed is updated * chore: tagRegex is updated * feat: update regex to math operators and text operators * chore: operator is updated * chore: options is updated --------- Co-authored-by: Yunus A M --- frontend/src/constants/queryBuilder.ts | 12 +++ .../filters/QueryBuilderSearch/utils.ts | 79 +++++-------------- .../src/hooks/queryBuilder/useAutoComplete.ts | 6 +- .../src/hooks/queryBuilder/useOperatorType.ts | 12 +++ frontend/src/hooks/queryBuilder/useOptions.ts | 36 +++++---- .../useSetCurrentKeyAndOperator.ts | 34 +++----- frontend/src/hooks/queryBuilder/useTag.ts | 1 + 7 files changed, 81 insertions(+), 99 deletions(-) diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index c53873bc5c..2ab6d365fd 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -278,23 +278,35 @@ export const QUERY_BUILDER_SEARCH_VALUES = { export const OPERATORS = { IN: 'IN', + in: 'in', NIN: 'NOT_IN', + not_in: 'not_in', LIKE: 'LIKE', + like: 'like', NLIKE: 'NOT_LIKE', + not_like: 'not_like', REGEX: 'REGEX', + regex: 'regex', NREGEX: 'NOT_REGEX', + nregex: 'not_regex', '=': '=', '!=': '!=', EXISTS: 'EXISTS', + exists: 'exists', NOT_EXISTS: 'NOT_EXISTS', + not_exists: 'not_exists', CONTAINS: 'CONTAINS', + contains: 'contains', NOT_CONTAINS: 'NOT_CONTAINS', + not_contains: 'not_contains', '>=': '>=', '>': '>', '<=': '<=', '<': '<', HAS: 'HAS', + has: 'has', NHAS: 'NHAS', + nhas: 'nhas', }; export const QUERY_BUILDER_OPERATORS_BY_TYPES = { diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts index 1859254fc4..dbe95d631f 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts @@ -3,25 +3,42 @@ import { parse } from 'papaparse'; import { orderByValueDelimiter } from '../OrderByFilter/utils'; +const operators = /=|!=|>=|>|<=|<$/; + // eslint-disable-next-line no-useless-escape -export const tagRegexp = /^\s*(.*?)\s*(IN|NOT_IN|LIKE|NOT_LIKE|REGEX|NOT_REGEX|=|!=|EXISTS|NOT_EXISTS|CONTAINS|NOT_CONTAINS|>=|>|<=|<|HAS|NHAS)\s*(.*)$/g; +export const tagRegexpV1 = /^\s*(.*?)\s*(IN|in|NOT_IN|nin|LIKE|like|NOT_LIKE|nlike|REGEX|regex|NOT_REGEX|nregex|=|!=|EXISTS|exists|NOT_EXISTS|nexists|CONTAINS|contains|NOT_CONTAINS|ncontains|>=|>|<=|<|HAS|has|NHAS|nhas)\s*(.*)$/g; + +export const tagRegexpV2 = /^\s*(.+?)\s+(IN|in|NOT_IN|nin|LIKE|like|NOT_LIKE|nlike|REGEX|regex|NOT_REGEX|nregex|EXISTS|exists|NOT_EXISTS|nexists|CONTAINS|contains|NOT_CONTAINS|ncontains|HAS|has|NHAS|nhas|=|!=|>=|>|<=|<)\s*(.*)$/g; export function isInNInOperator(value: string): boolean { return value === OPERATORS.IN || value === OPERATORS.NIN; } +function endsWithOperator(inputString: string): boolean { + return operators.test(inputString); +} + interface ITagToken { tagKey: string; tagOperator: string; tagValue: string[]; } +export function getMatchRegex(str: string): RegExp { + if (endsWithOperator(str)) { + return tagRegexpV1; + } + + return tagRegexpV2; +} + export function getTagToken(tag: string): ITagToken { - const matches = tag?.matchAll(tagRegexp); + const matches = tag?.matchAll(getMatchRegex(tag)); const [match] = matches ? Array.from(matches) : []; if (match) { const [, matchTagKey, matchTagOperator, matchTagValue] = match; + return { tagKey: matchTagKey, tagOperator: matchTagOperator, @@ -51,65 +68,11 @@ export function getRemovePrefixFromKey(tag: string): string { } export function getOperatorValue(op: string): string { - switch (op) { - case 'IN': - return 'in'; - case 'NOT_IN': - return 'nin'; - case OPERATORS.REGEX: - return 'regex'; - case OPERATORS.HAS: - return 'has'; - case OPERATORS.NHAS: - return 'nhas'; - case OPERATORS.NREGEX: - return 'nregex'; - case 'LIKE': - return 'like'; - case 'NOT_LIKE': - return 'nlike'; - case 'EXISTS': - return 'exists'; - case 'NOT_EXISTS': - return 'nexists'; - case 'CONTAINS': - return 'contains'; - case 'NOT_CONTAINS': - return 'ncontains'; - default: - return op; - } + return op.toLocaleLowerCase(); } export function getOperatorFromValue(op: string): string { - switch (op) { - case 'in': - return 'IN'; - case 'nin': - return 'NOT_IN'; - case 'like': - return 'LIKE'; - case 'regex': - return OPERATORS.REGEX; - case 'nregex': - return OPERATORS.NREGEX; - case 'nlike': - return 'NOT_LIKE'; - case 'exists': - return 'EXISTS'; - case 'nexists': - return 'NOT_EXISTS'; - case 'contains': - return 'CONTAINS'; - case 'ncontains': - return 'NOT_CONTAINS'; - case 'has': - return OPERATORS.HAS; - case 'nhas': - return OPERATORS.NHAS; - default: - return op; - } + return op.toLocaleLowerCase(); } export function replaceStringWithMaxLength( diff --git a/frontend/src/hooks/queryBuilder/useAutoComplete.ts b/frontend/src/hooks/queryBuilder/useAutoComplete.ts index dad262757a..9562f5e4d7 100644 --- a/frontend/src/hooks/queryBuilder/useAutoComplete.ts +++ b/frontend/src/hooks/queryBuilder/useAutoComplete.ts @@ -1,8 +1,8 @@ import { + getMatchRegex, getRemovePrefixFromKey, getTagToken, replaceStringWithMaxLength, - tagRegexp, } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; import { Option } from 'container/QueryBuilder/type'; import { parse } from 'papaparse'; @@ -33,7 +33,7 @@ export const useAutoComplete = ( searchKey, ); - const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys); + const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue); const handleSearch = (value: string): void => { const prefixFreeValue = getRemovePrefixFromKey(getTagToken(value).tagKey); @@ -58,7 +58,7 @@ export const useAutoComplete = ( (value: string): void => { if (isMulti) { setSearchValue((prev: string) => { - const matches = prev?.matchAll(tagRegexp); + const matches = prev?.matchAll(getMatchRegex(prev)); const [match] = matches ? Array.from(matches) : []; const [, , , matchTagValue] = match; const data = parse(matchTagValue).data.flat(); diff --git a/frontend/src/hooks/queryBuilder/useOperatorType.ts b/frontend/src/hooks/queryBuilder/useOperatorType.ts index 94de55df92..ff3a4ad115 100644 --- a/frontend/src/hooks/queryBuilder/useOperatorType.ts +++ b/frontend/src/hooks/queryBuilder/useOperatorType.ts @@ -8,23 +8,35 @@ export type OperatorType = const operatorTypeMapper: Record = { [OPERATORS.IN]: 'MULTIPLY_VALUE', + [OPERATORS.in]: 'MULTIPLY_VALUE', [OPERATORS.NIN]: 'MULTIPLY_VALUE', + [OPERATORS.not_in]: 'MULTIPLY_VALUE', [OPERATORS.EXISTS]: 'NON_VALUE', + [OPERATORS.exists]: 'NON_VALUE', [OPERATORS.NOT_EXISTS]: 'NON_VALUE', + [OPERATORS.not_exists]: 'NON_VALUE', [OPERATORS['<=']]: 'SINGLE_VALUE', [OPERATORS['<']]: 'SINGLE_VALUE', [OPERATORS['>=']]: 'SINGLE_VALUE', [OPERATORS['>']]: 'SINGLE_VALUE', [OPERATORS.LIKE]: 'SINGLE_VALUE', + [OPERATORS.like]: 'SINGLE_VALUE', [OPERATORS.NLIKE]: 'SINGLE_VALUE', + [OPERATORS.not_like]: 'SINGLE_VALUE', [OPERATORS.REGEX]: 'SINGLE_VALUE', + [OPERATORS.regex]: 'SINGLE_VALUE', [OPERATORS.NREGEX]: 'SINGLE_VALUE', + [OPERATORS.nregex]: 'SINGLE_VALUE', [OPERATORS.CONTAINS]: 'SINGLE_VALUE', + [OPERATORS.contains]: 'SINGLE_VALUE', [OPERATORS.NOT_CONTAINS]: 'SINGLE_VALUE', + [OPERATORS.not_contains]: 'SINGLE_VALUE', [OPERATORS['=']]: 'SINGLE_VALUE', [OPERATORS['!=']]: 'SINGLE_VALUE', [OPERATORS.HAS]: 'SINGLE_VALUE', + [OPERATORS.has]: 'SINGLE_VALUE', [OPERATORS.NHAS]: 'SINGLE_VALUE', + [OPERATORS.nhas]: 'SINGLE_VALUE', }; export const useOperatorType = (operator: string): OperatorType => diff --git a/frontend/src/hooks/queryBuilder/useOptions.ts b/frontend/src/hooks/queryBuilder/useOptions.ts index 82dc2c1e24..548ea2de0b 100644 --- a/frontend/src/hooks/queryBuilder/useOptions.ts +++ b/frontend/src/hooks/queryBuilder/useOptions.ts @@ -81,8 +81,8 @@ export const useOptions = ( const getKeyOperatorOptions = useCallback( (key: string) => { const operatorsOptions = operators?.map((operator) => ({ - value: `${key} ${operator} `, - label: `${key} ${operator} `, + value: `${key} ${operator.toLowerCase()} `, + label: `${key} ${operator.toLowerCase()} `, })); if (whereClauseConfig) { return [ @@ -148,26 +148,28 @@ export const useOptions = ( return useMemo( () => - ( - options.filter( + options + .filter( (option, index, self) => index === self.findIndex( (o) => o.label === option.label && o.value === option.value, // to remove duplicate & empty options from list ) && option.value !== '', - ) || [] - ).map((option) => { - const { tagValue } = getTagToken(searchValue); - if (isMulti) { - return { - ...option, - selected: tagValue - .filter((i) => i.trim().replace(/^\s+/, '') === option.value) - .includes(option.value), - }; - } - return option; - }), + ) + .map((option) => { + const { tagValue } = getTagToken(searchValue); + if (isMulti) { + return { + ...option, + selected: Array.isArray(tagValue) + ? tagValue + ?.filter((i) => i.trim().replace(/^\s+/, '') === option.value) + ?.includes(option.value) + : String(tagValue).includes(option.value), + }; + } + return option; + }), [isMulti, options, searchValue], ); }; diff --git a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts index 2da205b349..afaaa8ccab 100644 --- a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts +++ b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts @@ -1,32 +1,24 @@ -import { - getRemovePrefixFromKey, - getTagToken, -} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; -import { useMemo } from 'react'; -import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { getTagToken } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; +import { useMemo, useRef } from 'react'; type ICurrentKeyAndOperator = [string, string, string[]]; export const useSetCurrentKeyAndOperator = ( value: string, - keys: BaseAutocompleteData[], ): ICurrentKeyAndOperator => { - const [key, operator, result] = useMemo(() => { - let key = ''; - let operator = ''; + const keyRef = useRef(''); + const operatorRef = useRef(''); + + const result = useMemo(() => { let result: string[] = []; const { tagKey, tagOperator, tagValue } = getTagToken(value); - const isSuggestKey = keys?.some( - (el) => el?.key === getRemovePrefixFromKey(tagKey), - ); - if (isSuggestKey || keys.length === 0) { - key = tagKey || ''; - operator = tagOperator || ''; - result = tagValue || []; - } - return [key, operator, result]; - }, [value, keys]); + keyRef.current = tagKey || ''; + operatorRef.current = tagOperator || ''; + result = tagValue || []; - return [key, operator, result]; + return result; + }, [value]); + + return [keyRef.current, operatorRef.current, result]; }; diff --git a/frontend/src/hooks/queryBuilder/useTag.ts b/frontend/src/hooks/queryBuilder/useTag.ts index 268a01e0c6..45a9417403 100644 --- a/frontend/src/hooks/queryBuilder/useTag.ts +++ b/frontend/src/hooks/queryBuilder/useTag.ts @@ -74,6 +74,7 @@ export const useTag = ( const handleAddTag = useCallback( (value: string): void => { const { tagKey } = getTagToken(value); + const [key, id] = tagKey.split('-'); if (id === 'custom') { From e12aef136a44e270bf4db410ee3b38906ec5ba97 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Sat, 7 Oct 2023 21:23:53 +0545 Subject: [PATCH 13/34] perf(query-service): :hammer: improve backend build time (#3658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(query-service): :hammer: improve backend build time * chore(query-service): 🔧 address comments on image build time --------- Signed-off-by: Prashant Shahi --- Makefile | 69 ++++++++++++------- .../docker-compose-local.yaml | 6 +- ee/query-service/Dockerfile | 37 +++------- pkg/query-service/Dockerfile | 38 ++-------- 4 files changed, 63 insertions(+), 87 deletions(-) diff --git a/Makefile b/Makefile index 0d46bfa161..01ac0e33ab 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ BUILD_HASH ?= $(shell git rev-parse --short HEAD) BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) DEV_LICENSE_SIGNOZ_IO ?= https://staging-license.signoz.io/api/v1 +DEV_BUILD ?= "" # set to any non-empty value to enable dev build # Internal variables or constants. FRONTEND_DIRECTORY ?= frontend @@ -15,15 +16,15 @@ QUERY_SERVICE_DIRECTORY ?= pkg/query-service EE_QUERY_SERVICE_DIRECTORY ?= ee/query-service STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup -LOCAL_GOOS ?= $(shell go env GOOS) -LOCAL_GOARCH ?= $(shell go env GOARCH) + +GOOS ?= $(shell go env GOOS) +GOARCH ?= $(shell go env GOARCH) +GOPATH ?= $(shell go env GOPATH) REPONAME ?= signoz DOCKER_TAG ?= $(subst v,,$(BUILD_VERSION)) - FRONTEND_DOCKER_IMAGE ?= frontend QUERY_SERVICE_DOCKER_IMAGE ?= query-service -DEV_BUILD ?= "" # Build-time Go variables PACKAGE?=go.signoz.io/signoz @@ -69,49 +70,71 @@ build-push-frontend: build-frontend-static docker buildx build --file Dockerfile --progress plain --push --platform linux/arm64,linux/amd64 \ --tag $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) . +# Steps to build static binary of query service +.PHONY: build-query-service-static +build-query-service-static: + @echo "------------------" + @echo "--> Building query-service static binary" + @echo "------------------" + @if [ $(DEV_BUILD) != "" ]; then \ + cd $(QUERY_SERVICE_DIRECTORY) && \ + CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \ + -ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS} ${DEV_LD_FLAGS}"; \ + else \ + cd $(QUERY_SERVICE_DIRECTORY) && \ + CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \ + -ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS}"; \ + fi + +.PHONY: build-query-service-static-amd64 +build-query-service-static-amd64: + make GOARCH=amd64 build-query-service-static + +.PHONY: build-query-service-static-arm64 +build-query-service-static-arm64: + make CC=aarch64-linux-gnu-gcc GOARCH=arm64 build-query-service-static + +# Steps to build static binary of query service for all platforms +.PHONY: build-query-service-static-all +build-query-service-static-all: build-query-service-static-amd64 build-query-service-static-arm64 + # Steps to build and push docker image of query service -.PHONY: build-query-service-amd64 build-push-query-service +.PHONY: build-query-service-amd64 build-push-query-service # Step to build docker image of query service in amd64 (used in build pipeline) -build-query-service-amd64: +build-query-service-amd64: build-query-service-static-amd64 @echo "------------------" @echo "--> Building query-service docker image for amd64" @echo "------------------" @docker build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile \ - -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \ - --build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" . + --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \ + --build-arg TARGETPLATFORM="linux/amd64" . # Step to build and push docker image of query in amd64 and arm64 (used in push pipeline) -build-push-query-service: +build-push-query-service: build-query-service-static-all @echo "------------------" @echo "--> Building and pushing query-service docker image" @echo "------------------" @docker buildx build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile --progress plain \ - --push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS="$(LD_FLAGS)" \ + --push --platform linux/arm64,linux/amd64 \ --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) . # Step to build EE docker image of query service in amd64 (used in build pipeline) -build-ee-query-service-amd64: +build-ee-query-service-amd64: build-query-service-static-amd64 @echo "------------------" @echo "--> Building query-service docker image for amd64" @echo "------------------" - @if [ $(DEV_BUILD) != "" ]; then \ - docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \ - -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \ - --build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="${LD_FLAGS} ${DEV_LD_FLAGS}" .; \ - else \ - docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \ - -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \ - --build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .; \ - fi + @docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \ + --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \ + --build-arg TARGETPLATFORM="linux/amd64" . # Step to build and push EE docker image of query in amd64 and arm64 (used in push pipeline) -build-push-ee-query-service: +build-push-ee-query-service: build-query-service-static-all @echo "------------------" @echo "--> Building and pushing query-service docker image" @echo "------------------" @docker buildx build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \ --progress plain --push --platform linux/arm64,linux/amd64 \ - --build-arg LD_FLAGS="$(LD_FLAGS)" --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) . + --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) . dev-setup: mkdir -p /var/lib/signoz @@ -122,7 +145,7 @@ dev-setup: @echo "------------------" run-local: - @LOCAL_GOOS=$(LOCAL_GOOS) LOCAL_GOARCH=$(LOCAL_GOARCH) docker-compose -f \ + @docker-compose -f \ $(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-local.yaml \ up --build -d diff --git a/deploy/docker/clickhouse-setup/docker-compose-local.yaml b/deploy/docker/clickhouse-setup/docker-compose-local.yaml index 2c7b9a5c46..78aa72ff75 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-local.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-local.yaml @@ -8,7 +8,7 @@ services: dockerfile: "./Dockerfile" args: LDFLAGS: "" - TARGETPLATFORM: "${LOCAL_GOOS}/${LOCAL_GOARCH}" + TARGETPLATFORM: "${GOOS}/${GOARCH}" container_name: signoz-query-service environment: - ClickHouseUrl=tcp://clickhouse:9000 @@ -52,8 +52,8 @@ services: context: "../../../frontend" dockerfile: "./Dockerfile" args: - TARGETOS: "${LOCAL_GOOS}" - TARGETPLATFORM: "${LOCAL_GOARCH}" + TARGETOS: "${GOOS}" + TARGETPLATFORM: "${GOARCH}" container_name: signoz-frontend environment: - FRONTEND_API_ENDPOINT=http://query-service:8080 diff --git a/ee/query-service/Dockerfile b/ee/query-service/Dockerfile index 258b0869f7..09e2701aa5 100644 --- a/ee/query-service/Dockerfile +++ b/ee/query-service/Dockerfile @@ -1,43 +1,23 @@ -FROM golang:1.21-bookworm AS builder - -# LD_FLAGS is passed as argument from Makefile. It will be empty, if no argument passed -ARG LD_FLAGS -ARG TARGETPLATFORM - -ENV CGO_ENABLED=1 -ENV GOPATH=/go - -RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \ - export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) - -# Prepare and enter src directory -WORKDIR /go/src/github.com/signoz/signoz - -# Add the sources and proceed with build -ADD . . -RUN cd ee/query-service \ - && go build -tags timetzdata -a -o ./bin/query-service \ - -ldflags "-linkmode external -extldflags '-static' -s -w $LD_FLAGS" \ - && chmod +x ./bin/query-service - - # use a minimal alpine image -FROM alpine:3.16.7 +FROM alpine:3.17 # Add Maintainer Info LABEL maintainer="signoz" +# define arguments that can be passed during build time +ARG TARGETOS TARGETARCH + # add ca-certificates in case you need them RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* # set working directory WORKDIR /root -# copy the binary from builder -COPY --from=builder /go/src/github.com/signoz/signoz/ee/query-service/bin/query-service . +# copy the query-service binary +COPY ee/pkg/query-service/bin/query-service-${TARGETOS}-${TARGETARCH} /root/query-service # copy prometheus YAML config -COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml +COPY ee/pkg/query-service/config/prometheus.yml /root/config/prometheus.yml # Make query-service executable for non-root users RUN chmod 755 /root /root/query-service @@ -45,7 +25,6 @@ RUN chmod 755 /root /root/query-service # run the binary ENTRYPOINT ["./query-service"] -CMD ["-config", "../config/prometheus.yml"] -# CMD ["./query-service -config /root/config/prometheus.yml"] +CMD ["-config", "/root/config/prometheus.yml"] EXPOSE 8080 diff --git a/pkg/query-service/Dockerfile b/pkg/query-service/Dockerfile index e616ad5d12..9d62c5cc62 100644 --- a/pkg/query-service/Dockerfile +++ b/pkg/query-service/Dockerfile @@ -1,45 +1,20 @@ -FROM golang:1.21-bookworm AS builder - -# LD_FLAGS is passed as argument from Makefile. It will be empty, if no argument passed -ARG LD_FLAGS -ARG TARGETPLATFORM - -ENV CGO_ENABLED=1 -ENV GOPATH=/go - -RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \ - export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) - -# Prepare and enter src directory -WORKDIR /go/src/github.com/signoz/signoz - -# Cache dependencies -ADD go.mod . -ADD go.sum . -RUN go mod download -x - -# Add the sources and proceed with build -ADD . . -RUN cd pkg/query-service \ - && go build -tags timetzdata -a -o ./bin/query-service \ - -ldflags "-linkmode external -extldflags '-static' -s -w $LD_FLAGS" \ - && chmod +x ./bin/query-service - - # use a minimal alpine image -FROM alpine:3.16.7 +FROM alpine:3.17 # Add Maintainer Info LABEL maintainer="signoz" +# define arguments that can be passed during build time +ARG TARGETOS TARGETARCH + # add ca-certificates in case you need them RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* # set working directory WORKDIR /root -# copy the binary from builder -COPY --from=builder /go/src/github.com/signoz/signoz/pkg/query-service/bin/query-service . +# copy the query-service binary +COPY pkg/query-service/bin/query-service-${TARGETOS}-${TARGETARCH} /root/query-service # copy prometheus YAML config COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml @@ -51,6 +26,5 @@ RUN chmod 755 /root /root/query-service ENTRYPOINT ["./query-service"] CMD ["-config", "/root/config/prometheus.yml"] -# CMD ["./query-service -config /root/config/prometheus.yml"] EXPOSE 8080 From d7d4000240695c35a8d8a3b335b0eecd0796c823 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Sun, 8 Oct 2023 00:44:39 +0545 Subject: [PATCH 14/34] =?UTF-8?q?chore(query-service):=20=F0=9F=94=A7=20up?= =?UTF-8?q?date=20workflows=20and=20build=20files=20as=20per=20optimizatio?= =?UTF-8?q?n=20changes=20(#3686)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- .github/workflows/push.yaml | 10 ++++++++++ .github/workflows/staging-deployment.yaml | 1 + .github/workflows/testing-deployment.yaml | 1 + Makefile | 12 ++++-------- ee/query-service/Dockerfile | 4 ++-- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 98ee1e0fc4..a4c03d014c 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -42,6 +42,11 @@ jobs: else echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}-oss" >> $GITHUB_ENV fi + - name: Install cross-compilation tools + run: | + set -ex + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools - name: Build and push docker image run: make build-push-query-service @@ -77,6 +82,11 @@ jobs: else echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV fi + - name: Install cross-compilation tools + run: | + set -ex + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools - name: Build and push docker image run: make build-push-ee-query-service diff --git a/.github/workflows/staging-deployment.yaml b/.github/workflows/staging-deployment.yaml index 6de51f4733..21ea7a3c75 100644 --- a/.github/workflows/staging-deployment.yaml +++ b/.github/workflows/staging-deployment.yaml @@ -26,6 +26,7 @@ jobs: echo "GITHUB_SHA: ${GITHUB_SHA}" export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it export OTELCOL_TAG="main" + export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work docker system prune --force docker pull signoz/signoz-otel-collector:main cd ~/signoz diff --git a/.github/workflows/testing-deployment.yaml b/.github/workflows/testing-deployment.yaml index d65a4e8bbc..799222ee3e 100644 --- a/.github/workflows/testing-deployment.yaml +++ b/.github/workflows/testing-deployment.yaml @@ -26,6 +26,7 @@ jobs: echo "GITHUB_SHA: ${GITHUB_SHA}" export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it export DEV_BUILD="1" + export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work docker system prune --force cd ~/signoz git status diff --git a/Makefile b/Makefile index 01ac0e33ab..8e62b6cb5c 100644 --- a/Makefile +++ b/Makefile @@ -119,22 +119,18 @@ build-push-query-service: build-query-service-static-all --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) . # Step to build EE docker image of query service in amd64 (used in build pipeline) -build-ee-query-service-amd64: build-query-service-static-amd64 +build-ee-query-service-amd64: @echo "------------------" @echo "--> Building query-service docker image for amd64" @echo "------------------" - @docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \ - --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \ - --build-arg TARGETPLATFORM="linux/amd64" . + make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-query-service-amd64 # Step to build and push EE docker image of query in amd64 and arm64 (used in push pipeline) -build-push-ee-query-service: build-query-service-static-all +build-push-ee-query-service: build-ee-query-service-static-all @echo "------------------" @echo "--> Building and pushing query-service docker image" @echo "------------------" - @docker buildx build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \ - --progress plain --push --platform linux/arm64,linux/amd64 \ - --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) . + make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-push-query-service dev-setup: mkdir -p /var/lib/signoz diff --git a/ee/query-service/Dockerfile b/ee/query-service/Dockerfile index 09e2701aa5..46b2186ec4 100644 --- a/ee/query-service/Dockerfile +++ b/ee/query-service/Dockerfile @@ -14,10 +14,10 @@ RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* WORKDIR /root # copy the query-service binary -COPY ee/pkg/query-service/bin/query-service-${TARGETOS}-${TARGETARCH} /root/query-service +COPY ee/query-service/bin/query-service-${TARGETOS}-${TARGETARCH} /root/query-service # copy prometheus YAML config -COPY ee/pkg/query-service/config/prometheus.yml /root/config/prometheus.yml +COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml # Make query-service executable for non-root users RUN chmod 755 /root /root/query-service From 9e91375632c7b61564bd59e3ce2bbd3a4c39edde Mon Sep 17 00:00:00 2001 From: Raj Kamal Singh <1133322+rkssisodiya@users.noreply.github.com> Date: Sun, 8 Oct 2023 14:49:16 +0530 Subject: [PATCH 15/34] Logs pipeline editor - filter preview (#3683) * feat: get started with Logs Filter Preview * chore: rename PipelineFilterPreview -> PipelineFilterSummary * chore: initial styles for pipeline filter preview * feat: wire up logs fetching for pipeline filter preview * feat: show empty preview if filter is empty * feat: get logs preview table display started * feat: use simple div + style based display for logs preview * feat: log preview item expand action * feat: move preview below filter and make filter last i/p in pipeline form * feat: add duration selector for logs filter preview * feat: add matched logs count to pipeline filter preview * chore: reorganize preview logs list into its own file * chore: cleanup * chore: revert type export from useGetQueryRange.ts * chore: get all tests passing * chore: address review comments: import cloneDeep directly * chore: address review comments: avoid inline handler func, return JSX.Element | null * chore: address review comments: move preview interval selector helper into its own folder * chore: address feedback: fix cloneDeep import * chore: address feedback: avoid inline handler and remove eslint supression --- .../index.tsx} | 35 +++++++-- .../FormFields/FilterInput/styles.scss | 3 + .../Preview/LogsFilterPreview/index.tsx | 43 +++++++++++ .../Preview/LogsFilterPreview/styles.scss | 18 +++++ .../Preview/components/LogsList/index.tsx | 52 +++++++++++++ .../Preview/components/LogsList/styles.scss | 46 +++++++++++ .../components/LogsCountInInterval/index.tsx | 55 ++++++++++++++ .../LogsCountInInterval/styles.scss | 3 + .../PreviewIntervalSelector/index.tsx | 45 +++++++++++ .../PreviewIntervalSelector/styles.scss | 4 + .../Preview/components/SampleLogs/index.tsx | 76 +++++++++++++++++++ .../Preview/components/SampleLogs/styles.scss | 7 ++ .../index.tsx | 8 +- .../styles.scss | 0 .../TableComponents/index.tsx | 4 +- .../PipelinePage/PipelineListsView/config.ts | 16 ++-- .../TopNav/DateTimeSelection/config.ts | 6 +- 17 files changed, 398 insertions(+), 23 deletions(-) rename frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/{FilterInput.tsx => FilterInput/index.tsx} (64%) create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput/styles.scss create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/LogsFilterPreview/index.tsx create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/LogsFilterPreview/styles.scss create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/components/LogsList/index.tsx create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/components/LogsList/styles.scss create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/components/LogsCountInInterval/index.tsx create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/components/LogsCountInInterval/styles.scss create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/index.tsx create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/styles.scss create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/components/SampleLogs/index.tsx create mode 100644 frontend/src/container/PipelinePage/PipelineListsView/Preview/components/SampleLogs/styles.scss rename frontend/src/container/PipelinePage/PipelineListsView/TableComponents/{PipelineFilterPreview => PipelineFilterSummary}/index.tsx (72%) rename frontend/src/container/PipelinePage/PipelineListsView/TableComponents/{PipelineFilterPreview => PipelineFilterSummary}/styles.scss (100%) diff --git a/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput.tsx b/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput/index.tsx similarity index 64% rename from frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput.tsx rename to frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput/index.tsx index ef6b93f11c..94466aa197 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput.tsx +++ b/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput/index.tsx @@ -1,3 +1,5 @@ +import './styles.scss'; + import { Form } from 'antd'; import { initialQueryBuilderFormValuesMap } from 'constants/queryBuilder'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; @@ -5,9 +7,10 @@ import isEqual from 'lodash-es/isEqual'; import { useTranslation } from 'react-i18next'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; -import { ProcessorFormField } from '../../AddNewProcessor/config'; -import { formValidationRules } from '../../config'; -import { FormLabelStyle } from '../styles'; +import { ProcessorFormField } from '../../../AddNewProcessor/config'; +import { formValidationRules } from '../../../config'; +import LogsFilterPreview from '../../../Preview/LogsFilterPreview'; +import { FormLabelStyle } from '../../styles'; function TagFilterInput({ value, @@ -41,9 +44,27 @@ interface TagFilterInputProps { placeholder: string; } +function TagFilterInputWithLogsResultPreview({ + value, + onChange, + placeholder, +}: TagFilterInputProps): JSX.Element { + return ( + <> + +
+ +
+ + ); +} + function FilterInput({ fieldData }: FilterInputProps): JSX.Element { const { t } = useTranslation('pipeline'); - return ( - {/* Antd form will supply value and onChange to here. + {/* Antd form will supply value and onChange here. // @ts-ignore */} - + ); } diff --git a/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput/styles.scss b/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput/styles.scss new file mode 100644 index 0000000000..8508da2799 --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/AddNewPipeline/FormFields/FilterInput/styles.scss @@ -0,0 +1,3 @@ +.pipeline-filter-input-preview-container { + margin-top: 1rem; +} diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/LogsFilterPreview/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/Preview/LogsFilterPreview/index.tsx new file mode 100644 index 0000000000..c4bd7be438 --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/LogsFilterPreview/index.tsx @@ -0,0 +1,43 @@ +import './styles.scss'; + +import { RelativeDurationOptions } from 'container/TopNav/DateTimeSelection/config'; +import { useState } from 'react'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +import PreviewIntervalSelector from '../components/PreviewIntervalSelector'; +import SampleLogs from '../components/SampleLogs'; + +function LogsFilterPreview({ filter }: LogsFilterPreviewProps): JSX.Element { + const last1HourInterval = RelativeDurationOptions[3].value; + const [previewTimeInterval, setPreviewTimeInterval] = useState( + last1HourInterval, + ); + + const isEmptyFilter = (filter?.items?.length || 0) < 1; + + return ( +
+
+
Filtered Logs Preview
+ +
+
+ {isEmptyFilter ? ( +
Please select a filter
+ ) : ( + + )} +
+
+ ); +} + +interface LogsFilterPreviewProps { + filter: TagFilter; +} + +export default LogsFilterPreview; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/LogsFilterPreview/styles.scss b/frontend/src/container/PipelinePage/PipelineListsView/Preview/LogsFilterPreview/styles.scss new file mode 100644 index 0000000000..725a12e59a --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/LogsFilterPreview/styles.scss @@ -0,0 +1,18 @@ +.logs-filter-preview-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 8px; +} + +.logs-filter-preview-content { + position: relative; + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + height: 8rem; + overflow: hidden; + border: 1px solid rgba(253, 253, 253, 0.12); +} diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/LogsList/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/LogsList/index.tsx new file mode 100644 index 0000000000..122d5e2b3a --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/LogsList/index.tsx @@ -0,0 +1,52 @@ +import './styles.scss'; + +import { ExpandAltOutlined } from '@ant-design/icons'; +import LogDetail from 'components/LogDetail'; +import dayjs from 'dayjs'; +import { useActiveLog } from 'hooks/logs/useActiveLog'; +import { ILog } from 'types/api/logs/log'; + +function LogsList({ logs }: LogsListProps): JSX.Element { + const { + activeLog, + onSetActiveLog, + onClearActiveLog, + onAddToQuery, + } = useActiveLog(); + + const makeLogDetailsHandler = (log: ILog) => (): void => onSetActiveLog(log); + + return ( +
+ {logs.map((log) => ( +
+
+ {dayjs(String(log.timestamp)).format('MMM DD HH:mm:ss.SSS')} +
+
{log.body}
+
+ +
+
+ ))} + +
+ ); +} + +interface LogsListProps { + logs: ILog[]; +} + +export default LogsList; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/LogsList/styles.scss b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/LogsList/styles.scss new file mode 100644 index 0000000000..80cec9ef28 --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/LogsList/styles.scss @@ -0,0 +1,46 @@ +.logs-preview-list-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: stretch; + + box-sizing: border-box; + padding: 0.25rem 0.5rem; +} + +.logs-preview-list-item { + width: 100%; + position: relative; + + display: flex; + justify-content: space-between; + align-items: center; + + flex-grow: 1; +} + +.logs-preview-list-item:not(:first-child) { + border-top: 1px solid rgba(253, 253, 253, 0.12); +} + +.logs-preview-list-item-timestamp { + margin-right: 0.75rem; + white-space: nowrap; +} + +.logs-preview-list-item-body { + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.logs-preview-list-item-expand{ + margin-left: 0.75rem; + color: #1677ff; + padding: 0.25rem 0.375rem; + cursor: pointer; + font-size: 12px; +} diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/components/LogsCountInInterval/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/components/LogsCountInInterval/index.tsx new file mode 100644 index 0000000000..63ee3ff3c0 --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/components/LogsCountInInterval/index.tsx @@ -0,0 +1,55 @@ +import './styles.scss'; + +import { + initialFilters, + initialQueriesMap, + PANEL_TYPES, +} from 'constants/queryBuilder'; +import { Time } from 'container/TopNav/DateTimeSelection/config'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import cloneDeep from 'lodash-es/cloneDeep'; +import { useMemo } from 'react'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; +import { LogsAggregatorOperator } from 'types/common/queryBuilder'; + +function LogsCountInInterval({ + filter, + timeInterval, +}: LogsCountInIntervalProps): JSX.Element | null { + const query = useMemo(() => { + const q = cloneDeep(initialQueriesMap.logs); + q.builder.queryData[0] = { + ...q.builder.queryData[0], + filters: filter || initialFilters, + aggregateOperator: LogsAggregatorOperator.COUNT, + }; + return q; + }, [filter]); + + const result = useGetQueryRange({ + graphType: PANEL_TYPES.TABLE, + query, + selectedTime: 'GLOBAL_TIME', + globalSelectedInterval: timeInterval, + }); + + if (!result.isFetched) { + return null; + } + + const count = + result?.data?.payload?.data?.newResult?.data?.result?.[0]?.series?.[0] + ?.values?.[0]?.value; + return ( +
+ {count} matches in +
+ ); +} + +interface LogsCountInIntervalProps { + filter: TagFilter; + timeInterval: Time; +} + +export default LogsCountInInterval; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/components/LogsCountInInterval/styles.scss b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/components/LogsCountInInterval/styles.scss new file mode 100644 index 0000000000..2074e176a6 --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/components/LogsCountInInterval/styles.scss @@ -0,0 +1,3 @@ +.logs-filter-preview-matched-logs-count { + margin-right: 0.5rem; +} diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/index.tsx new file mode 100644 index 0000000000..a40f1d0376 --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/index.tsx @@ -0,0 +1,45 @@ +import './styles.scss'; + +import { Select } from 'antd'; +import { + RelativeDurationOptions, + Time, +} from 'container/TopNav/DateTimeSelection/config'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; + +import LogsCountInInterval from './components/LogsCountInInterval'; + +function PreviewIntervalSelector({ + previewFilter, + value, + onChange, +}: PreviewIntervalSelectorProps): JSX.Element { + const onSelectInterval = (value: unknown): void => onChange(value as Time); + + const isEmptyFilter = (previewFilter?.items?.length || 0) < 1; + + return ( +
+ {!isEmptyFilter && ( + + )} +
+ +
+
+ ); +} + +interface PreviewIntervalSelectorProps { + value: Time; + onChange: (interval: Time) => void; + previewFilter: TagFilter; +} + +export default PreviewIntervalSelector; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/styles.scss b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/styles.scss new file mode 100644 index 0000000000..d2bc3347ea --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/PreviewIntervalSelector/styles.scss @@ -0,0 +1,4 @@ +.logs-filter-preview-time-interval-summary { + display: flex; + align-items: center; +} diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/SampleLogs/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/SampleLogs/index.tsx new file mode 100644 index 0000000000..82d7fba4fc --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/SampleLogs/index.tsx @@ -0,0 +1,76 @@ +import './styles.scss'; + +import { + initialFilters, + initialQueriesMap, + PANEL_TYPES, +} from 'constants/queryBuilder'; +import { Time } from 'container/TopNav/DateTimeSelection/config'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import cloneDeep from 'lodash-es/cloneDeep'; +import { useMemo } from 'react'; +import { ILog } from 'types/api/logs/log'; +import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; +import { LogsAggregatorOperator } from 'types/common/queryBuilder'; + +import LogsList from '../LogsList'; + +function SampleLogs({ filter, timeInterval }: SampleLogsProps): JSX.Element { + const sampleLogsQuery = useMemo(() => { + const q = cloneDeep(initialQueriesMap.logs); + q.builder.queryData[0] = { + ...q.builder.queryData[0], + filters: filter || initialFilters, + aggregateOperator: LogsAggregatorOperator.NOOP, + orderBy: [{ columnName: 'timestamp', order: 'desc' }], + limit: 5, + }; + return q; + }, [filter]); + + const sampleLogsResponse = useGetQueryRange({ + graphType: PANEL_TYPES.LIST, + query: sampleLogsQuery, + selectedTime: 'GLOBAL_TIME', + globalSelectedInterval: timeInterval, + }); + + if (sampleLogsResponse?.isError) { + return ( +
+ could not fetch logs for filter +
+ ); + } + + if (sampleLogsResponse?.isFetching) { + return
Loading...
; + } + + if ((filter?.items?.length || 0) < 1) { + return ( +
Please select a filter
+ ); + } + + const logsList = + sampleLogsResponse?.data?.payload?.data?.newResult?.data?.result[0]?.list || + []; + + if (logsList.length < 1) { + return
No logs found
; + } + + const logs: ILog[] = logsList.map((item) => ({ + ...item.data, + timestamp: item.timestamp, + })); + return ; +} + +interface SampleLogsProps { + filter: TagFilter; + timeInterval: Time; +} + +export default SampleLogs; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/SampleLogs/styles.scss b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/SampleLogs/styles.scss new file mode 100644 index 0000000000..815a8ce45f --- /dev/null +++ b/frontend/src/container/PipelinePage/PipelineListsView/Preview/components/SampleLogs/styles.scss @@ -0,0 +1,7 @@ +.sample-logs-notice-container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterSummary/index.tsx similarity index 72% rename from frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/index.tsx rename to frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterSummary/index.tsx index b33ed6c087..6217f168b0 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/index.tsx +++ b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterSummary/index.tsx @@ -3,9 +3,9 @@ import './styles.scss'; import { queryFilterTags } from 'hooks/queryBuilder/useTag'; import { PipelineData } from 'types/api/pipeline/def'; -function PipelineFilterPreview({ +function PipelineFilterSummary({ filter, -}: PipelineFilterPreviewProps): JSX.Element { +}: PipelineFilterSummaryProps): JSX.Element { return (
{queryFilterTags(filter).map((tag) => ( @@ -17,8 +17,8 @@ function PipelineFilterPreview({ ); } -interface PipelineFilterPreviewProps { +interface PipelineFilterSummaryProps { filter: PipelineData['filter']; } -export default PipelineFilterPreview; +export default PipelineFilterSummary; diff --git a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/styles.scss b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterSummary/styles.scss similarity index 100% rename from frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterPreview/styles.scss rename to frontend/src/container/PipelinePage/PipelineListsView/TableComponents/PipelineFilterSummary/styles.scss diff --git a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/index.tsx b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/index.tsx index 25ec206566..8fc4b5b6eb 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/index.tsx +++ b/frontend/src/container/PipelinePage/PipelineListsView/TableComponents/index.tsx @@ -4,7 +4,7 @@ import { PipelineData, ProcessorData } from 'types/api/pipeline/def'; import { PipelineIndexIcon } from '../AddNewProcessor/styles'; import { ColumnDataStyle, ListDataStyle, ProcessorIndexIcon } from '../styles'; -import PipelineFilterPreview from './PipelineFilterPreview'; +import PipelineFilterSummary from './PipelineFilterSummary'; const componentMap: ComponentMap = { orderId: ({ record }) => {record}, @@ -15,7 +15,7 @@ const componentMap: ComponentMap = { ), id: ({ record }) => {record}, name: ({ record }) => {record}, - filter: ({ record }) => , + filter: ({ record }) => , }; function TableComponents({ diff --git a/frontend/src/container/PipelinePage/PipelineListsView/config.ts b/frontend/src/container/PipelinePage/PipelineListsView/config.ts index baecbb8d14..6fc56c48b5 100644 --- a/frontend/src/container/PipelinePage/PipelineListsView/config.ts +++ b/frontend/src/container/PipelinePage/PipelineListsView/config.ts @@ -14,25 +14,25 @@ import NameInput from './AddNewPipeline/FormFields/NameInput'; export const pipelineFields = [ { id: 1, - fieldName: 'Filter', - placeholder: 'pipeline_filter_placeholder', - name: 'filter', - component: FilterInput, - }, - { - id: 2, fieldName: 'Name', placeholder: 'pipeline_name_placeholder', name: 'name', component: NameInput, }, { - id: 4, + id: 2, fieldName: 'Description', placeholder: 'pipeline_description_placeholder', name: 'description', component: DescriptionTextArea, }, + { + id: 3, + fieldName: 'Filter', + placeholder: 'pipeline_filter_placeholder', + name: 'filter', + component: FilterInput, + }, ]; export const tagInputStyle: React.CSSProperties = { diff --git a/frontend/src/container/TopNav/DateTimeSelection/config.ts b/frontend/src/container/TopNav/DateTimeSelection/config.ts index 2aaf3e1c19..38c6c06611 100644 --- a/frontend/src/container/TopNav/DateTimeSelection/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelection/config.ts @@ -41,7 +41,7 @@ export interface Option { label: string; } -export const ServiceMapOptions: Option[] = [ +export const RelativeDurationOptions: Option[] = [ { value: '5min', label: 'Last 5 min' }, { value: '15min', label: 'Last 15 min' }, { value: '30min', label: 'Last 30 min' }, @@ -53,7 +53,7 @@ export const ServiceMapOptions: Option[] = [ export const getDefaultOption = (route: string): Time => { if (route === ROUTES.SERVICE_MAP) { - return ServiceMapOptions[2].value; + return RelativeDurationOptions[2].value; } if (route === ROUTES.APPLICATION) { return Options[2].value; @@ -63,7 +63,7 @@ export const getDefaultOption = (route: string): Time => { export const getOptions = (routes: string): Option[] => { if (routes === ROUTES.SERVICE_MAP) { - return ServiceMapOptions; + return RelativeDurationOptions; } return Options; }; From b14f800fee4449edbb6c00e74c8576e633c13b20 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Sun, 8 Oct 2023 23:11:58 +0545 Subject: [PATCH 16/34] =?UTF-8?q?ci:=20=F0=9F=91=B7=20pin=20Go=20v1.21=20a?= =?UTF-8?q?nd=20bump=20up=20actions/*=20in=20GH=20build/push=20workflows?= =?UTF-8?q?=20(#3687)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: 👷 pin Go v1.21 in GH build/push workflows * chore: 💚 update actions/* to v4 --------- Signed-off-by: Prashant Shahi --- .github/workflows/build.yaml | 12 ++++++------ .github/workflows/codeql.yaml | 2 +- .github/workflows/commitlint.yml | 2 +- .github/workflows/create-issue-on-pr-merge.yml | 4 ++-- .github/workflows/dependency-review.yml | 2 +- .github/workflows/e2e-k3s.yaml | 2 +- .github/workflows/playwright.yaml | 4 ++-- .github/workflows/push.yaml | 16 ++++++++++++---- .github/workflows/sonar.yml | 2 +- Makefile | 2 +- 10 files changed, 28 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f5f5e5610a..1d8d4e7b70 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: cd frontend && yarn install - name: Run ESLint @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create .env file run: | echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env @@ -54,12 +54,12 @@ jobs: build-query-service: runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@v4 - name: Setup golang uses: actions/setup-go@v4 with: go-version: "1.21" - - name: Checkout code - uses: actions/checkout@v3 - name: Run tests shell: bash run: | @@ -72,12 +72,12 @@ jobs: build-ee-query-service: runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@v4 - name: Setup golang uses: actions/setup-go@v4 with: go-version: "1.21" - - name: Checkout code - uses: actions/checkout@v3 - name: Build EE query-service image shell: bash run: | diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 14a0c127aa..be02f3bb82 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml index b624a90b9f..3a38338cf0 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/commitlint.yml @@ -7,7 +7,7 @@ jobs: lint-commits: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@v5 diff --git a/.github/workflows/create-issue-on-pr-merge.yml b/.github/workflows/create-issue-on-pr-merge.yml index 2b0c849ffa..2a79618d12 100644 --- a/.github/workflows/create-issue-on-pr-merge.yml +++ b/.github/workflows/create-issue-on-pr-merge.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: signoz/gh-bot - name: Use Node v16 - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 16 - name: Setup Cache & Install Dependencies diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 053a8733dc..be454590f3 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' with: fail-on-severity: high diff --git a/.github/workflows/e2e-k3s.yaml b/.github/workflows/e2e-k3s.yaml index 71061bfc73..770a2f4df3 100644 --- a/.github/workflows/e2e-k3s.yaml +++ b/.github/workflows/e2e-k3s.yaml @@ -13,7 +13,7 @@ jobs: DOCKER_TAG: pull-${{ github.event.number }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build query-service image env: diff --git a/.github/workflows/playwright.yaml b/.github/workflows/playwright.yaml index d6c05dfd6f..9ad3ef4313 100644 --- a/.github/workflows/playwright.yaml +++ b/.github/workflows/playwright.yaml @@ -9,8 +9,8 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: "16.x" - name: Install dependencies diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index a4c03d014c..f8eb005883 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -14,7 +14,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Setup golang + uses: actions/setup-go@v4 + with: + go-version: "1.21" - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx @@ -54,7 +58,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Setup golang + uses: actions/setup-go@v4 + with: + go-version: "1.21" - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx @@ -94,7 +102,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies working-directory: frontend run: yarn install @@ -138,7 +146,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create .env file run: | echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 742768525f..8c62c12d1b 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Sonar analysis diff --git a/Makefile b/Makefile index 8e62b6cb5c..5213c4597a 100644 --- a/Makefile +++ b/Makefile @@ -126,7 +126,7 @@ build-ee-query-service-amd64: make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-query-service-amd64 # Step to build and push EE docker image of query in amd64 and arm64 (used in push pipeline) -build-push-ee-query-service: build-ee-query-service-static-all +build-push-ee-query-service: @echo "------------------" @echo "--> Building and pushing query-service docker image" @echo "------------------" From e7a5eb7b2228f9f561927420af156e6c5f2f896d Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Sun, 8 Oct 2023 23:21:17 +0530 Subject: [PATCH 17/34] feat: new dashboard page is updated (#3385) * feat: dashboard widget page is refactored * chore: key is updated * chore: delete widget is updated * chore: naming of the file is updated * feat: dashboard changes are updated and selected dashboard and dashboardId is added * chore: dashboard widget page is updated * feat: setlayout is updated * chore: selected dashboard is updated * chore: dashboard is updated * fix: feedback is updated * chore: comments are resolved * chore: empty widget id is updated * fix: variables is updated * chore: dashboard variable and name,description is now updated in hooks * chore: build is fixed * chore: loading experience is updated * chore: title is updated * fix: dashboard variables and other changes are updated * feat: dashboard reducer is removed * feat: widget header is updated * feat: widget header is updated * chore: dashboard is updated * chore: feedback is updated * fix: issues are fixed * chore: delete is updated * chore: warning message is updated * chore: warning message is updated * chore: widget graph component * feat: dashboard condition is updated * chore: getChartData is updated * chore: widget details page is updated * feat: tab sync is updated * chore: layout is updated * chore: labels is updated * chore: message is updated * chore: warining message is updated --------- Co-authored-by: Rajat Dabade Co-authored-by: Vishal Sharma --- frontend/public/locales/en-GB/dashboard.json | 9 +- frontend/public/locales/en/dashboard.json | 9 +- frontend/src/AppRoutes/index.tsx | 33 +- frontend/src/api/dashboard/delete.ts | 25 +- frontend/src/api/dashboard/get.ts | 27 +- frontend/src/constants/reactQueryKeys.ts | 2 + .../FormAlertRules/ChartPreview/index.tsx | 2 +- .../EmptyWidget/index.tsx | 0 .../EmptyWidget/styles.ts | 0 .../FullView/GraphManager.styles.scss | 0 .../GridCard/FullView/GraphManager.tsx | 136 +++++++ .../FullView/TableRender/CustomCheckBox.tsx | 15 +- .../FullView/TableRender/GetLabel.tsx | 0 .../TableRender/GraphManagerColumns.tsx} | 15 +- .../GridCard}/FullView/TableRender/Label.tsx | 0 .../GridCard}/FullView/contants.ts | 0 .../GridCard}/FullView/index.tsx | 57 ++- .../GridCard}/FullView/styles.ts | 0 .../GridCard}/FullView/types.ts | 37 +- .../GridCard}/FullView/utils.ts | 5 - .../GridCard}/Graph.test.tsx | 0 .../GridCard/WidgetGraphComponent.tsx | 277 +++++++++++++ .../GridCard}/__mock__/mockChartData.ts | 0 .../GridCard}/__mock__/mockLegendEntryData.ts | 0 .../GridCardLayout/GridCard/index.tsx | 133 ++++++ .../GridCard}/styles.ts | 0 .../GridCard}/types.ts | 23 +- .../GridCard}/utils.ts | 0 .../GridCardLayout/GridCardLayout.tsx | 131 ++++++ .../WidgetHeader/DisplayThreshold.tsx | 0 .../WidgetHeader/config.ts | 9 +- .../WidgetHeader/contants.ts | 0 .../WidgetHeader/index.tsx | 22 +- .../WidgetHeader/styles.ts | 0 .../WidgetHeader/types.ts | 0 .../WidgetHeader/utils.ts | 0 .../src/container/GridCardLayout/config.ts | 17 + .../src/container/GridCardLayout/index.tsx | 35 ++ .../styles.ts | 0 .../src/container/GridCardLayout/types.ts | 6 + .../Graph/FullView/GraphManager.tsx | 203 ---------- .../FullView/TableRender/GetCheckBox.tsx | 27 -- .../Graph/WidgetGraphComponent.tsx | 334 --------------- .../container/GridGraphLayout/Graph/index.tsx | 187 --------- .../container/GridGraphLayout/GraphLayout.tsx | 113 ------ .../src/container/GridGraphLayout/config.ts | 8 - .../src/container/GridGraphLayout/index.tsx | 383 ------------------ .../src/container/GridGraphLayout/utils.ts | 78 ---- .../ListOfDashboard/ImportJSON/index.tsx | 21 +- .../TableComponents/DeleteButton.tsx | 57 +-- .../src/container/ListOfDashboard/index.tsx | 49 +-- .../LiveLogs/LiveLogsContainer/index.tsx | 2 +- .../src/container/LogsExplorerChart/index.tsx | 2 +- .../MetricsApplication.factory.ts | 2 + .../MetricsApplication/Tabs/DBCall.tsx | 7 +- .../MetricsApplication/Tabs/External.tsx | 10 +- .../MetricsApplication/Tabs/Overview.tsx | 4 +- .../Tabs/Overview/ApDex/ApDexMetrics.tsx | 5 +- .../Tabs/Overview/ApDex/ApDexTraces.tsx | 3 +- .../Tabs/Overview/ServiceOverview.tsx | 4 +- .../Tabs/Overview/TopLevelOperations.tsx | 5 +- .../Tabs/Overview/TopOperationMetrics.tsx | 9 +- .../MetricsApplication/TopOperationsTable.tsx | 4 +- .../src/container/MetricsApplication/types.ts | 1 + .../NewDashboard/ComponentsSlider/index.tsx | 138 ++++--- .../DashboardSettings/General/index.tsx | 99 ++--- .../DashboardSettings/Variables/index.tsx | 86 ++-- .../NewDashboard/DashboardSettings/index.tsx | 10 +- .../DashboardVariablesSelection/index.tsx | 99 +++-- .../DescriptionOfDashboard/SettingsDrawer.tsx | 4 +- .../DescriptionOfDashboard/index.tsx | 34 +- .../NewDashboard/GridGraphs/index.tsx | 12 +- .../LeftContainer/QuerySection/index.tsx | 89 ++-- .../LeftContainer/WidgetGraph/WidgetGraph.tsx | 57 +-- .../LeftContainer/WidgetGraph/index.tsx | 15 +- frontend/src/container/NewWidget/index.tsx | 203 +++++----- .../src/container/ServiceApplication/types.ts | 2 +- .../src/container/ServiceApplication/utils.ts | 2 +- .../TimeSeriesView/TimeSeriesView.tsx | 2 +- .../hooks/dashboard/useDeleteDashboard.tsx | 15 + .../hooks/dashboard/useUpdateDashboard.tsx | 13 +- .../hooks/queryBuilder/useGetQueriesRange.ts | 8 +- .../hooks/queryBuilder/useGetQueryRange.ts | 6 +- .../queryBuilder/useGetWidgetQueryRange.ts | 7 +- frontend/src/hooks/useChartMutable.ts | 2 +- frontend/src/hooks/useTabFocus.tsx | 28 ++ .../getDashboardVariables.ts | 48 +-- .../dashboard/getQueryResults.ts | 5 +- .../dashboard/prepareQueryRangePayload.ts | 0 frontend/src/lib/getChartData.ts | 56 ++- .../queryBuilderMappers/mapQueryDataToApi.ts | 2 +- frontend/src/pages/DashboardWidget/index.tsx | 71 +--- frontend/src/pages/NewDashboard/index.tsx | 61 +-- .../src/providers/Dashboard/Dashboard.tsx | 231 +++++++++++ frontend/src/providers/Dashboard/types.ts | 18 + frontend/src/providers/Dashboard/util.ts | 22 + .../actions/dashboard/applySettingsToPanel.ts | 25 -- .../actions/dashboard/deleteDashboard.ts | 49 --- .../store/actions/dashboard/deleteQuery.ts | 17 - .../store/actions/dashboard/deleteWidget.ts | 70 ---- .../store/actions/dashboard/getDashboard.ts | 68 ---- frontend/src/store/actions/dashboard/index.ts | 6 - .../store/actions/dashboard/saveDashboard.ts | 174 -------- .../actions/dashboard/toggleAddWidget.ts | 17 - .../store/actions/dashboard/toggleEditMode.ts | 10 - .../actions/dashboard/updateDashboardTitle.ts | 55 --- .../store/actions/dashboard/updateQuery.ts | 21 - .../dashboard/updatedDashboardVariables.ts | 39 -- frontend/src/store/actions/index.ts | 1 - frontend/src/store/reducers/dashboard.ts | 331 --------------- frontend/src/store/reducers/index.ts | 2 - frontend/src/types/actions/dashboard.ts | 189 --------- frontend/src/types/actions/index.ts | 2 - frontend/src/types/api/dashboard/delete.ts | 4 + frontend/src/types/reducer/dashboards.ts | 10 - .../src/utils/dashboard/selectedDashboard.ts | 18 - 116 files changed, 1809 insertions(+), 3287 deletions(-) rename frontend/src/container/{GridGraphLayout => GridCardLayout}/EmptyWidget/index.tsx (100%) rename frontend/src/container/{GridGraphLayout => GridCardLayout}/EmptyWidget/styles.ts (100%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/GraphManager.styles.scss (100%) create mode 100644 frontend/src/container/GridCardLayout/GridCard/FullView/GraphManager.tsx rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/TableRender/CustomCheckBox.tsx (59%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/TableRender/GetLabel.tsx (100%) rename frontend/src/container/{GridGraphLayout/Graph/FullView/TableRender/GraphManagerColumns.ts => GridCardLayout/GridCard/FullView/TableRender/GraphManagerColumns.tsx} (87%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/TableRender/Label.tsx (100%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/contants.ts (100%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/index.tsx (80%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/styles.ts (100%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/types.ts (76%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/FullView/utils.ts (94%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/Graph.test.tsx (100%) create mode 100644 frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/__mock__/mockChartData.ts (100%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/__mock__/mockLegendEntryData.ts (100%) create mode 100644 frontend/src/container/GridCardLayout/GridCard/index.tsx rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/styles.ts (100%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/types.ts (67%) rename frontend/src/container/{GridGraphLayout/Graph => GridCardLayout/GridCard}/utils.ts (100%) create mode 100644 frontend/src/container/GridCardLayout/GridCardLayout.tsx rename frontend/src/container/{GridGraphLayout => GridCardLayout}/WidgetHeader/DisplayThreshold.tsx (100%) rename frontend/src/container/{GridGraphLayout => GridCardLayout}/WidgetHeader/config.ts (61%) rename frontend/src/container/{GridGraphLayout => GridCardLayout}/WidgetHeader/contants.ts (100%) rename frontend/src/container/{GridGraphLayout => GridCardLayout}/WidgetHeader/index.tsx (92%) rename frontend/src/container/{GridGraphLayout => GridCardLayout}/WidgetHeader/styles.ts (100%) rename frontend/src/container/{GridGraphLayout => GridCardLayout}/WidgetHeader/types.ts (100%) rename frontend/src/container/{GridGraphLayout => GridCardLayout}/WidgetHeader/utils.ts (100%) create mode 100644 frontend/src/container/GridCardLayout/config.ts create mode 100644 frontend/src/container/GridCardLayout/index.tsx rename frontend/src/container/{GridGraphLayout => GridCardLayout}/styles.ts (100%) create mode 100644 frontend/src/container/GridCardLayout/types.ts delete mode 100644 frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx delete mode 100644 frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GetCheckBox.tsx delete mode 100644 frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx delete mode 100644 frontend/src/container/GridGraphLayout/Graph/index.tsx delete mode 100644 frontend/src/container/GridGraphLayout/GraphLayout.tsx delete mode 100644 frontend/src/container/GridGraphLayout/config.ts delete mode 100644 frontend/src/container/GridGraphLayout/index.tsx delete mode 100644 frontend/src/container/GridGraphLayout/utils.ts create mode 100644 frontend/src/hooks/dashboard/useDeleteDashboard.tsx create mode 100644 frontend/src/hooks/useTabFocus.tsx rename frontend/src/{store/actions => lib}/dashboard/getQueryResults.ts (99%) rename frontend/src/{store/actions => lib}/dashboard/prepareQueryRangePayload.ts (100%) create mode 100644 frontend/src/providers/Dashboard/Dashboard.tsx create mode 100644 frontend/src/providers/Dashboard/types.ts create mode 100644 frontend/src/providers/Dashboard/util.ts delete mode 100644 frontend/src/store/actions/dashboard/applySettingsToPanel.ts delete mode 100644 frontend/src/store/actions/dashboard/deleteDashboard.ts delete mode 100644 frontend/src/store/actions/dashboard/deleteQuery.ts delete mode 100644 frontend/src/store/actions/dashboard/deleteWidget.ts delete mode 100644 frontend/src/store/actions/dashboard/getDashboard.ts delete mode 100644 frontend/src/store/actions/dashboard/index.ts delete mode 100644 frontend/src/store/actions/dashboard/saveDashboard.ts delete mode 100644 frontend/src/store/actions/dashboard/toggleAddWidget.ts delete mode 100644 frontend/src/store/actions/dashboard/toggleEditMode.ts delete mode 100644 frontend/src/store/actions/dashboard/updateDashboardTitle.ts delete mode 100644 frontend/src/store/actions/dashboard/updateQuery.ts delete mode 100644 frontend/src/store/actions/dashboard/updatedDashboardVariables.ts delete mode 100644 frontend/src/store/reducers/dashboard.ts delete mode 100644 frontend/src/types/actions/dashboard.ts delete mode 100644 frontend/src/types/reducer/dashboards.ts delete mode 100644 frontend/src/utils/dashboard/selectedDashboard.ts diff --git a/frontend/public/locales/en-GB/dashboard.json b/frontend/public/locales/en-GB/dashboard.json index b643f4727d..b69113483d 100644 --- a/frontend/public/locales/en-GB/dashboard.json +++ b/frontend/public/locales/en-GB/dashboard.json @@ -13,5 +13,12 @@ "import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file", "error_loading_json": "Error loading JSON file", "empty_json_not_allowed": "Empty JSON is not allowed", - "new_dashboard_title": "Sample Title" + "new_dashboard_title": "Sample Title", + "layout_saved_successfully": "Layout saved successfully", + "add_panel": "Add Panel", + "save_layout": "Save Layout", + "variable_updated_successfully": "Variable updated successfully", + "error_while_updating_variable": "Error while updating variable", + "dashboard_has_been_updated": "Dashboard has been updated", + "do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?" } diff --git a/frontend/public/locales/en/dashboard.json b/frontend/public/locales/en/dashboard.json index b643f4727d..b69113483d 100644 --- a/frontend/public/locales/en/dashboard.json +++ b/frontend/public/locales/en/dashboard.json @@ -13,5 +13,12 @@ "import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file", "error_loading_json": "Error loading JSON file", "empty_json_not_allowed": "Empty JSON is not allowed", - "new_dashboard_title": "Sample Title" + "new_dashboard_title": "Sample Title", + "layout_saved_successfully": "Layout saved successfully", + "add_panel": "Add Panel", + "save_layout": "Save Layout", + "variable_updated_successfully": "Variable updated successfully", + "error_while_updating_variable": "Error while updating variable", + "dashboard_has_been_updated": "Dashboard has been updated", + "do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?" } diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index 56ba19fdf4..c2a0db3da1 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -12,6 +12,7 @@ import useGetFeatureFlag from 'hooks/useGetFeatureFlag'; import { NotificationProvider } from 'hooks/useNotifications'; import { ResourceProvider } from 'hooks/useResourceAttribute'; import history from 'lib/history'; +import { DashboardProvider } from 'providers/Dashboard/Dashboard'; import { QueryBuilderProvider } from 'providers/QueryBuilder'; import { Suspense, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -110,22 +111,24 @@ function App(): JSX.Element { - - }> - - {routes.map(({ path, component, exact }) => ( - - ))} + + + }> + + {routes.map(({ path, component, exact }) => ( + + ))} - - - - + + + + + diff --git a/frontend/src/api/dashboard/delete.ts b/frontend/src/api/dashboard/delete.ts index 2f7c8f16b9..8faf711383 100644 --- a/frontend/src/api/dashboard/delete.ts +++ b/frontend/src/api/dashboard/delete.ts @@ -1,24 +1,9 @@ import axios from 'api'; -import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; -import { AxiosError } from 'axios'; -import { ErrorResponse, SuccessResponse } from 'types/api'; -import { Props } from 'types/api/dashboard/delete'; +import { PayloadProps, Props } from 'types/api/dashboard/delete'; -const deleteDashboard = async ( - props: Props, -): Promise | ErrorResponse> => { - try { - const response = await axios.delete(`/dashboards/${props.uuid}`); - - return { - statusCode: 200, - error: null, - message: response.data.status, - payload: response.data.data, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } -}; +const deleteDashboard = (props: Props): Promise => + axios + .delete(`/dashboards/${props.uuid}`) + .then((response) => response.data); export default deleteDashboard; diff --git a/frontend/src/api/dashboard/get.ts b/frontend/src/api/dashboard/get.ts index 6c9c953e7d..9b10f6467d 100644 --- a/frontend/src/api/dashboard/get.ts +++ b/frontend/src/api/dashboard/get.ts @@ -1,24 +1,11 @@ import axios from 'api'; -import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; -import { AxiosError } from 'axios'; -import { ErrorResponse, SuccessResponse } from 'types/api'; -import { PayloadProps, Props } from 'types/api/dashboard/get'; +import { ApiResponse } from 'types/api'; +import { Props } from 'types/api/dashboard/get'; +import { Dashboard } from 'types/api/dashboard/getAll'; -const get = async ( - props: Props, -): Promise | ErrorResponse> => { - try { - const response = await axios.get(`/dashboards/${props.uuid}`); - - return { - statusCode: 200, - error: null, - message: response.data.status, - payload: response.data.data, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } -}; +const get = (props: Props): Promise => + axios + .get>(`/dashboards/${props.uuid}`) + .then((res) => res.data.data); export default get; diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index 1f984ebd46..9a368510a1 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -3,5 +3,7 @@ export const REACT_QUERY_KEY = { GET_QUERY_RANGE: 'GET_QUERY_RANGE', GET_ALL_DASHBOARDS: 'GET_ALL_DASHBOARDS', GET_TRIGGERED_ALERTS: 'GET_TRIGGERED_ALERTS', + DASHBOARD_BY_ID: 'DASHBOARD_BY_ID', GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS', + DELETE_DASHBOARD: 'DELETE_DASHBOARD', }; diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index f6bf35cbd7..d73003f06e 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -127,7 +127,7 @@ function ChartPreview({ ( + getDefaultTableDataSet(data), + ); + + const { notifications } = useNotifications(); + + const checkBoxOnChangeHandler = useCallback( + (e: CheckboxChangeEvent, index: number): void => { + const newStates = [...graphsVisibilityStates]; + + newStates[index] = e.target.checked; + + lineChartRef?.current?.toggleGraph(index, e.target.checked); + + setGraphsVisibilityStates([...newStates]); + }, + [graphsVisibilityStates, setGraphsVisibilityStates, lineChartRef], + ); + + const labelClickedHandler = useCallback( + (labelIndex: number): void => { + const newGraphVisibilityStates = Array(data.datasets.length).fill( + false, + ); + newGraphVisibilityStates[labelIndex] = true; + + newGraphVisibilityStates.forEach((state, index) => { + lineChartRef?.current?.toggleGraph(index, state); + parentChartRef?.current?.toggleGraph(index, state); + }); + setGraphsVisibilityStates(newGraphVisibilityStates); + }, + [ + data.datasets.length, + setGraphsVisibilityStates, + lineChartRef, + parentChartRef, + ], + ); + + const columns = getGraphManagerTableColumns({ + data, + checkBoxOnChangeHandler, + graphVisibilityState: graphsVisibilityStates || [], + labelClickedHandler, + yAxisUnit, + }); + + const filterHandler = useCallback( + (event: React.ChangeEvent): void => { + const value = event.target.value.toString().toLowerCase(); + const updatedDataSet = tableDataSet.map((item) => { + if (item.label?.toLocaleLowerCase().includes(value)) { + return { ...item, show: true }; + } + return { ...item, show: false }; + }); + setTableDataSet(updatedDataSet); + }, + [tableDataSet], + ); + + const saveHandler = useCallback((): void => { + saveLegendEntriesToLocalStorage({ + data, + graphVisibilityState: graphsVisibilityStates || [], + name, + }); + notifications.success({ + message: 'The updated graphs & legends are saved', + }); + if (onToggleModelHandler) { + onToggleModelHandler(); + } + }, [data, graphsVisibilityStates, name, notifications, onToggleModelHandler]); + + const dataSource = tableDataSet.filter((item) => item.show); + + return ( +
+
+ + +
+
+ + + + + + +
+
+ ); +} + +GraphManager.defaultProps = { + graphVisibilityStateHandler: undefined, +}; + +export default memo(GraphManager); diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/CustomCheckBox.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/CustomCheckBox.tsx similarity index 59% rename from frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/CustomCheckBox.tsx rename to frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/CustomCheckBox.tsx index 22ae630bb8..eda971c1e4 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/CustomCheckBox.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/CustomCheckBox.tsx @@ -1,3 +1,4 @@ +import { grey } from '@ant-design/colors'; import { Checkbox, ConfigProvider } from 'antd'; import { CheckboxChangeEvent } from 'antd/es/checkbox'; @@ -6,7 +7,7 @@ import { CheckBoxProps } from '../types'; function CustomCheckBox({ data, index, - graphVisibilityState, + graphVisibilityState = [], checkBoxOnChangeHandler, }: CheckBoxProps): JSX.Element { const { datasets } = data; @@ -15,17 +16,21 @@ function CustomCheckBox({ checkBoxOnChangeHandler(e, index); }; + const color = datasets[index]?.borderColor?.toString() || grey[0]; + + const isChecked = graphVisibilityState[index] || false; + return ( - + ); } diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GetLabel.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/GetLabel.tsx similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GetLabel.tsx rename to frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/GetLabel.tsx diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GraphManagerColumns.ts b/frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/GraphManagerColumns.tsx similarity index 87% rename from frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GraphManagerColumns.ts rename to frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/GraphManagerColumns.tsx index cc10f83f00..3702a9b3e0 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GraphManagerColumns.ts +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/GraphManagerColumns.tsx @@ -5,7 +5,7 @@ import { ChartData } from 'chart.js'; import { ColumnsKeyAndDataIndex, ColumnsTitle } from '../contants'; import { DataSetProps } from '../types'; import { getGraphManagerTableHeaderTitle } from '../utils'; -import { getCheckBox } from './GetCheckBox'; +import CustomCheckBox from './CustomCheckBox'; import { getLabel } from './GetLabel'; export const getGraphManagerTableColumns = ({ @@ -20,11 +20,14 @@ export const getGraphManagerTableColumns = ({ width: 50, dataIndex: ColumnsKeyAndDataIndex.Index, key: ColumnsKeyAndDataIndex.Index, - ...getCheckBox({ - checkBoxOnChangeHandler, - graphVisibilityState, - data, - }), + render: (_: string, __: DataSetProps, index: number): JSX.Element => ( + + ), }, { title: ColumnsTitle[ColumnsKeyAndDataIndex.Label], diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/Label.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/Label.tsx similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/Label.tsx rename to frontend/src/container/GridCardLayout/GridCard/FullView/TableRender/Label.tsx diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/contants.ts b/frontend/src/container/GridCardLayout/GridCard/FullView/contants.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/FullView/contants.ts rename to frontend/src/container/GridCardLayout/GridCard/FullView/contants.ts diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx similarity index 80% rename from frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx rename to frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx index 1437a29157..02391bdc0c 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx @@ -12,17 +12,16 @@ import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; import { useChartMutable } from 'hooks/useChartMutable'; import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import getChartData from 'lib/getChartData'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; -import { toggleGraphsVisibilityInChart } from '../utils'; import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants'; import GraphManager from './GraphManager'; import { GraphContainer, TimeContainer } from './styles'; import { FullViewProps } from './types'; -import { getIsGraphLegendToggleAvailable } from './utils'; function FullView({ widget, @@ -34,45 +33,29 @@ function FullView({ isDependedDataLoaded = false, graphsVisibilityStates, onToggleModelHandler, + setGraphsVisibilityStates, + parentChartRef, }: FullViewProps): JSX.Element { const { selectedTime: globalSelectedTime } = useSelector< AppState, GlobalReducer >((state) => state.globalTime); + const { selectedDashboard } = useDashboard(); + const getSelectedTime = useCallback( () => timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')), [widget], ); - const canModifyChart = useChartMutable({ - panelType: widget.panelTypes, - panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE, - }); - const lineChartRef = useRef(); - useEffect(() => { - if (graphsVisibilityStates && canModifyChart && lineChartRef.current) { - toggleGraphsVisibilityInChart({ - graphsVisibilityStates, - lineChartRef, - }); - } - }, [graphsVisibilityStates, canModifyChart]); - const [selectedTime, setSelectedTime] = useState({ name: getSelectedTime()?.name || '', enum: widget?.timePreferance || 'GLOBAL_TIME', }); - const queryKey = useMemo( - () => - `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`, - [selectedTime, globalSelectedTime, widget], - ); - const updatedQuery = useStepInterval(widget?.query); const response = useGetQueryRange( @@ -81,14 +64,19 @@ function FullView({ graphType: widget.panelTypes, query: updatedQuery, globalSelectedInterval: globalSelectedTime, - variables: getDashboardVariables(), + variables: getDashboardVariables(selectedDashboard?.data.variables), }, { - queryKey, + queryKey: `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`, enabled: !isDependedDataLoaded, }, ); + const canModifyChart = useChartMutable({ + panelType: widget.panelTypes, + panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE, + }); + const chartDataSet = useMemo( () => getChartData({ @@ -101,9 +89,14 @@ function FullView({ [response], ); - const isGraphLegendToggleAvailable = getIsGraphLegendToggleAvailable( - widget.panelTypes, - ); + useEffect(() => { + if (!response.isFetching && lineChartRef.current) { + graphsVisibilityStates?.forEach((e, i) => { + lineChartRef?.current?.toggleGraph(i, e); + parentChartRef?.current?.toggleGraph(i, e); + }); + } + }, [graphsVisibilityStates, parentChartRef, response.isFetching]); if (response.isFetching) { return ; @@ -128,10 +121,10 @@ function FullView({ )} - + )} diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts b/frontend/src/container/GridCardLayout/GridCard/FullView/styles.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts rename to frontend/src/container/GridCardLayout/GridCard/FullView/styles.ts diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/types.ts b/frontend/src/container/GridCardLayout/GridCard/FullView/types.ts similarity index 76% rename from frontend/src/container/GridGraphLayout/Graph/FullView/types.ts rename to frontend/src/container/GridCardLayout/GridCard/FullView/types.ts index 7d329e1399..ae686496e5 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/types.ts @@ -1,7 +1,8 @@ import { CheckboxChangeEvent } from 'antd/es/checkbox'; import { ChartData, ChartDataset } from 'chart.js'; -import { GraphOnClickHandler } from 'components/Graph/types'; +import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types'; import { PANEL_TYPES } from 'constants/queryBuilder'; +import { MutableRefObject } from 'react'; import { Widgets } from 'types/api/dashboard/getAll'; export interface DataSetProps { @@ -40,20 +41,6 @@ export interface LabelProps { label: string; } -export interface GraphManagerProps { - data: ChartData; - name: string; - yAxisUnit?: string; - onToggleModelHandler?: () => void; -} - -export interface CheckBoxProps { - data: ChartData; - index: number; - graphVisibilityState: boolean[]; - checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void; -} - export interface FullViewProps { widget: Widgets; fullViewOptions?: boolean; @@ -64,6 +51,26 @@ export interface FullViewProps { isDependedDataLoaded?: boolean; graphsVisibilityStates?: boolean[]; onToggleModelHandler?: GraphManagerProps['onToggleModelHandler']; + setGraphsVisibilityStates: (graphsVisibilityStates: boolean[]) => void; + parentChartRef: GraphManagerProps['lineChartRef']; +} + +export interface GraphManagerProps { + data: ChartData; + name: string; + yAxisUnit?: string; + onToggleModelHandler?: () => void; + setGraphsVisibilityStates: FullViewProps['setGraphsVisibilityStates']; + graphsVisibilityStates: FullViewProps['graphsVisibilityStates']; + lineChartRef?: MutableRefObject; + parentChartRef?: MutableRefObject; +} + +export interface CheckBoxProps { + data: ChartData; + index: number; + graphVisibilityState: boolean[]; + checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void; } export interface SaveLegendEntriesToLocalStoreProps { diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/utils.ts b/frontend/src/container/GridCardLayout/GridCard/FullView/utils.ts similarity index 94% rename from frontend/src/container/GridGraphLayout/Graph/FullView/utils.ts rename to frontend/src/container/GridCardLayout/GridCard/FullView/utils.ts index 256bc39050..b1ffb3a032 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/utils.ts +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/utils.ts @@ -1,6 +1,5 @@ import { ChartData, ChartDataset } from 'chart.js'; import { LOCALSTORAGE } from 'constants/localStorage'; -import { PANEL_TYPES } from 'constants/queryBuilder'; import { ExtendedChartDataset, @@ -110,10 +109,6 @@ export const saveLegendEntriesToLocalStorage = ({ } }; -export const getIsGraphLegendToggleAvailable = ( - panelType: PANEL_TYPES, -): boolean => panelType === PANEL_TYPES.TIME_SERIES; - export const getGraphManagerTableHeaderTitle = ( title: string, yAxisUnit?: string, diff --git a/frontend/src/container/GridGraphLayout/Graph/Graph.test.tsx b/frontend/src/container/GridCardLayout/GridCard/Graph.test.tsx similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/Graph.test.tsx rename to frontend/src/container/GridCardLayout/GridCard/Graph.test.tsx diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx new file mode 100644 index 0000000000..c011ca2471 --- /dev/null +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx @@ -0,0 +1,277 @@ +import { Typography } from 'antd'; +import { ToggleGraphProps } from 'components/Graph/types'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import GridPanelSwitch from 'container/GridPanelSwitch'; +import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; +import { useNotifications } from 'hooks/useNotifications'; +import createQueryParams from 'lib/createQueryParams'; +import history from 'lib/history'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { + Dispatch, + SetStateAction, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { Dashboard } from 'types/api/dashboard/getAll'; +import AppReducer from 'types/reducer/app'; +import { v4 } from 'uuid'; + +import WidgetHeader from '../WidgetHeader'; +import FullView from './FullView'; +import { FullViewContainer, Modal } from './styles'; +import { WidgetGraphComponentProps } from './types'; +import { getGraphVisibilityStateOnDataChange } from './utils'; + +function WidgetGraphComponent({ + data, + widget, + queryResponse, + errorMessage, + name, + onDragSelect, + onClickHandler, + threshold, + headerMenuList, + isWarning, +}: WidgetGraphComponentProps): JSX.Element { + const [deleteModal, setDeleteModal] = useState(false); + const [modal, setModal] = useState(false); + const [hovered, setHovered] = useState(false); + const { notifications } = useNotifications(); + const { pathname } = useLocation(); + + const lineChartRef = useRef(); + + const { graphVisibilityStates: localStoredVisibilityStates } = useMemo( + () => + getGraphVisibilityStateOnDataChange({ + data, + isExpandedName: true, + name, + }), + [data, name], + ); + + useEffect(() => { + if (!lineChartRef.current) return; + + localStoredVisibilityStates.forEach((state, index) => { + lineChartRef.current?.toggleGraph(index, state); + }); + }, [localStoredVisibilityStates]); + + const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard(); + + const [graphsVisibilityStates, setGraphsVisibilityStates] = useState< + boolean[] + >(localStoredVisibilityStates); + + const { featureResponse } = useSelector( + (state) => state.app, + ); + const onToggleModal = useCallback( + (func: Dispatch>) => { + func((value) => !value); + }, + [], + ); + + const updateDashboardMutation = useUpdateDashboard(); + + const onDeleteHandler = (): void => { + if (!selectedDashboard) return; + + const updatedWidgets = selectedDashboard?.data?.widgets?.filter( + (e) => e.id !== widget.id, + ); + + const updatedLayout = + selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || []; + + const updatedSelectedDashboard: Dashboard = { + ...selectedDashboard, + data: { + ...selectedDashboard.data, + widgets: updatedWidgets, + layout: updatedLayout, + }, + uuid: selectedDashboard.uuid, + }; + + updateDashboardMutation.mutateAsync(updatedSelectedDashboard, { + onSuccess: (updatedDashboard) => { + if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []); + if (setSelectedDashboard && updatedDashboard.payload) { + setSelectedDashboard(updatedDashboard.payload); + } + featureResponse.refetch(); + }, + onError: () => { + notifications.error({ + message: SOMETHING_WENT_WRONG, + }); + }, + }); + }; + + const onCloneHandler = async (): Promise => { + if (!selectedDashboard) return; + + const uuid = v4(); + + const layout = [ + ...(selectedDashboard.data.layout || []), + { + i: uuid, + w: 6, + x: 0, + h: 2, + y: 0, + }, + ]; + + updateDashboardMutation.mutateAsync( + { + ...selectedDashboard, + data: { + ...selectedDashboard.data, + layout, + widgets: [ + ...(selectedDashboard.data.widgets || []), + { + ...{ + ...widget, + id: uuid, + }, + }, + ], + }, + }, + { + onSuccess: () => { + notifications.success({ + message: 'Panel cloned successfully, redirecting to new copy.', + }); + const queryParams = { + graphType: widget?.panelTypes, + widgetId: uuid, + }; + history.push(`${pathname}/new?${createQueryParams(queryParams)}`); + }, + }, + ); + }; + + const handleOnView = (): void => { + onToggleModal(setModal); + }; + + const handleOnDelete = (): void => { + onToggleModal(setDeleteModal); + }; + + const onDeleteModelHandler = (): void => { + onToggleModal(setDeleteModal); + }; + + const onToggleModelHandler = (): void => { + onToggleModal(setModal); + }; + + return ( + { + setHovered(true); + }} + onFocus={(): void => { + setHovered(true); + }} + onMouseOut={(): void => { + setHovered(false); + }} + onBlur={(): void => { + setHovered(false); + }} + > + + Are you sure you want to delete this widget + + + + + + + + +
+ +
+ +
+ ); +} + +WidgetGraphComponent.defaultProps = { + yAxisUnit: undefined, + setLayout: undefined, + onDragSelect: undefined, + onClickHandler: undefined, +}; + +export default WidgetGraphComponent; diff --git a/frontend/src/container/GridGraphLayout/Graph/__mock__/mockChartData.ts b/frontend/src/container/GridCardLayout/GridCard/__mock__/mockChartData.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/__mock__/mockChartData.ts rename to frontend/src/container/GridCardLayout/GridCard/__mock__/mockChartData.ts diff --git a/frontend/src/container/GridGraphLayout/Graph/__mock__/mockLegendEntryData.ts b/frontend/src/container/GridCardLayout/GridCard/__mock__/mockLegendEntryData.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/__mock__/mockLegendEntryData.ts rename to frontend/src/container/GridCardLayout/GridCard/__mock__/mockLegendEntryData.ts diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx new file mode 100644 index 0000000000..59e58fb74a --- /dev/null +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -0,0 +1,133 @@ +import { Skeleton } from 'antd'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; +import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; +import getChartData from 'lib/getChartData'; +import isEmpty from 'lodash-es/isEmpty'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { memo, useMemo, useState } from 'react'; +import { useInView } from 'react-intersection-observer'; +import { useDispatch, useSelector } from 'react-redux'; +import { UpdateTimeInterval } from 'store/actions'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import EmptyWidget from '../EmptyWidget'; +import { MenuItemKeys } from '../WidgetHeader/contants'; +import { GridCardGraphProps } from './types'; +import WidgetGraphComponent from './WidgetGraphComponent'; + +function GridCardGraph({ + widget, + name, + onClickHandler, + headerMenuList = [MenuItemKeys.View], + isQueryEnabled, + threshold, +}: GridCardGraphProps): JSX.Element { + const dispatch = useDispatch(); + const [errorMessage, setErrorMessage] = useState(); + + const onDragSelect = (start: number, end: number): void => { + const startTimestamp = Math.trunc(start); + const endTimestamp = Math.trunc(end); + + if (startTimestamp !== endTimestamp) { + dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp])); + } + }; + + const { ref: graphRef, inView: isGraphVisible } = useInView({ + threshold: 0, + triggerOnce: true, + initialInView: false, + }); + + const { selectedDashboard } = useDashboard(); + + const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const updatedQuery = useStepInterval(widget?.query); + + const isEmptyWidget = + widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget); + + const queryResponse = useGetQueryRange( + { + selectedTime: widget?.timePreferance, + graphType: widget?.panelTypes, + query: updatedQuery, + globalSelectedInterval, + variables: getDashboardVariables(selectedDashboard?.data.variables), + }, + { + queryKey: [ + maxTime, + minTime, + globalSelectedInterval, + selectedDashboard?.data?.variables, + widget?.query, + widget?.panelTypes, + ], + keepPreviousData: true, + enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled, + refetchOnMount: false, + onError: (error) => { + setErrorMessage(error.message); + }, + }, + ); + + const chartData = useMemo( + () => + getChartData({ + queryData: [ + { + queryData: queryResponse?.data?.payload?.data?.result || [], + }, + ], + createDataset: undefined, + isWarningLimit: true, + }), + [queryResponse], + ); + + const isEmptyLayout = widget?.id === PANEL_TYPES.EMPTY_WIDGET; + + if (queryResponse.isLoading) { + return ; + } + + return ( + + + + {isEmptyLayout && } + + ); +} + +GridCardGraph.defaultProps = { + onDragSelect: undefined, + onClickHandler: undefined, + isQueryEnabled: true, + threshold: undefined, + headerMenuList: [MenuItemKeys.View], +}; + +export default memo(GridCardGraph); diff --git a/frontend/src/container/GridGraphLayout/Graph/styles.ts b/frontend/src/container/GridCardLayout/GridCard/styles.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/styles.ts rename to frontend/src/container/GridCardLayout/GridCard/styles.ts diff --git a/frontend/src/container/GridGraphLayout/Graph/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts similarity index 67% rename from frontend/src/container/GridGraphLayout/Graph/types.ts rename to frontend/src/container/GridCardLayout/GridCard/types.ts index 49b637a178..fccf488dc8 100644 --- a/frontend/src/container/GridGraphLayout/Graph/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/types.ts @@ -1,15 +1,11 @@ import { ChartData } from 'chart.js'; import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types'; -import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react'; -import { Layout } from 'react-grid-layout'; +import { MutableRefObject, ReactNode } from 'react'; import { UseQueryResult } from 'react-query'; -import { DeleteWidgetProps } from 'store/actions/dashboard/deleteWidget'; -import AppActions from 'types/actions'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { Widgets } from 'types/api/dashboard/getAll'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; -import { LayoutProps } from '..'; import { MenuItemKeys } from '../WidgetHeader/contants'; import { LegendEntryProps } from './FullView/types'; @@ -18,15 +14,7 @@ export interface GraphVisibilityLegendEntryProps { legendEntry: LegendEntryProps[]; } -export interface DispatchProps { - deleteWidget: ({ - widgetId, - }: DeleteWidgetProps) => (dispatch: Dispatch) => void; -} - -export interface WidgetGraphComponentProps extends DispatchProps { - enableModel: boolean; - enableWidgetHeader: boolean; +export interface WidgetGraphComponentProps { widget: Widgets; queryResponse: UseQueryResult< SuccessResponse | ErrorResponse @@ -34,21 +22,16 @@ export interface WidgetGraphComponentProps extends DispatchProps { errorMessage: string | undefined; data: ChartData; name: string; - yAxisUnit?: string; - layout?: Layout[]; - setLayout?: Dispatch>; onDragSelect?: (start: number, end: number) => void; onClickHandler?: GraphOnClickHandler; threshold?: ReactNode; headerMenuList: MenuItemKeys[]; + isWarning: boolean; } export interface GridCardGraphProps { widget: Widgets; name: string; - yAxisUnit: string | undefined; - layout?: Layout[]; - setLayout?: Dispatch>; onDragSelect?: (start: number, end: number) => void; onClickHandler?: GraphOnClickHandler; threshold?: ReactNode; diff --git a/frontend/src/container/GridGraphLayout/Graph/utils.ts b/frontend/src/container/GridCardLayout/GridCard/utils.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/Graph/utils.ts rename to frontend/src/container/GridCardLayout/GridCard/utils.ts diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.tsx b/frontend/src/container/GridCardLayout/GridCardLayout.tsx new file mode 100644 index 0000000000..ac60e54146 --- /dev/null +++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx @@ -0,0 +1,131 @@ +import { PlusOutlined, SaveFilled } from '@ant-design/icons'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; +import useComponentPermission from 'hooks/useComponentPermission'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import { useNotifications } from 'hooks/useNotifications'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { Dashboard, Widgets } from 'types/api/dashboard/getAll'; +import AppReducer from 'types/reducer/app'; + +import { headerMenuList } from './config'; +import GridCard from './GridCard'; +import { + Button, + ButtonContainer, + Card, + CardContainer, + ReactGridLayout, +} from './styles'; +import { GraphLayoutProps } from './types'; + +function GraphLayout({ + onAddPanelHandler, + widgets, +}: GraphLayoutProps): JSX.Element { + const { selectedDashboard, layouts, setLayouts } = useDashboard(); + const { t } = useTranslation(['dashboard']); + + const { featureResponse, role } = useSelector( + (state) => state.app, + ); + + const isDarkMode = useIsDarkMode(); + + const updateDashboardMutation = useUpdateDashboard(); + + const { notifications } = useNotifications(); + + const [saveLayoutPermission, addPanelPermission] = useComponentPermission( + ['save_layout', 'add_panel'], + role, + ); + + const onSaveHandler = (): void => { + if (!selectedDashboard) return; + + const updatedDashboard: Dashboard = { + ...selectedDashboard, + data: { + ...selectedDashboard.data, + layout: layouts.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET), + }, + uuid: selectedDashboard.uuid, + }; + + updateDashboardMutation.mutate(updatedDashboard, { + onSuccess: () => { + featureResponse.refetch(); + + notifications.success({ + message: t('dashboard:layout_saved_successfully'), + }); + }, + onError: () => { + notifications.error({ + message: SOMETHING_WENT_WRONG, + }); + }, + }); + }; + + return ( + <> + + {saveLayoutPermission && ( + + )} + + {addPanelPermission && ( + + )} + + + + {layouts.map((layout) => { + const { i: id } = layout; + const currentWidget = (widgets || [])?.find((e) => e.id === id); + + return ( + + + + + + ); + })} + + + ); +} + +export default GraphLayout; diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/DisplayThreshold.tsx b/frontend/src/container/GridCardLayout/WidgetHeader/DisplayThreshold.tsx similarity index 100% rename from frontend/src/container/GridGraphLayout/WidgetHeader/DisplayThreshold.tsx rename to frontend/src/container/GridCardLayout/WidgetHeader/DisplayThreshold.tsx diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/config.ts b/frontend/src/container/GridCardLayout/WidgetHeader/config.ts similarity index 61% rename from frontend/src/container/GridGraphLayout/WidgetHeader/config.ts rename to frontend/src/container/GridCardLayout/WidgetHeader/config.ts index 1e15697049..68e9dd5b6c 100644 --- a/frontend/src/container/GridGraphLayout/WidgetHeader/config.ts +++ b/frontend/src/container/GridCardLayout/WidgetHeader/config.ts @@ -3,7 +3,11 @@ import { CSSProperties } from 'react'; const positionCss: CSSProperties['position'] = 'absolute'; -export const spinnerStyles = { position: positionCss, right: '0.5rem' }; +export const spinnerStyles = { + position: positionCss, + top: '0', + right: '0', +}; export const tooltipStyles = { fontSize: '1rem', top: '0.313rem', @@ -21,3 +25,6 @@ export const overlayStyles: CSSProperties = { justifyContent: 'center', position: 'absolute', }; + +export const WARNING_MESSAGE = + 'Too many timeseries in the result. UI has restricted to showing the top 20. Please check the query if this is needed and contact support@signoz.io if you need to show >20 timeseries in the panel'; diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/contants.ts b/frontend/src/container/GridCardLayout/WidgetHeader/contants.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/WidgetHeader/contants.ts rename to frontend/src/container/GridCardLayout/WidgetHeader/contants.ts diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx b/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx similarity index 92% rename from frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx rename to frontend/src/container/GridCardLayout/WidgetHeader/index.tsx index 20ad222e55..a916534082 100644 --- a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx +++ b/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx @@ -5,10 +5,12 @@ import { EditFilled, ExclamationCircleOutlined, FullscreenOutlined, + WarningOutlined, } from '@ant-design/icons'; import { Dropdown, MenuProps, Tooltip, Typography } from 'antd'; import Spinner from 'components/Spinner'; import { QueryParams } from 'constants/query'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; import useComponentPermission from 'hooks/useComponentPermission'; import history from 'lib/history'; @@ -27,6 +29,7 @@ import { overlayStyles, spinnerStyles, tooltipStyles, + WARNING_MESSAGE, } from './config'; import { MENUITEM_KEYS_VS_LABELS, MenuItemKeys } from './contants'; import { @@ -52,6 +55,7 @@ interface IWidgetHeaderProps { errorMessage: string | undefined; threshold?: ReactNode; headerMenuList?: MenuItemKeys[]; + isWarning: boolean; } function WidgetHeader({ @@ -65,7 +69,8 @@ function WidgetHeader({ errorMessage, threshold, headerMenuList, -}: IWidgetHeaderProps): JSX.Element { + isWarning, +}: IWidgetHeaderProps): JSX.Element | null { const [localHover, setLocalHover] = useState(false); const [isOpen, setIsOpen] = useState(false); @@ -126,7 +131,7 @@ function WidgetHeader({ icon: , label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.View], isVisible: headerMenuList?.includes(MenuItemKeys.View) || false, - disabled: queryResponse.isLoading, + disabled: queryResponse.isFetching, }, { key: MenuItemKeys.Edit, @@ -158,7 +163,7 @@ function WidgetHeader({ disabled: false, }, ], - [queryResponse.isLoading, headerMenuList, editWidget, deleteWidget], + [headerMenuList, queryResponse.isFetching, editWidget, deleteWidget], ); const updatedMenuList = useMemo(() => generateMenuList(actions), [actions]); @@ -175,6 +180,10 @@ function WidgetHeader({ [updatedMenuList, onMenuItemSelectHandler], ); + if (widget.id === PANEL_TYPES.EMPTY_WIDGET) { + return null; + } + return ( + {threshold} {queryResponse.isFetching && !queryResponse.isError && ( @@ -211,6 +221,12 @@ function WidgetHeader({ )} + + {isWarning && ( + + + + )} ); } diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts b/frontend/src/container/GridCardLayout/WidgetHeader/styles.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts rename to frontend/src/container/GridCardLayout/WidgetHeader/styles.ts diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/types.ts b/frontend/src/container/GridCardLayout/WidgetHeader/types.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/WidgetHeader/types.ts rename to frontend/src/container/GridCardLayout/WidgetHeader/types.ts diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/utils.ts b/frontend/src/container/GridCardLayout/WidgetHeader/utils.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/WidgetHeader/utils.ts rename to frontend/src/container/GridCardLayout/WidgetHeader/utils.ts diff --git a/frontend/src/container/GridCardLayout/config.ts b/frontend/src/container/GridCardLayout/config.ts new file mode 100644 index 0000000000..3fa9e8e569 --- /dev/null +++ b/frontend/src/container/GridCardLayout/config.ts @@ -0,0 +1,17 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants'; + +export const headerMenuList = [ + MenuItemKeys.View, + MenuItemKeys.Clone, + MenuItemKeys.Delete, + MenuItemKeys.Edit, +]; + +export const EMPTY_WIDGET_LAYOUT = { + i: PANEL_TYPES.EMPTY_WIDGET, + w: 6, + x: 0, + h: 2, + y: 0, +}; diff --git a/frontend/src/container/GridCardLayout/index.tsx b/frontend/src/container/GridCardLayout/index.tsx new file mode 100644 index 0000000000..e715d7d539 --- /dev/null +++ b/frontend/src/container/GridCardLayout/index.tsx @@ -0,0 +1,35 @@ +import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { useCallback } from 'react'; +import { Layout } from 'react-grid-layout'; + +import { EMPTY_WIDGET_LAYOUT } from './config'; +import GraphLayoutContainer from './GridCardLayout'; + +function GridGraph(): JSX.Element { + const { + selectedDashboard, + setLayouts, + handleToggleDashboardSlider, + } = useDashboard(); + + const { data } = selectedDashboard || {}; + const { widgets } = data || {}; + + const onEmptyWidgetHandler = useCallback(() => { + handleToggleDashboardSlider(true); + + setLayouts((preLayout: Layout[]) => [ + EMPTY_WIDGET_LAYOUT, + ...(preLayout || []), + ]); + }, [handleToggleDashboardSlider, setLayouts]); + + return ( + + ); +} + +export default GridGraph; diff --git a/frontend/src/container/GridGraphLayout/styles.ts b/frontend/src/container/GridCardLayout/styles.ts similarity index 100% rename from frontend/src/container/GridGraphLayout/styles.ts rename to frontend/src/container/GridCardLayout/styles.ts diff --git a/frontend/src/container/GridCardLayout/types.ts b/frontend/src/container/GridCardLayout/types.ts new file mode 100644 index 0000000000..0d2b678af6 --- /dev/null +++ b/frontend/src/container/GridCardLayout/types.ts @@ -0,0 +1,6 @@ +import { Widgets } from 'types/api/dashboard/getAll'; + +export interface GraphLayoutProps { + onAddPanelHandler: VoidFunction; + widgets?: Widgets[]; +} diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx deleted file mode 100644 index 31f528a410..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import './GraphManager.styles.scss'; - -import { Button, Input } from 'antd'; -import { CheckboxChangeEvent } from 'antd/es/checkbox'; -import { ResizeTable } from 'components/ResizeTable'; -import { Events } from 'constants/events'; -import { useNotifications } from 'hooks/useNotifications'; -import isEqual from 'lodash-es/isEqual'; -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { eventEmitter } from 'utils/getEventEmitter'; - -import { getGraphVisibilityStateOnDataChange } from '../utils'; -import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns'; -import { ExtendedChartDataset, GraphManagerProps } from './types'; -import { - getDefaultTableDataSet, - saveLegendEntriesToLocalStorage, -} from './utils'; - -function GraphManager({ - data, - name, - yAxisUnit, - onToggleModelHandler, -}: GraphManagerProps): JSX.Element { - const { - graphVisibilityStates: localstoredVisibilityStates, - legendEntry, - } = useMemo( - () => - getGraphVisibilityStateOnDataChange({ - data, - isExpandedName: false, - name, - }), - [data, name], - ); - - const [graphVisibilityState, setGraphVisibilityState] = useState( - localstoredVisibilityStates, - ); - - const [tableDataSet, setTableDataSet] = useState( - getDefaultTableDataSet(data), - ); - - const { notifications } = useNotifications(); - - // useEffect for updating graph visibility state on data change - useEffect(() => { - const newGraphVisibilityStates = Array(data.datasets.length).fill( - true, - ); - data.datasets.forEach((dataset, i) => { - const index = legendEntry.findIndex( - (entry) => entry.label === dataset.label, - ); - if (index !== -1) { - newGraphVisibilityStates[i] = legendEntry[index].show; - } - }); - eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, { - name, - graphVisibilityStates: newGraphVisibilityStates, - }); - setGraphVisibilityState(newGraphVisibilityStates); - }, [data, name, legendEntry]); - - // useEffect for listening to events event graph legend is clicked - useEffect(() => { - const eventListener = eventEmitter.on( - Events.UPDATE_GRAPH_MANAGER_TABLE, - (data) => { - if (data.name === name) { - const newGraphVisibilityStates = graphVisibilityState; - newGraphVisibilityStates[data.index] = !newGraphVisibilityStates[ - data.index - ]; - eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, { - name, - graphVisibilityStates: newGraphVisibilityStates, - }); - setGraphVisibilityState([...newGraphVisibilityStates]); - } - }, - ); - return (): void => { - eventListener.off(Events.UPDATE_GRAPH_MANAGER_TABLE); - }; - }, [graphVisibilityState, name]); - - const checkBoxOnChangeHandler = useCallback( - (e: CheckboxChangeEvent, index: number): void => { - graphVisibilityState[index] = e.target.checked; - setGraphVisibilityState([...graphVisibilityState]); - eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, { - name, - graphVisibilityStates: [...graphVisibilityState], - }); - }, - [graphVisibilityState, name], - ); - - const labelClickedHandler = useCallback( - (labelIndex: number): void => { - const newGraphVisibilityStates = Array(data.datasets.length).fill( - false, - ); - newGraphVisibilityStates[labelIndex] = true; - setGraphVisibilityState([...newGraphVisibilityStates]); - eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, { - name, - graphVisibilityStates: newGraphVisibilityStates, - }); - }, - [data.datasets.length, name], - ); - - const columns = useMemo( - () => - getGraphManagerTableColumns({ - data, - checkBoxOnChangeHandler, - graphVisibilityState, - labelClickedHandler, - yAxisUnit, - }), - [ - checkBoxOnChangeHandler, - data, - graphVisibilityState, - labelClickedHandler, - yAxisUnit, - ], - ); - - const filterHandler = useCallback( - (event: React.ChangeEvent): void => { - const value = event.target.value.toString().toLowerCase(); - const updatedDataSet = tableDataSet.map((item) => { - if (item.label?.toLocaleLowerCase().includes(value)) { - return { ...item, show: true }; - } - return { ...item, show: false }; - }); - setTableDataSet(updatedDataSet); - }, - [tableDataSet], - ); - - const saveHandler = useCallback((): void => { - saveLegendEntriesToLocalStorage({ - data, - graphVisibilityState, - name, - }); - notifications.success({ - message: 'The updated graphs & legends are saved', - }); - if (onToggleModelHandler) { - onToggleModelHandler(); - } - }, [data, graphVisibilityState, name, notifications, onToggleModelHandler]); - - const dataSource = tableDataSet.filter((item) => item.show); - - return ( -
-
- - -
-
- - - - - - -
-
- ); -} - -GraphManager.defaultProps = { - graphVisibilityStateHandler: undefined, -}; - -export default memo( - GraphManager, - (prevProps, nextProps) => - isEqual(prevProps.data, nextProps.data) && prevProps.name === nextProps.name, -); diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GetCheckBox.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GetCheckBox.tsx deleted file mode 100644 index 55485be5ad..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/TableRender/GetCheckBox.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { CheckboxChangeEvent } from 'antd/es/checkbox'; -import { ColumnType } from 'antd/es/table'; -import { ChartData } from 'chart.js'; - -import { DataSetProps } from '../types'; -import CustomCheckBox from './CustomCheckBox'; - -export const getCheckBox = ({ - data, - checkBoxOnChangeHandler, - graphVisibilityState, -}: GetCheckBoxProps): ColumnType => ({ - render: (index: number): JSX.Element => ( - - ), -}); - -interface GetCheckBoxProps { - data: ChartData; - checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void; - graphVisibilityState: boolean[]; -} diff --git a/frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx b/frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx deleted file mode 100644 index f40d1e4190..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx +++ /dev/null @@ -1,334 +0,0 @@ -import { Typography } from 'antd'; -import { ToggleGraphProps } from 'components/Graph/types'; -import { Events } from 'constants/events'; -import GridPanelSwitch from 'container/GridPanelSwitch'; -import { useChartMutable } from 'hooks/useChartMutable'; -import { useNotifications } from 'hooks/useNotifications'; -import createQueryParams from 'lib/createQueryParams'; -import history from 'lib/history'; -import { isEmpty, isEqual } from 'lodash-es'; -import { - Dispatch, - memo, - SetStateAction, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { useTranslation } from 'react-i18next'; -import { connect, useSelector } from 'react-redux'; -import { useLocation } from 'react-router-dom'; -import { bindActionCreators } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { DeleteWidget } from 'store/actions/dashboard/deleteWidget'; -import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import AppReducer from 'types/reducer/app'; -import DashboardReducer from 'types/reducer/dashboards'; -import { eventEmitter } from 'utils/getEventEmitter'; -import { v4 } from 'uuid'; - -import { UpdateDashboard } from '../utils'; -import WidgetHeader from '../WidgetHeader'; -import FullView from './FullView'; -import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './FullView/contants'; -import { FullViewContainer, Modal } from './styles'; -import { DispatchProps, WidgetGraphComponentProps } from './types'; -import { - getGraphVisibilityStateOnDataChange, - toggleGraphsVisibilityInChart, -} from './utils'; - -function WidgetGraphComponent({ - enableModel, - enableWidgetHeader, - data, - widget, - queryResponse, - errorMessage, - name, - yAxisUnit, - layout = [], - deleteWidget, - setLayout, - onDragSelect, - onClickHandler, - threshold, - headerMenuList, -}: WidgetGraphComponentProps): JSX.Element { - const [deleteModal, setDeleteModal] = useState(false); - const [modal, setModal] = useState(false); - const [hovered, setHovered] = useState(false); - const { notifications } = useNotifications(); - const { t } = useTranslation(['common']); - const { pathname } = useLocation(); - - const { graphVisibilityStates: localstoredVisibilityStates } = useMemo( - () => - getGraphVisibilityStateOnDataChange({ - data, - isExpandedName: true, - name, - }), - [data, name], - ); - - const [graphsVisibilityStates, setGraphsVisilityStates] = useState( - localstoredVisibilityStates, - ); - - const { dashboards } = useSelector( - (state) => state.dashboards, - ); - const [selectedDashboard] = dashboards; - - const canModifyChart = useChartMutable({ - panelType: widget.panelTypes, - panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE, - }); - - const lineChartRef = useRef(); - - // Updating the visibility state of the graph on data change according to global time range - useEffect(() => { - if (canModifyChart) { - const newGraphVisibilityState = getGraphVisibilityStateOnDataChange({ - data, - isExpandedName: true, - name, - }); - setGraphsVisilityStates(newGraphVisibilityState.graphVisibilityStates); - } - }, [canModifyChart, data, name]); - - useEffect(() => { - const eventListener = eventEmitter.on( - Events.UPDATE_GRAPH_VISIBILITY_STATE, - (data) => { - if (data.name === `${name}expanded` && canModifyChart) { - setGraphsVisilityStates([...data.graphVisibilityStates]); - } - }, - ); - return (): void => { - eventListener.off(Events.UPDATE_GRAPH_VISIBILITY_STATE); - }; - }, [canModifyChart, name]); - - useEffect(() => { - if (canModifyChart && lineChartRef.current) { - toggleGraphsVisibilityInChart({ - graphsVisibilityStates, - lineChartRef, - }); - } - }, [graphsVisibilityStates, canModifyChart]); - - const { featureResponse } = useSelector( - (state) => state.app, - ); - const onToggleModal = useCallback( - (func: Dispatch>) => { - func((value) => !value); - }, - [], - ); - - const onDeleteHandler = useCallback(() => { - const isEmptyWidget = widget?.id === 'empty' || isEmpty(widget); - const widgetId = isEmptyWidget ? layout[0].i : widget?.id; - - featureResponse - .refetch() - .then(() => { - deleteWidget({ widgetId, setLayout }); - onToggleModal(setDeleteModal); - }) - .catch(() => { - notifications.error({ - message: t('common:something_went_wrong'), - }); - }); - }, [ - widget, - layout, - featureResponse, - deleteWidget, - setLayout, - onToggleModal, - notifications, - t, - ]); - - const onCloneHandler = async (): Promise => { - const uuid = v4(); - - const layout = [ - { - i: uuid, - w: 6, - x: 0, - h: 2, - y: 0, - }, - ...(selectedDashboard.data.layout || []), - ]; - - if (widget) { - await UpdateDashboard( - { - data: selectedDashboard.data, - generateWidgetId: uuid, - graphType: widget?.panelTypes, - selectedDashboard, - layout, - widgetData: widget, - isRedirected: false, - }, - notifications, - ).then(() => { - notifications.success({ - message: 'Panel cloned successfully, redirecting to new copy.', - }); - - const queryParams = { - graphType: widget?.panelTypes, - widgetId: uuid, - }; - history.push(`${pathname}/new?${createQueryParams(queryParams)}`); - }); - } - }; - - const handleOnView = (): void => { - onToggleModal(setModal); - }; - - const handleOnDelete = (): void => { - onToggleModal(setDeleteModal); - }; - - const onDeleteModelHandler = (): void => { - onToggleModal(setDeleteModal); - }; - - const onToggleModelHandler = (): void => { - onToggleModal(setModal); - }; - - const getModals = (): JSX.Element => ( - <> - - Are you sure you want to delete this widget - - - - - - - - - ); - - return ( - { - setHovered(true); - }} - onFocus={(): void => { - setHovered(true); - }} - onMouseOut={(): void => { - setHovered(false); - }} - onBlur={(): void => { - setHovered(false); - }} - > - {enableModel && getModals()} - {!isEmpty(widget) && data && ( - <> - {enableWidgetHeader && ( -
- -
- )} - - - )} -
- ); -} - -WidgetGraphComponent.defaultProps = { - yAxisUnit: undefined, - layout: undefined, - setLayout: undefined, - onDragSelect: undefined, - onClickHandler: undefined, -}; - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - deleteWidget: bindActionCreators(DeleteWidget, dispatch), -}); - -export default connect( - null, - mapDispatchToProps, -)( - memo( - WidgetGraphComponent, - (prevProps, nextProps) => - isEqual(prevProps.data, nextProps.data) && prevProps.name === nextProps.name, - ), -); diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx deleted file mode 100644 index 94b9d10252..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { ChartData } from 'chart.js'; -import Spinner from 'components/Spinner'; -import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; -import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; -import usePreviousValue from 'hooks/usePreviousValue'; -import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; -import getChartData from 'lib/getChartData'; -import isEmpty from 'lodash-es/isEmpty'; -import { memo, useMemo, useState } from 'react'; -import { useInView } from 'react-intersection-observer'; -import { useSelector } from 'react-redux'; -import { AppState } from 'store/reducers'; -import DashboardReducer from 'types/reducer/dashboards'; -import { GlobalReducer } from 'types/reducer/globalTime'; -import { getSelectedDashboardVariable } from 'utils/dashboard/selectedDashboard'; - -import EmptyWidget from '../EmptyWidget'; -import { MenuItemKeys } from '../WidgetHeader/contants'; -import { GridCardGraphProps } from './types'; -import WidgetGraphComponent from './WidgetGraphComponent'; - -function GridCardGraph({ - widget, - name, - yAxisUnit, - layout = [], - setLayout, - onDragSelect, - onClickHandler, - headerMenuList = [MenuItemKeys.View], - isQueryEnabled, - threshold, -}: GridCardGraphProps): JSX.Element { - const { isAddWidget } = useSelector( - (state) => state.dashboards, - ); - - const { ref: graphRef, inView: isGraphVisible } = useInView({ - threshold: 0, - triggerOnce: true, - initialInView: false, - }); - - const [errorMessage, setErrorMessage] = useState(''); - - const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector< - AppState, - GlobalReducer - >((state) => state.globalTime); - const { dashboards } = useSelector( - (state) => state.dashboards, - ); - - const variables = getSelectedDashboardVariable(dashboards); - - const updatedQuery = useStepInterval(widget?.query); - - const isEmptyWidget = useMemo( - () => widget?.id === 'empty' || isEmpty(widget), - [widget], - ); - - const queryResponse = useGetQueryRange( - { - selectedTime: widget?.timePreferance, - graphType: widget?.panelTypes, - query: updatedQuery, - globalSelectedInterval, - variables: getDashboardVariables(), - }, - { - queryKey: [ - `GetMetricsQueryRange-${widget?.timePreferance}-${globalSelectedInterval}-${widget?.id}`, - maxTime, - minTime, - globalSelectedInterval, - variables, - widget?.query, - widget?.panelTypes, - ], - keepPreviousData: true, - enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled && !isAddWidget, - refetchOnMount: false, - onError: (error) => { - setErrorMessage(error.message); - }, - }, - ); - - const chartData = useMemo( - () => - getChartData({ - queryData: [ - { - queryData: queryResponse?.data?.payload?.data?.result || [], - }, - ], - }), - [queryResponse], - ); - - const prevChartDataSetRef = usePreviousValue(chartData); - - const isEmptyLayout = widget?.id === 'empty' || isEmpty(widget); - - if (queryResponse.isRefetching || queryResponse.isLoading) { - return ; - } - - if ((queryResponse.isError && !isEmptyLayout) || !isQueryEnabled) { - return ( - - {!isEmpty(widget) && prevChartDataSetRef && ( - - )} - - ); - } - - if (!isEmpty(widget) && prevChartDataSetRef?.labels) { - return ( - - - - ); - } - - return ( - - {!isEmpty(widget) && !!queryResponse.data?.payload && ( - - )} - - {isEmptyLayout && } - - ); -} - -GridCardGraph.defaultProps = { - onDragSelect: undefined, - onClickHandler: undefined, - isQueryEnabled: true, - threshold: undefined, - headerMenuList: [MenuItemKeys.View], -}; - -export default memo(GridCardGraph); diff --git a/frontend/src/container/GridGraphLayout/GraphLayout.tsx b/frontend/src/container/GridGraphLayout/GraphLayout.tsx deleted file mode 100644 index 6fc6aca63f..0000000000 --- a/frontend/src/container/GridGraphLayout/GraphLayout.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { PlusOutlined, SaveFilled } from '@ant-design/icons'; -import { PANEL_TYPES } from 'constants/queryBuilder'; -import useComponentPermission from 'hooks/useComponentPermission'; -import { useIsDarkMode } from 'hooks/useDarkMode'; -import { Dispatch, SetStateAction } from 'react'; -import { Layout } from 'react-grid-layout'; -import { useSelector } from 'react-redux'; -import { AppState } from 'store/reducers'; -import { Widgets } from 'types/api/dashboard/getAll'; -import AppReducer from 'types/reducer/app'; -import DashboardReducer from 'types/reducer/dashboards'; - -import { LayoutProps, State } from '.'; -import { - Button, - ButtonContainer, - Card, - CardContainer, - ReactGridLayout, -} from './styles'; - -function GraphLayout({ - layouts, - saveLayoutState, - onLayoutSaveHandler, - addPanelLoading, - onAddPanelHandler, - onLayoutChangeHandler, - widgets, - setLayout, -}: GraphLayoutProps): JSX.Element { - const { isAddWidget } = useSelector( - (state) => state.dashboards, - ); - const { role } = useSelector((state) => state.app); - const isDarkMode = useIsDarkMode(); - - const [saveLayoutPermission, addPanelPermission] = useComponentPermission( - ['save_layout', 'add_panel'], - role, - ); - - return ( - <> - - {saveLayoutPermission && ( - - )} - - {addPanelPermission && ( - - )} - - - - {layouts.map(({ Component, ...rest }) => { - const currentWidget = (widgets || [])?.find((e) => e.id === rest.i); - - return ( - - - - - - ); - })} - - - ); -} - -interface GraphLayoutProps { - layouts: LayoutProps[]; - saveLayoutState: State; - onLayoutSaveHandler: (layout: Layout[]) => Promise; - addPanelLoading: boolean; - onAddPanelHandler: VoidFunction; - onLayoutChangeHandler: (layout: Layout[]) => Promise; - widgets: Widgets[] | undefined; - setLayout: Dispatch>; -} - -export default GraphLayout; diff --git a/frontend/src/container/GridGraphLayout/config.ts b/frontend/src/container/GridGraphLayout/config.ts deleted file mode 100644 index 0357c7795c..0000000000 --- a/frontend/src/container/GridGraphLayout/config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { MenuItemKeys } from 'container/GridGraphLayout/WidgetHeader/contants'; - -export const headerMenuList = [ - MenuItemKeys.View, - MenuItemKeys.Clone, - MenuItemKeys.Delete, - MenuItemKeys.Edit, -]; diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx deleted file mode 100644 index fe65154fd9..0000000000 --- a/frontend/src/container/GridGraphLayout/index.tsx +++ /dev/null @@ -1,383 +0,0 @@ -/* eslint-disable react/no-unstable-nested-components */ - -import updateDashboardApi from 'api/dashboard/update'; -import { PANEL_TYPES } from 'constants/queryBuilder'; -import useComponentPermission from 'hooks/useComponentPermission'; -import { useNotifications } from 'hooks/useNotifications'; -import { - Dispatch, - SetStateAction, - useCallback, - useEffect, - useState, -} from 'react'; -import { Layout } from 'react-grid-layout'; -import { useTranslation } from 'react-i18next'; -import { connect, useDispatch, useSelector } from 'react-redux'; -import { bindActionCreators, Dispatch as ReduxDispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { AppDispatch } from 'store'; -import { UpdateTimeInterval } from 'store/actions'; -import { - ToggleAddWidget, - ToggleAddWidgetProps, -} from 'store/actions/dashboard/toggleAddWidget'; -import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import { UPDATE_DASHBOARD } from 'types/actions/dashboard'; -import { Dashboard, Widgets } from 'types/api/dashboard/getAll'; -import AppReducer from 'types/reducer/app'; -import DashboardReducer from 'types/reducer/dashboards'; - -import { headerMenuList } from './config'; -import Graph from './Graph'; -import GraphLayoutContainer from './GraphLayout'; -import { UpdateDashboard } from './utils'; - -export const getPreLayouts = ( - widgets: Widgets[] | undefined, - layout: Layout[], -): LayoutProps[] => - layout.map((e, index) => ({ - ...e, - Component: ({ setLayout }: ComponentProps): JSX.Element => { - const widget = widgets?.find((widget) => widget.id === e.i); - - return ( - - ); - }, - })); - -function GridGraph(props: Props): JSX.Element { - const { toggleAddWidget } = props; - const [addPanelLoading, setAddPanelLoading] = useState(false); - const { t } = useTranslation(['common']); - const { dashboards, isAddWidget } = useSelector( - (state) => state.dashboards, - ); - const { role } = useSelector((state) => state.app); - - const [saveLayoutPermission] = useComponentPermission(['save_layout'], role); - const [saveLayoutState, setSaveLayoutState] = useState({ - loading: false, - error: false, - errorMessage: '', - payload: [], - }); - const [selectedDashboard] = dashboards; - const { data } = selectedDashboard; - const { widgets } = data; - const dispatch: AppDispatch = useDispatch>(); - - const [layouts, setLayout] = useState( - getPreLayouts(widgets, selectedDashboard.data.layout || []), - ); - - const onDragSelect = useCallback( - (start: number, end: number) => { - const startTimestamp = Math.trunc(start); - const endTimestamp = Math.trunc(end); - - if (startTimestamp !== endTimestamp) { - dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp])); - } - }, - [dispatch], - ); - - const { notifications } = useNotifications(); - - useEffect(() => { - (async (): Promise => { - if (!isAddWidget) { - const isEmptyLayoutPresent = layouts.find((e) => e.i === 'empty'); - if (isEmptyLayoutPresent) { - // non empty layout - const updatedLayout = layouts.filter((e) => e.i !== 'empty'); - // non widget - const updatedWidget = widgets?.filter((e) => e.id !== 'empty'); - setLayout(updatedLayout); - - const updatedDashboard: Dashboard = { - ...selectedDashboard, - data: { - ...selectedDashboard.data, - layout: updatedLayout, - widgets: updatedWidget, - }, - }; - - await updateDashboardApi({ - data: updatedDashboard.data, - uuid: updatedDashboard.uuid, - }); - - dispatch({ - type: UPDATE_DASHBOARD, - payload: updatedDashboard, - }); - } - } - })(); - }, [dispatch, isAddWidget, layouts, selectedDashboard, widgets]); - - const { featureResponse } = useSelector( - (state) => state.app, - ); - - const errorMessage = t('common:something_went_wrong'); - - const onLayoutSaveHandler = useCallback( - async (layout: Layout[]) => { - try { - setSaveLayoutState((state) => ({ - ...state, - error: false, - errorMessage: '', - loading: true, - })); - - featureResponse - .refetch() - .then(async () => { - const updatedDashboard: Dashboard = { - ...selectedDashboard, - data: { - title: data.title, - description: data.description, - name: data.name, - tags: data.tags, - widgets: data.widgets, - variables: data.variables, - layout, - }, - uuid: selectedDashboard.uuid, - }; - // Save layout only when users has the has the permission to do so. - if (saveLayoutPermission) { - const response = await updateDashboardApi(updatedDashboard); - if (response.statusCode === 200) { - setSaveLayoutState((state) => ({ - ...state, - error: false, - errorMessage: '', - loading: false, - })); - dispatch({ - type: UPDATE_DASHBOARD, - payload: updatedDashboard, - }); - } else { - setSaveLayoutState((state) => ({ - ...state, - error: true, - errorMessage: response.error || errorMessage, - loading: false, - })); - } - } - }) - .catch(() => { - setSaveLayoutState((state) => ({ - ...state, - error: true, - errorMessage, - loading: false, - })); - notifications.error({ - message: errorMessage, - }); - }); - } catch (error) { - notifications.error({ - message: errorMessage, - }); - } - }, - [ - data.description, - data.name, - data.tags, - data.title, - data.variables, - data.widgets, - dispatch, - errorMessage, - featureResponse, - notifications, - saveLayoutPermission, - selectedDashboard, - ], - ); - - const setLayoutFunction = useCallback( - (layout: Layout[]) => { - setLayout( - layout.map((e) => { - const currentWidget = - widgets?.find((widget) => widget.id === e.i) || ({} as Widgets); - - return { - ...e, - Component: (): JSX.Element => ( - - ), - }; - }), - ); - }, - [widgets, onDragSelect], - ); - - const onEmptyWidgetHandler = useCallback(async () => { - try { - const id = 'empty'; - - const layout = [ - { - i: id, - w: 6, - x: 0, - h: 2, - y: 0, - }, - ...(data.layout || []), - ]; - - await UpdateDashboard( - { - data, - generateWidgetId: id, - graphType: PANEL_TYPES.EMPTY_WIDGET, - selectedDashboard, - layout, - isRedirected: false, - }, - notifications, - ); - - setLayoutFunction(layout); - } catch (error) { - notifications.error({ - message: error instanceof Error ? error.toString() : errorMessage, - }); - } - }, [data, selectedDashboard, setLayoutFunction, notifications, errorMessage]); - - const onLayoutChangeHandler = async (layout: Layout[]): Promise => { - setLayoutFunction(layout); - - // await onLayoutSaveHandler(layout); - }; - - const onAddPanelHandler = useCallback(() => { - try { - setAddPanelLoading(true); - featureResponse - .refetch() - .then(() => { - const isEmptyLayoutPresent = - layouts.find((e) => e.i === 'empty') !== undefined; - - if (!isEmptyLayoutPresent) { - onEmptyWidgetHandler() - .then(() => { - setAddPanelLoading(false); - toggleAddWidget(true); - }) - .catch(() => { - notifications.error({ - message: errorMessage, - }); - }); - } else { - toggleAddWidget(true); - setAddPanelLoading(false); - } - }) - .catch(() => - notifications.error({ - message: errorMessage, - }), - ); - } catch (error) { - notifications.error({ - message: errorMessage, - }); - } - }, [ - featureResponse, - layouts, - onEmptyWidgetHandler, - toggleAddWidget, - notifications, - errorMessage, - ]); - - useEffect( - () => (): void => { - toggleAddWidget(false); - }, - [toggleAddWidget], - ); - - return ( - - ); -} - -interface ComponentProps { - setLayout: Dispatch>; -} - -export interface LayoutProps extends Layout { - Component: (props: ComponentProps) => JSX.Element; -} - -export interface State { - loading: boolean; - error: boolean; - payload: Layout[]; - errorMessage: string; -} - -interface DispatchProps { - toggleAddWidget: ( - props: ToggleAddWidgetProps, - ) => (dispatch: ReduxDispatch) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - toggleAddWidget: bindActionCreators(ToggleAddWidget, dispatch), -}); - -type Props = DispatchProps; - -export default connect(null, mapDispatchToProps)(GridGraph); diff --git a/frontend/src/container/GridGraphLayout/utils.ts b/frontend/src/container/GridGraphLayout/utils.ts deleted file mode 100644 index a18fe52886..0000000000 --- a/frontend/src/container/GridGraphLayout/utils.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { NotificationInstance } from 'antd/es/notification/interface'; -import updateDashboardApi from 'api/dashboard/update'; -import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; -import { Layout } from 'react-grid-layout'; -import store from 'store'; -import { Dashboard, Widgets } from 'types/api/dashboard/getAll'; - -export const UpdateDashboard = async ( - { - data, - graphType, - generateWidgetId, - layout, - selectedDashboard, - isRedirected, - widgetData, - }: UpdateDashboardProps, - notify: NotificationInstance, -): Promise => { - const copyTitle = `${widgetData?.title} - Copy`; - const updatedSelectedDashboard: Dashboard = { - ...selectedDashboard, - data: { - title: data.title, - description: data.description, - name: data.name, - tags: data.tags, - variables: data.variables, - widgets: [ - ...(data.widgets || []), - { - description: widgetData?.description || '', - id: generateWidgetId, - isStacked: false, - nullZeroValues: widgetData?.nullZeroValues || '', - opacity: '', - panelTypes: graphType, - query: widgetData?.query || initialQueriesMap.metrics, - timePreferance: widgetData?.timePreferance || 'GLOBAL_TIME', - title: widgetData ? copyTitle : '', - yAxisUnit: widgetData?.yAxisUnit, - }, - ], - layout, - }, - uuid: selectedDashboard.uuid, - }; - - const response = await updateDashboardApi(updatedSelectedDashboard); - - if (response.payload) { - store.dispatch({ - type: 'UPDATE_DASHBOARD', - payload: response.payload, - }); - } - - if (isRedirected) { - if (response.statusCode === 200) { - return response.payload; - } - notify.error({ - message: response.error || 'Something went wrong', - }); - return undefined; - } - return undefined; -}; - -interface UpdateDashboardProps { - data: Dashboard['data']; - graphType: PANEL_TYPES; - generateWidgetId: string; - layout: Layout[]; - selectedDashboard: Dashboard; - isRedirected: boolean; - widgetData?: Widgets; -} diff --git a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx index 3a7f406b4e..aa658d56cc 100644 --- a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx +++ b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx @@ -9,11 +9,7 @@ import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { generatePath } from 'react-router-dom'; -import { Dispatch } from 'redux'; -import AppActions from 'types/actions'; -import { FLUSH_DASHBOARD } from 'types/actions/dashboard'; import { DashboardData } from 'types/api/dashboard/getAll'; import { EditorContainer, FooterContainer } from './styles'; @@ -31,8 +27,6 @@ function ImportJSON({ ); const [isFeatureAlert, setIsFeatureAlert] = useState(false); - const dispatch = useDispatch>(); - const [dashboardCreating, setDashboardCreating] = useState(false); const [editorValue, setEditorValue] = useState(''); @@ -77,16 +71,11 @@ function ImportJSON({ }); if (response.statusCode === 200) { - dispatch({ - type: FLUSH_DASHBOARD, - }); - setTimeout(() => { - history.push( - generatePath(ROUTES.DASHBOARD, { - dashboardId: response.payload.uuid, - }), - ); - }, 10); + history.push( + generatePath(ROUTES.DASHBOARD, { + dashboardId: response.payload.uuid, + }), + ); } else if (response.error === 'feature usage exceeded') { setIsFeatureAlert(true); notifications.error({ diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx index 39d3c02a23..03c0ac9912 100644 --- a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx @@ -1,37 +1,36 @@ import { ExclamationCircleOutlined } from '@ant-design/icons'; import { Modal } from 'antd'; +import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; +import { useDeleteDashboard } from 'hooks/dashboard/useDeleteDashboard'; import { useCallback } from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { DeleteDashboard, DeleteDashboardProps } from 'store/actions'; -import AppActions from 'types/actions'; +import { useQueryClient } from 'react-query'; import { Data } from '../index'; import { TableLinkText } from './styles'; -function DeleteButton({ - deleteDashboard, - id, - refetchDashboardList, -}: DeleteButtonProps): JSX.Element { +function DeleteButton({ id }: Data): JSX.Element { const [modal, contextHolder] = Modal.useModal(); + const queryClient = useQueryClient(); + + const deleteDashboardMutation = useDeleteDashboard(id); + const openConfirmationDialog = useCallback((): void => { modal.confirm({ title: 'Do you really want to delete this dashboard?', icon: , onOk() { - deleteDashboard({ - uuid: id, - refetch: refetchDashboardList, + deleteDashboardMutation.mutateAsync(undefined, { + onSuccess: () => { + queryClient.invalidateQueries([REACT_QUERY_KEY.GET_ALL_DASHBOARDS]); + }, }); }, okText: 'Delete', okButtonProps: { danger: true }, centered: true, }); - }, [modal, deleteDashboard, id, refetchDashboardList]); + }, [modal, deleteDashboardMutation, queryClient]); return ( <> @@ -44,37 +43,12 @@ function DeleteButton({ ); } -interface DispatchProps { - deleteDashboard: ({ - uuid, - }: DeleteDashboardProps) => (dispatch: Dispatch) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - deleteDashboard: bindActionCreators(DeleteDashboard, dispatch), -}); - -export type DeleteButtonProps = Data & DispatchProps; - -const WrapperDeleteButton = connect(null, mapDispatchToProps)(DeleteButton); - // This is to avoid the type collision function Wrapper(props: Data): JSX.Element { - const { - createdBy, - description, - id, - key, - refetchDashboardList, - lastUpdatedTime, - name, - tags, - } = props; + const { createdBy, description, id, key, lastUpdatedTime, name, tags } = props; return ( - ); diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index 6041b8baf4..87f47e54af 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -17,21 +17,11 @@ import SearchFilter from 'container/ListOfDashboard/SearchFilter'; import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard'; import useComponentPermission from 'hooks/useComponentPermission'; import history from 'lib/history'; -import { - Dispatch, - Key, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; +import { Key, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { UseQueryResult } from 'react-query'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; import { generatePath } from 'react-router-dom'; import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import { GET_ALL_DASHBOARD_SUCCESS } from 'types/actions/dashboard'; import { Dashboard } from 'types/api/dashboard/getAll'; import AppReducer from 'types/reducer/app'; import { popupContainer } from 'utils/selectPopupContainer'; @@ -40,9 +30,7 @@ import ImportJSON from './ImportJSON'; import { ButtonContainer, NewDashboardButton, TableContainer } from './styles'; import Createdby from './TableComponents/CreatedBy'; import DateComponent from './TableComponents/Date'; -import DeleteButton, { - DeleteButtonProps, -} from './TableComponents/DeleteButton'; +import DeleteButton from './TableComponents/DeleteButton'; import Name from './TableComponents/Name'; import Tags from './TableComponents/Tags'; @@ -53,7 +41,6 @@ function ListOfAllDashboard(): JSX.Element { refetch: refetchDashboardList, } = useGetAllDashboard(); - const dispatch = useDispatch>(); const { role } = useSelector((state) => state.app); const [action, createNewDashboard, newDashboard] = useComponentPermission( @@ -134,31 +121,12 @@ function ListOfAllDashboard(): JSX.Element { title: 'Action', dataIndex: '', width: 40, - render: ({ - createdBy, - description, - id, - key, - lastUpdatedTime, - name, - tags, - }: DeleteButtonProps) => ( - - ), + render: DeleteButton, }); } return tableColumns; - }, [action, refetchDashboardList]); + }, [action]); const data: Data[] = filteredDashboards?.map((e) => ({ @@ -186,10 +154,6 @@ function ListOfAllDashboard(): JSX.Element { }); if (response.statusCode === 200) { - dispatch({ - type: GET_ALL_DASHBOARD_SUCCESS, - payload: [], - }); history.push( generatePath(ROUTES.DASHBOARD, { dashboardId: response.payload.uuid, @@ -210,7 +174,7 @@ function ListOfAllDashboard(): JSX.Element { errorMessage: (error as AxiosError).toString() || 'Something went Wrong', }); } - }, [newDashboardState, t, dispatch]); + }, [newDashboardState, t]); const getText = useCallback(() => { if (!newDashboardState.error && !newDashboardState.loading) { @@ -352,7 +316,6 @@ export interface Data { createdBy: string; lastUpdatedTime: string; id: string; - refetchDashboardList: UseQueryResult['refetch']; } export default ListOfAllDashboard; diff --git a/frontend/src/container/LiveLogs/LiveLogsContainer/index.tsx b/frontend/src/container/LiveLogs/LiveLogsContainer/index.tsx index 49a071d17e..e47ae535a6 100644 --- a/frontend/src/container/LiveLogs/LiveLogsContainer/index.tsx +++ b/frontend/src/container/LiveLogs/LiveLogsContainer/index.tsx @@ -11,11 +11,11 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import useDebouncedFn from 'hooks/useDebouncedFunction'; import { useEventSourceEvent } from 'hooks/useEventSourceEvent'; import { useNotifications } from 'hooks/useNotifications'; +import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload'; import { useEventSource } from 'providers/EventSource'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; -import { prepareQueryRangePayload } from 'store/actions/dashboard/prepareQueryRangePayload'; import { AppState } from 'store/reducers'; import { ILog } from 'types/api/logs/log'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; diff --git a/frontend/src/container/LogsExplorerChart/index.tsx b/frontend/src/container/LogsExplorerChart/index.tsx index a64f8eb382..ec329907f3 100644 --- a/frontend/src/container/LogsExplorerChart/index.tsx +++ b/frontend/src/container/LogsExplorerChart/index.tsx @@ -48,7 +48,7 @@ function LogsExplorerChart({ ) : ( ({ description: '', id: v4(), @@ -17,4 +18,5 @@ export const getWidgetQueryBuilder = ({ query, timePreferance: 'GLOBAL_TIME', title, + yAxisUnit, }); diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx index 83e6da6db2..15af9981c0 100644 --- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx @@ -1,6 +1,6 @@ import { Col } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; -import Graph from 'container/GridGraphLayout/Graph/'; +import Graph from 'container/GridCardLayout/GridCard'; import { databaseCallsAvgDuration, databaseCallsRPS, @@ -65,6 +65,7 @@ function DBCall(): JSX.Element { }, title: GraphTitle.DATABASE_CALLS_RPS, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: 'reqps', }), [servicename, tagFilterItems], ); @@ -83,6 +84,7 @@ function DBCall(): JSX.Element { }, title: GraphTitle.DATABASE_CALLS_AVG_DURATION, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: 'ms', }), [servicename, tagFilterItems], ); @@ -107,7 +109,6 @@ function DBCall(): JSX.Element { { onGraphClickHandler(setSelectedTimeStamp)( ChartEvent, @@ -135,12 +136,12 @@ function DBCall(): JSX.Element { > View Traces + { onGraphClickHandler(setSelectedTimeStamp)( ChartEvent, diff --git a/frontend/src/container/MetricsApplication/Tabs/External.tsx b/frontend/src/container/MetricsApplication/Tabs/External.tsx index 3abe8d4ca4..cda6e9275c 100644 --- a/frontend/src/container/MetricsApplication/Tabs/External.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/External.tsx @@ -1,6 +1,6 @@ import { Col } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; -import Graph from 'container/GridGraphLayout/Graph/'; +import Graph from 'container/GridCardLayout/GridCard'; import { externalCallDuration, externalCallDurationByAddress, @@ -56,6 +56,7 @@ function External(): JSX.Element { }, title: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: '%', }), [servicename, tagFilterItems], ); @@ -80,6 +81,7 @@ function External(): JSX.Element { }, title: GraphTitle.EXTERNAL_CALL_DURATION, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: 'ms', }), [servicename, tagFilterItems], ); @@ -100,6 +102,7 @@ function External(): JSX.Element { }, title: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: 'reqps', }), [servicename, tagFilterItems], ); @@ -120,6 +123,7 @@ function External(): JSX.Element { }, title: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: 'ms', }), [servicename, tagFilterItems], ); @@ -146,7 +150,6 @@ function External(): JSX.Element { { onGraphClickHandler(setSelectedTimeStamp)( ChartEvent, @@ -181,7 +184,6 @@ function External(): JSX.Element { { onGraphClickHandler(setSelectedTimeStamp)( ChartEvent, @@ -217,7 +219,6 @@ function External(): JSX.Element { { onGraphClickHandler(setSelectedTimeStamp)( ChartEvent, @@ -252,7 +253,6 @@ function External(): JSX.Element { { onGraphClickHandler(setSelectedTimeStamp)( ChartEvent, diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx index b7ab171ccd..d67053e1e0 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx @@ -131,6 +131,7 @@ function Application(): JSX.Element { }, title: GraphTitle.RATE_PER_OPS, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: 'ops', }), [servicename, tagFilterItems, topLevelOperationsRoute], ); @@ -151,6 +152,7 @@ function Application(): JSX.Element { }, title: GraphTitle.ERROR_PERCENTAGE, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: '%', }), [servicename, tagFilterItems, topLevelOperationsRoute], ); @@ -222,7 +224,6 @@ function Application(): JSX.Element { topLevelOperationsIsError={topLevelOperationsIsError} name="operations_per_sec" widget={operationPerSecWidget} - yAxisUnit="ops" opName="Rate" /> @@ -267,7 +268,6 @@ function Application(): JSX.Element { topLevelOperationsIsError={topLevelOperationsIsError} name="error_percentage_%" widget={errorPercentageWidget} - yAxisUnit="%" opName="Error" /> diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/ApDex/ApDexMetrics.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/ApDex/ApDexMetrics.tsx index b20613bae7..ade8a1bec3 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/ApDex/ApDexMetrics.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/ApDex/ApDexMetrics.tsx @@ -6,8 +6,8 @@ import { apDexToolTipUrlText, } from 'constants/apDex'; import { PANEL_TYPES } from 'constants/queryBuilder'; -import Graph from 'container/GridGraphLayout/Graph'; -import DisplayThreshold from 'container/GridGraphLayout/WidgetHeader/DisplayThreshold'; +import Graph from 'container/GridCardLayout/GridCard'; +import DisplayThreshold from 'container/GridCardLayout/WidgetHeader/DisplayThreshold'; import { GraphTitle } from 'container/MetricsApplication/constant'; import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries'; @@ -87,7 +87,6 @@ function ApDexMetrics({ widget={apDexMetricsWidget} onDragSelect={onDragSelect} onClickHandler={handleGraphClick('ApDex')} - yAxisUnit="" threshold={threshold} isQueryEnabled={isQueryEnabled} /> diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/ApDex/ApDexTraces.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/ApDex/ApDexTraces.tsx index bf6297785f..1b2e5ba0cd 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/ApDex/ApDexTraces.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/ApDex/ApDexTraces.tsx @@ -1,7 +1,7 @@ // This component is not been used in the application as we support only metrics for ApDex as of now. // This component is been kept for future reference. import { PANEL_TYPES } from 'constants/queryBuilder'; -import Graph from 'container/GridGraphLayout/Graph'; +import Graph from 'container/GridCardLayout/GridCard'; import { GraphTitle } from 'container/MetricsApplication/constant'; import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; import { apDexTracesQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries'; @@ -52,7 +52,6 @@ function ApDexTraces({ widget={apDexTracesWidget} onDragSelect={onDragSelect} onClickHandler={handleGraphClick('ApDex')} - yAxisUnit="" threshold={thresholdValue} isQueryEnabled={isQueryEnabled} /> diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx index 52014ac48b..cb124f545a 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx @@ -1,7 +1,7 @@ import Spinner from 'components/Spinner'; import { FeatureKeys } from 'constants/features'; import { PANEL_TYPES } from 'constants/queryBuilder'; -import Graph from 'container/GridGraphLayout/Graph/'; +import Graph from 'container/GridCardLayout/GridCard'; import { GraphTitle } from 'container/MetricsApplication/constant'; import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; import { latency } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries'; @@ -59,6 +59,7 @@ function ServiceOverview({ }, title: GraphTitle.LATENCY, panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: 'ns', }), [servicename, isSpanMetricEnable, topLevelOperationsRoute, tagFilterItems], ); @@ -93,7 +94,6 @@ function ServiceOverview({ name="service_latency" onDragSelect={onDragSelect} widget={latencyWidget} - yAxisUnit="ns" onClickHandler={handleGraphClick('Service')} isQueryEnabled={isQueryEnabled} /> diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/TopLevelOperations.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/TopLevelOperations.tsx index 903ff3a15f..f71a0f7301 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/TopLevelOperations.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/TopLevelOperations.tsx @@ -2,7 +2,7 @@ import { Typography } from 'antd'; import axios from 'axios'; import Spinner from 'components/Spinner'; import { SOMETHING_WENT_WRONG } from 'constants/api'; -import Graph from 'container/GridGraphLayout/Graph/'; +import Graph from 'container/GridCardLayout/GridCard'; import { Card, GraphContainer } from 'container/MetricsApplication/styles'; import { Widgets } from 'types/api/dashboard/getAll'; @@ -17,7 +17,6 @@ function TopLevelOperation({ onDragSelect, handleGraphClick, widget, - yAxisUnit, }: TopLevelOperationProps): JSX.Element { return ( @@ -37,7 +36,6 @@ function TopLevelOperation({ name={name} widget={widget} onClickHandler={handleGraphClick(opName)} - yAxisUnit={yAxisUnit} onDragSelect={onDragSelect} /> )} @@ -56,7 +54,6 @@ interface TopLevelOperationProps { onDragSelect: (start: number, end: number) => void; handleGraphClick: (type: string) => ClickHandlerType; widget: Widgets; - yAxisUnit: string; } export default TopLevelOperation; diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperationMetrics.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperationMetrics.tsx index 4ca191b68c..b0385e2ab0 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperationMetrics.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperationMetrics.tsx @@ -7,9 +7,7 @@ import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; import { useNotifications } from 'hooks/useNotifications'; import useResourceAttribute from 'hooks/useResourceAttribute'; import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; -import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import { RowData } from 'lib/query/createTableColumnsFromQuery'; -import { isEmpty } from 'lodash-es'; import { ReactNode, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; @@ -58,10 +56,7 @@ function TopOperationMetrics(): JSX.Element { const updatedQuery = useStepInterval(keyOperationWidget.query); - const isEmptyWidget = useMemo( - () => keyOperationWidget.id === 'empty' || isEmpty(keyOperationWidget), - [keyOperationWidget], - ); + const isEmptyWidget = keyOperationWidget.id === PANEL_TYPES.EMPTY_WIDGET; const { data, isLoading } = useGetQueryRange( { @@ -69,7 +64,7 @@ function TopOperationMetrics(): JSX.Element { graphType: keyOperationWidget?.panelTypes, query: updatedQuery, globalSelectedInterval, - variables: getDashboardVariables(), + variables: {}, }, { queryKey: [ diff --git a/frontend/src/container/MetricsApplication/TopOperationsTable.tsx b/frontend/src/container/MetricsApplication/TopOperationsTable.tsx index e753a965a5..4c895716e7 100644 --- a/frontend/src/container/MetricsApplication/TopOperationsTable.tsx +++ b/frontend/src/container/MetricsApplication/TopOperationsTable.tsx @@ -10,7 +10,7 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import { getErrorRate, navigateToTrace } from './utils'; -function TopOperationsTable(props: TopOperationsTableProps): JSX.Element { +function TopOperationsTable({ data }: TopOperationsTableProps): JSX.Element { const { minTime, maxTime } = useSelector( (state) => state.globalTime, ); @@ -20,8 +20,6 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element { convertRawQueriesToTraceSelectedTags(queries) || [], ); - const { data } = props; - const params = useParams<{ servicename: string }>(); const handleOnClick = (operation: string): void => { diff --git a/frontend/src/container/MetricsApplication/types.ts b/frontend/src/container/MetricsApplication/types.ts index d9a7251745..f87ce66a2a 100644 --- a/frontend/src/container/MetricsApplication/types.ts +++ b/frontend/src/container/MetricsApplication/types.ts @@ -8,6 +8,7 @@ export interface GetWidgetQueryBuilderProps { query: Widgets['query']; title?: ReactNode; panelTypes: Widgets['panelTypes']; + yAxisUnit?: Widgets['yAxisUnit']; } export interface NavigateToTraceProps { diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx index da6e28ccca..c9c1c6dde1 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx +++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx @@ -1,67 +1,97 @@ +import { SOMETHING_WENT_WRONG } from 'constants/api'; import { QueryParams } from 'constants/query'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useNotifications } from 'hooks/useNotifications'; import createQueryParams from 'lib/createQueryParams'; import history from 'lib/history'; -import { CSSProperties, useCallback } from 'react'; -import { connect, useSelector } from 'react-redux'; -import { useLocation } from 'react-router-dom'; -import { bindActionCreators, Dispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { - ToggleAddWidget, - ToggleAddWidgetProps, -} from 'store/actions/dashboard/toggleAddWidget'; -import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import DashboardReducer from 'types/reducer/dashboards'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { CSSProperties } from 'react'; +import { v4 as uuid } from 'uuid'; import menuItems from './menuItems'; import { Card, Container, Text } from './styles'; -function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element { - const { dashboards } = useSelector( - (state) => state.dashboards, - ); +function DashboardGraphSlider(): JSX.Element { + const isDarkMode = useIsDarkMode(); - const { pathname } = useLocation(); + const { + handleToggleDashboardSlider, + layouts, + selectedDashboard, + } = useDashboard(); + + const { data } = selectedDashboard || {}; const { notifications } = useNotifications(); - const [selectedDashboard] = dashboards; - const { data } = selectedDashboard; + const updateDashboardMutation = useUpdateDashboard(); - const onClickHandler = useCallback( - (name: PANEL_TYPES) => (): void => { - try { - const emptyLayout = data.layout?.find((e) => e.i === 'empty'); + const onClickHandler = (name: PANEL_TYPES) => (): void => { + const id = uuid(); - if (emptyLayout === undefined) { - notifications.error({ - message: 'Please click on Add Panel Button', + updateDashboardMutation.mutateAsync( + { + uuid: selectedDashboard?.uuid || '', + data: { + title: data?.title || '', + variables: data?.variables || {}, + description: data?.description || '', + name: data?.name || '', + tags: data?.tags || [], + layout: [ + { + i: id, + w: 6, + x: 0, + h: 2, + y: 0, + }, + ...(layouts.filter((layout) => layout.i !== PANEL_TYPES.EMPTY_WIDGET) || + []), + ], + widgets: [ + ...(data?.widgets || []), + { + id, + title: '', + description: '', + isStacked: false, + nullZeroValues: '', + opacity: '', + panelTypes: name, + query: initialQueriesMap.metrics, + timePreferance: 'GLOBAL_TIME', + }, + ], + }, + }, + { + onSuccess: (data) => { + if (data.payload) { + handleToggleDashboardSlider(false); + + const queryParams = { + graphType: name, + widgetId: id, + [QueryParams.compositeQuery]: JSON.stringify(initialQueriesMap.metrics), + }; + + history.push( + `${history.location.pathname}/new?${createQueryParams(queryParams)}`, + ); + } + }, + onError: () => { + notifications.success({ + message: SOMETHING_WENT_WRONG, }); - return; - } + }, + }, + ); + }; - toggleAddWidget(false); - - const queryParams = { - graphType: name, - widgetId: emptyLayout.i, - [QueryParams.compositeQuery]: JSON.stringify(initialQueriesMap.metrics), - }; - - history.push(`${pathname}/new?${createQueryParams(queryParams)}`); - } catch (error) { - notifications.error({ - message: 'Something went wrong', - }); - } - }, - [data, toggleAddWidget, notifications, pathname], - ); - const isDarkMode = useIsDarkMode(); const fillColor: CSSProperties['color'] = isDarkMode ? 'white' : 'black'; return ( @@ -76,18 +106,4 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element { ); } -interface DispatchProps { - toggleAddWidget: ( - props: ToggleAddWidgetProps, - ) => (dispatch: Dispatch) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - toggleAddWidget: bindActionCreators(ToggleAddWidget, dispatch), -}); - -type Props = DispatchProps; - -export default connect(null, mapDispatchToProps)(DashboardGraphSlider); +export default DashboardGraphSlider; diff --git a/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx index a8f1892fa4..3f6eec23b4 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx @@ -1,33 +1,23 @@ import { SaveOutlined } from '@ant-design/icons'; import { Col, Divider, Input, Space, Typography } from 'antd'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; import AddTags from 'container/NewDashboard/DashboardSettings/General/AddTags'; -import { useCallback, useState } from 'react'; +import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; +import { useNotifications } from 'hooks/useNotifications'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { connect, useSelector } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { - UpdateDashboardTitleDescriptionTags, - UpdateDashboardTitleDescriptionTagsProps, -} from 'store/actions'; -import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import DashboardReducer from 'types/reducer/dashboards'; import { Button } from './styles'; -function GeneralDashboardSettings({ - updateDashboardTitleDescriptionTags, -}: DescriptionOfDashboardProps): JSX.Element { - const { dashboards } = useSelector( - (state) => state.dashboards, - ); +function GeneralDashboardSettings(): JSX.Element { + const { selectedDashboard, setSelectedDashboard } = useDashboard(); - const [selectedDashboard] = dashboards; - const selectedData = selectedDashboard.data; - const { title } = selectedData; - const { tags } = selectedData; - const { description } = selectedData; + const updateDashboardMutation = useUpdateDashboard(); + + const selectedData = selectedDashboard?.data; + + const { title = '', tags = [], description = '' } = selectedData || {}; const [updatedTitle, setUpdatedTitle] = useState(title); const [updatedTags, setUpdatedTags] = useState(tags || []); @@ -37,27 +27,35 @@ function GeneralDashboardSettings({ const { t } = useTranslation('common'); - const onSaveHandler = useCallback(() => { - const dashboard = selectedDashboard; - // @TODO need to update this function to take title,description,tags only - updateDashboardTitleDescriptionTags({ - dashboard: { - ...dashboard, + const { notifications } = useNotifications(); + + const onSaveHandler = (): void => { + if (!selectedDashboard) return; + + updateDashboardMutation.mutateAsync( + { + ...selectedDashboard, data: { - ...dashboard.data, + ...selectedDashboard.data, description: updatedDescription, tags: updatedTags, title: updatedTitle, }, }, - }); - }, [ - updatedTitle, - updatedTags, - updatedDescription, - selectedDashboard, - updateDashboardTitleDescriptionTags, - ]); + { + onSuccess: (updatedDashboard) => { + if (updatedDashboard.payload) { + setSelectedDashboard(updatedDashboard.payload); + } + }, + onError: () => { + notifications.error({ + message: SOMETHING_WENT_WRONG, + }); + }, + }, + ); + }; return ( @@ -83,7 +81,13 @@ function GeneralDashboardSettings({
-
@@ -92,21 +96,4 @@ function GeneralDashboardSettings({ ); } -interface DispatchProps { - updateDashboardTitleDescriptionTags: ( - props: UpdateDashboardTitleDescriptionTagsProps, - ) => (dispatch: Dispatch) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - updateDashboardTitleDescriptionTags: bindActionCreators( - UpdateDashboardTitleDescriptionTags, - dispatch, - ), -}); - -type DescriptionOfDashboardProps = DispatchProps; - -export default connect(null, mapDispatchToProps)(GeneralDashboardSettings); +export default GeneralDashboardSettings; diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx index 3f90bf565b..388dffc286 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx @@ -1,39 +1,28 @@ import { blue, red } from '@ant-design/colors'; import { PlusOutlined } from '@ant-design/icons'; import { Button, Modal, Row, Space, Tag } from 'antd'; -import { NotificationInstance } from 'antd/es/notification/interface'; import { ResizeTable } from 'components/ResizeTable'; +import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useNotifications } from 'hooks/useNotifications'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useRef, useState } from 'react'; -import { connect, useSelector } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { UpdateDashboardVariables } from 'store/actions/dashboard/updatedDashboardVariables'; -import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import { IDashboardVariable } from 'types/api/dashboard/getAll'; -import DashboardReducer from 'types/reducer/dashboards'; +import { useTranslation } from 'react-i18next'; +import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll'; import { TVariableViewMode } from './types'; import VariableItem from './VariableItem/VariableItem'; -function VariablesSetting({ - updateDashboardVariables, -}: DispatchProps): JSX.Element { +function VariablesSetting(): JSX.Element { const variableToDelete = useRef(null); const [deleteVariableModal, setDeleteVariableModal] = useState(false); - const { dashboards } = useSelector( - (state) => state.dashboards, - ); + const { t } = useTranslation(['dashboard']); + + const { selectedDashboard, setSelectedDashboard } = useDashboard(); const { notifications } = useNotifications(); - const [selectedDashboard] = dashboards; - - const { - data: { variables = {} }, - } = selectedDashboard; + const { variables = {} } = selectedDashboard?.data || {}; const variablesTableData = Object.keys(variables).map((variableName) => ({ key: variableName, @@ -64,6 +53,41 @@ function VariablesSetting({ setVariableViewMode(viewType); }; + const updateMutation = useUpdateDashboard(); + + const updateVariables = ( + updatedVariablesData: Dashboard['data']['variables'], + ): void => { + if (!selectedDashboard) { + return; + } + + updateMutation.mutateAsync( + { + ...selectedDashboard, + data: { + ...selectedDashboard.data, + variables: updatedVariablesData, + }, + }, + { + onSuccess: (updatedDashboard) => { + if (updatedDashboard.payload) { + setSelectedDashboard(updatedDashboard.payload); + notifications.success({ + message: t('variable_updated_successfully'), + }); + } + }, + onError: () => { + notifications.error({ + message: t('error_while_updating_variable'), + }); + }, + }, + ); + }; + const onVariableSaveHandler = ( name: string, variableData: IDashboardVariable, @@ -79,7 +103,7 @@ function VariablesSetting({ if (oldName) { delete newVariables[oldName]; } - updateDashboardVariables(newVariables, notifications); + updateVariables(newVariables); onDoneVariableViewMode(); }; @@ -91,7 +115,7 @@ function VariablesSetting({ const handleDeleteConfirm = (): void => { const newVariables = { ...variables }; if (variableToDelete?.current) delete newVariables[variableToDelete?.current]; - updateDashboardVariables(newVariables, notifications); + updateVariables(newVariables); variableToDelete.current = null; setDeleteVariableModal(false); }; @@ -182,20 +206,4 @@ function VariablesSetting({ ); } -interface DispatchProps { - updateDashboardVariables: ( - props: Record, - notify: NotificationInstance, - ) => (dispatch: Dispatch) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - updateDashboardVariables: bindActionCreators( - UpdateDashboardVariables, - dispatch, - ), -}); - -export default connect(null, mapDispatchToProps)(VariablesSetting); +export default VariablesSetting; diff --git a/frontend/src/container/NewDashboard/DashboardSettings/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/index.tsx index 50a69495fa..5a1bc6afac 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/index.tsx @@ -3,12 +3,12 @@ import { Tabs } from 'antd'; import GeneralDashboardSettings from './General'; import VariablesSetting from './Variables'; -function DashboardSettingsContent(): JSX.Element { - const items = [ - { label: 'General', key: 'general', children: }, - { label: 'Variables', key: 'variables', children: }, - ]; +const items = [ + { label: 'General', key: 'general', children: }, + { label: 'Variables', key: 'variables', children: }, +]; +function DashboardSettingsContent(): JSX.Element { return ; } diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx index d318e684f8..561af111ae 100644 --- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx @@ -1,34 +1,25 @@ import { Row } from 'antd'; -import { NotificationInstance } from 'antd/es/notification/interface'; +import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useNotifications } from 'hooks/useNotifications'; import { map, sortBy } from 'lodash-es'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useState } from 'react'; -import { connect, useSelector } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { UpdateDashboardVariables } from 'store/actions/dashboard/updatedDashboardVariables'; +import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import { IDashboardVariable } from 'types/api/dashboard/getAll'; +import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll'; import AppReducer from 'types/reducer/app'; -import DashboardReducer from 'types/reducer/dashboards'; import VariableItem from './VariableItem'; -function DashboardVariableSelection({ - updateDashboardVariables, -}: DispatchProps): JSX.Element { - const { dashboards } = useSelector( - (state) => state.dashboards, - ); - const [selectedDashboard] = dashboards; - const { - data: { variables = {} }, - } = selectedDashboard; +function DashboardVariableSelection(): JSX.Element | null { + const { selectedDashboard, setSelectedDashboard } = useDashboard(); + + const { data } = selectedDashboard || {}; + + const { variables } = data || {}; const [update, setUpdate] = useState(false); const [lastUpdatedVar, setLastUpdatedVar] = useState(''); - const { notifications } = useNotifications(); const { role } = useSelector((state) => state.app); @@ -37,6 +28,42 @@ function DashboardVariableSelection({ setUpdate(!update); }; + const updateMutation = useUpdateDashboard(); + const { notifications } = useNotifications(); + + const updateVariables = ( + updatedVariablesData: Dashboard['data']['variables'], + ): void => { + if (!selectedDashboard) { + return; + } + + updateMutation.mutateAsync( + { + ...selectedDashboard, + data: { + ...selectedDashboard.data, + variables: updatedVariablesData, + }, + }, + { + onSuccess: (updatedDashboard) => { + if (updatedDashboard.payload) { + setSelectedDashboard(updatedDashboard.payload); + notifications.success({ + message: 'Variable updated successfully', + }); + } + }, + onError: () => { + notifications.error({ + message: 'Error while updating variable', + }); + }, + }, + ); + }; + const onValueUpdate = ( name: string, value: IDashboardVariable['selectedValue'], @@ -44,8 +71,8 @@ function DashboardVariableSelection({ const updatedVariablesData = { ...variables }; updatedVariablesData[name].selectedValue = value; - if (role !== 'VIEWER') { - updateDashboardVariables(updatedVariablesData, notifications); + if (role !== 'VIEWER' && selectedDashboard) { + updateVariables(updatedVariablesData); } onVarChanged(name); @@ -58,13 +85,17 @@ function DashboardVariableSelection({ updatedVariablesData[name].allSelected = value; if (role !== 'VIEWER') { - updateDashboardVariables(updatedVariablesData, notifications); + updateVariables(updatedVariablesData); } onVarChanged(name); }; + if (!variables) { + return null; + } + return ( - + {map(sortBy(Object.keys(variables)), (variableName) => ( ))} @@ -83,20 +114,4 @@ function DashboardVariableSelection({ ); } -interface DispatchProps { - updateDashboardVariables: ( - props: Parameters[0], - notify: NotificationInstance, - ) => (dispatch: Dispatch) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - updateDashboardVariables: bindActionCreators( - UpdateDashboardVariables, - dispatch, - ), -}); - -export default connect(null, mapDispatchToProps)(DashboardVariableSelection); +export default DashboardVariableSelection; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx index 72a10a3f30..fc51efd69a 100644 --- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx +++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx @@ -6,7 +6,7 @@ import DashboardSettingsContent from '../DashboardSettings'; import { DrawerContainer } from './styles'; function SettingsDrawer(): JSX.Element { - const [visible, setVisible] = useState(false); // TODO Make it False + const [visible, setVisible] = useState(false); const showDrawer = (): void => { setVisible(true); @@ -25,7 +25,7 @@ function SettingsDrawer(): JSX.Element { placement="right" width="70%" onClose={onClose} - visible={visible} + open={visible} > diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx index 2faafd7cbb..3c6ca326a3 100644 --- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx +++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx @@ -1,25 +1,22 @@ import { ShareAltOutlined } from '@ant-design/icons'; import { Button, Card, Col, Row, Space, Tag, Typography } from 'antd'; import useComponentPermission from 'hooks/useComponentPermission'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; -import DashboardReducer from 'types/reducer/dashboards'; import DashboardVariableSelection from '../DashboardVariablesSelection'; import SettingsDrawer from './SettingsDrawer'; import ShareModal from './ShareModal'; function DescriptionOfDashboard(): JSX.Element { - const { dashboards } = useSelector( - (state) => state.dashboards, - ); + const { selectedDashboard } = useDashboard(); - const [selectedDashboard] = dashboards; - const selectedData = selectedDashboard.data; - const { title, tags, description } = selectedData; + const selectedData = selectedDashboard?.data; + const { title, tags, description } = selectedData || {}; const [isJSONModalVisible, isIsJSONModalVisible] = useState(false); @@ -34,26 +31,29 @@ function DescriptionOfDashboard(): JSX.Element { return ( - + {title} {description} +
- {tags?.map((e) => ( - {e} + {tags?.map((tag) => ( + {tag} ))}
+ - + {selectedData && ( + + )} + {editDashboard && }