diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f5f5e5610adc..1d8d4e7b70a7 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 14a0c127aae4..be02f3bb8253 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 b624a90b9f06..3a38338cf025 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 2b0c849ffa2c..2a79618d1203 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 053a8733dca7..be454590f3ab 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 71061bfc73d8..770a2f4df3dd 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 d6c05dfd6f1e..9ad3ef431351 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 98ee1e0fc4ca..f8eb00588313 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 @@ -42,6 +46,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 @@ -49,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 @@ -77,6 +90,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 @@ -84,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 @@ -128,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 742768525fb8..8c62c12d1b95 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/.github/workflows/staging-deployment.yaml b/.github/workflows/staging-deployment.yaml index 6de51f473398..21ea7a3c759e 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 d65a4e8bbca2..799222ee3e37 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 7d976341c1fa..5213c4597ae6 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 @@ -37,10 +38,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 +62,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 "------------------" @@ -57,24 +70,52 @@ build-push-frontend: 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) @@ -82,24 +123,14 @@ build-ee-query-service-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 + 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: @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) . + make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-push-query-service dev-setup: mkdir -p /var/lib/signoz @@ -110,7 +141,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 @@ -153,3 +184,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/README.zh-cn.md b/README.zh-cn.md index aaa89551bfbf..32b6328fcb9c 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -1,170 +1,225 @@ -
-
+
-
监视你的应用,并可排查已部署应用中的问题,这是一个开源的可替代DataDog、NewRelic的方案
+监控你的应用,并且可排查已部署应用的问题,这是一个可替代 DataDog、NewRelic 的开源方案
-## +
+
+
+
+### 日志管理
+
+
+
+### 基础设施监控
+
+
+
+### 异常监控
+
+
+
+### 告警
+
+
 
+### 使用 Helm 在 Kubernetes 部署 -### 使用Helm在Kubernetes上部署 - -请跟着[这里](https://signoz.io/docs/deployment/helm_chart)的步骤使用helm charts安装 +请一步步跟随 [这里](https://signoz.io/docs/deployment/helm_chart) 通过 helm 来安装 
### 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)
@@ -40,4 +48,54 @@ function Code({
);
}
-export { Code, Pre };
+function Link({ href, children }: LinkProps): JSX.Element {
+ return (
+
+ {children}
+
+ );
+}
+
+const interpolateMarkdown = (
+ markdownContent: any,
+ variables: { [s: string]: unknown } | ArrayLike,
+) => {
+ let interpolatedContent = markdownContent;
+
+ const variableEntries = Object.entries(variables);
+
+ // Loop through variables and replace placeholders with values
+ for (const [key, value] of variableEntries) {
+ const placeholder = `{{${key}}}`;
+ const regex = new RegExp(placeholder, 'g');
+ interpolatedContent = interpolatedContent.replace(regex, value);
+ }
+
+ return interpolatedContent;
+};
+
+function MarkdownRenderer({
+ markdownContent,
+ variables,
+}: {
+ markdownContent: any;
+ variables: any;
+}): JSX.Element {
+ const interpolatedMarkdown = interpolateMarkdown(markdownContent, variables);
+
+ return (
+
+ {interpolatedMarkdown}
+
+ );
+}
+
+export { Code, Link, MarkdownRenderer, Pre };
diff --git a/frontend/src/components/TextToolTip/TextToolTip.style.scss b/frontend/src/components/TextToolTip/TextToolTip.style.scss
new file mode 100644
index 000000000000..192d98264cfe
--- /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 000000000000..6c8fad783ed0
--- /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 72f631a872df..c40a841fd07c 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;
diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts
index 1f984ebd4613..ec55889516d2 100644
--- a/frontend/src/constants/reactQueryKeys.ts
+++ b/frontend/src/constants/reactQueryKeys.ts
@@ -3,5 +3,8 @@ 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',
+ LOGS_PIPELINE_PREVIEW: 'LOGS_PIPELINE_PREVIEW',
};
diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts
index c910d0d25e00..b156036ce4f1 100644
--- a/frontend/src/constants/routes.ts
+++ b/frontend/src/constants/routes.ts
@@ -24,6 +24,7 @@ const ROUTES = {
VERSION: '/status',
MY_SETTINGS: '/my-settings',
ORG_SETTINGS: '/settings/org-settings',
+ INGESTION_SETTINGS: '/settings/ingestion-settings',
SOMETHING_WENT_WRONG: '/something-went-wrong',
UN_AUTHORIZED: '/un-authorized',
NOT_FOUND: '/not-found',
diff --git a/frontend/src/container/CreateAlertRule/defaults.ts b/frontend/src/container/CreateAlertRule/defaults.ts
index 2ac2f3a7b88b..8517d9b18c8d 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,
diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx
index f6bf35cbd72f..c00b78b44980 100644
--- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx
+++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx
@@ -25,6 +25,7 @@ export interface ChartPreviewProps {
headline?: JSX.Element;
alertDef?: AlertDef;
userQueryKey?: string;
+ allowSelectedIntervalForStepGen?: boolean;
}
function ChartPreview({
@@ -35,6 +36,7 @@ function ChartPreview({
selectedInterval = '5min',
headline,
userQueryKey,
+ allowSelectedIntervalForStepGen = false,
alertDef,
}: ChartPreviewProps): JSX.Element | null {
const { t } = useTranslation('alerts');
@@ -89,6 +91,9 @@ function ChartPreview({
globalSelectedInterval: selectedInterval,
graphType,
selectedTime,
+ params: {
+ allowSelectedIntervalForStepGen,
+ },
},
{
queryKey: [
@@ -127,7 +132,7 @@ function ChartPreview({
);
+ const updatedStagedQuery = useMemo((): Query | null => {
+ const newQuery: Query | null = stagedQuery;
+ if (newQuery) {
+ newQuery.builder.queryData[0].stepInterval = getUpdatedStepInterval(
+ alertDef.evalWindow,
+ );
+ }
+ return newQuery;
+ }, [alertDef.evalWindow, stagedQuery]);
+
const renderQBChartPreview = (): JSX.Element => (
}
name=""
- query={stagedQuery}
+ query={updatedStagedQuery}
selectedInterval={toChartInterval(alertDef.evalWindow)}
alertDef={alertDef}
+ allowSelectedIntervalForStepGen
/>
);
diff --git a/frontend/src/container/FormAlertRules/utils.test.ts b/frontend/src/container/FormAlertRules/utils.test.ts
new file mode 100644
index 000000000000..49acf94bc1fa
--- /dev/null
+++ b/frontend/src/container/FormAlertRules/utils.test.ts
@@ -0,0 +1,14 @@
+// Write a test for getUpdatedStepInterval function in src/container/FormAlertRules/utils.ts
+
+import { getUpdatedStepInterval } from './utils';
+
+describe('getUpdatedStepInterval', () => {
+ it('should return 60', () => {
+ const result = getUpdatedStepInterval('5m0s');
+ expect(result).toEqual(60);
+ });
+ it('should return 60 for 10m0s', () => {
+ const result = getUpdatedStepInterval('10m0s');
+ expect(result).toEqual(60);
+ });
+});
diff --git a/frontend/src/container/FormAlertRules/utils.ts b/frontend/src/container/FormAlertRules/utils.ts
index 67042569a0c3..3734474c2972 100644
--- a/frontend/src/container/FormAlertRules/utils.ts
+++ b/frontend/src/container/FormAlertRules/utils.ts
@@ -1,4 +1,6 @@
import { Time } from 'container/TopNav/DateTimeSelection/config';
+import getStartEndRangeTime from 'lib/getStartEndRangeTime';
+import getStep from 'lib/getStep';
// toChartInterval converts eval window to chart selection time interval
export const toChartInterval = (evalWindow: string | undefined): Time => {
@@ -21,3 +23,15 @@ export const toChartInterval = (evalWindow: string | undefined): Time => {
return '5min';
}
};
+
+export const getUpdatedStepInterval = (evalWindow?: string): number => {
+ const { start, end } = getStartEndRangeTime({
+ type: 'GLOBAL_TIME',
+ interval: toChartInterval(evalWindow),
+ });
+ return getStep({
+ start,
+ end,
+ inputFormat: 'ns',
+ });
+};
diff --git a/frontend/src/container/GridGraphLayout/EmptyWidget/index.tsx b/frontend/src/container/GridCardLayout/EmptyWidget/index.tsx
similarity index 100%
rename from frontend/src/container/GridGraphLayout/EmptyWidget/index.tsx
rename to frontend/src/container/GridCardLayout/EmptyWidget/index.tsx
diff --git a/frontend/src/container/GridGraphLayout/EmptyWidget/styles.ts b/frontend/src/container/GridCardLayout/EmptyWidget/styles.ts
similarity index 100%
rename from frontend/src/container/GridGraphLayout/EmptyWidget/styles.ts
rename to frontend/src/container/GridCardLayout/EmptyWidget/styles.ts
diff --git a/frontend/src/container/GridCardLayout/GridCard/FullView/GraphManager.styles.scss b/frontend/src/container/GridCardLayout/GridCard/FullView/GraphManager.styles.scss
new file mode 100644
index 000000000000..2d594aa8a917
--- /dev/null
+++ b/frontend/src/container/GridCardLayout/GridCard/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/GridCardLayout/GridCard/FullView/GraphManager.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/GraphManager.tsx
new file mode 100644
index 000000000000..e6c6e26c0ba7
--- /dev/null
+++ b/frontend/src/container/GridCardLayout/GridCard/FullView/GraphManager.tsx
@@ -0,0 +1,136 @@
+import './GraphManager.styles.scss';
+
+import { Button, Input } from 'antd';
+import { CheckboxChangeEvent } from 'antd/es/checkbox';
+import { ResizeTable } from 'components/ResizeTable';
+import { useNotifications } from 'hooks/useNotifications';
+import { memo, useCallback, useState } from 'react';
+
+import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns';
+import { ExtendedChartDataset, GraphManagerProps } from './types';
+import {
+ getDefaultTableDataSet,
+ saveLegendEntriesToLocalStorage,
+} from './utils';
+
+function GraphManager({
+ data,
+ name,
+ yAxisUnit,
+ onToggleModelHandler,
+ setGraphsVisibilityStates,
+ graphsVisibilityStates = [],
+ lineChartRef,
+ parentChartRef,
+}: GraphManagerProps): JSX.Element {
+ const [tableDataSet, setTableDataSet] = useState(
+ 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 22ae630bb828..eda971c1e4e8 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 cc10f83f0046..3702a9b3e03a 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 1437a2915787..02391bdc0c56 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 72%
rename from frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts
rename to frontend/src/container/GridCardLayout/GridCard/FullView/styles.ts
index 9e5bd095411e..b73a2e9112a6 100644
--- a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts
+++ b/frontend/src/container/GridCardLayout/GridCard/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;
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 7d329e13992a..ae686496e517 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 256bc390505b..b1ffb3a032c2 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 000000000000..c011ca2471ec
--- /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 000000000000..598f4dd7081e
--- /dev/null
+++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx
@@ -0,0 +1,134 @@
+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,
+ widget.timePreferance,
+ ],
+ 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 49b637a17828..fccf488dc8ce 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 000000000000..b99f2396c96f
--- /dev/null
+++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx
@@ -0,0 +1,141 @@
+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,
+ setSelectedDashboard,
+ } = 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: (updatedDashboard) => {
+ if (updatedDashboard.payload) {
+ if (updatedDashboard.payload.data.layout)
+ setLayouts(updatedDashboard.payload.data.layout);
+ setSelectedDashboard(updatedDashboard.payload);
+ }
+ notifications.success({
+ message: t('dashboard:layout_saved_successfully'),
+ });
+
+ featureResponse.refetch();
+ },
+ onError: () => {
+ notifications.error({
+ message: SOMETHING_WENT_WRONG,
+ });
+ },
+ });
+ };
+
+ return (
+ <>
+
+ {saveLayoutPermission && (
+ }
+ disabled={updateDashboardMutation.isLoading}
+ >
+ {t('dashboard:save_layout')}
+
+ )}
+
+ {addPanelPermission && (
+ }>
+ {t('dashboard:add_panel')}
+
+ )}
+
+
+
+ {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 57%
rename from frontend/src/container/GridGraphLayout/WidgetHeader/config.ts
rename to frontend/src/container/GridCardLayout/WidgetHeader/config.ts
index 1e15697049e5..2a8f2d76ee5c 100644
--- a/frontend/src/container/GridGraphLayout/WidgetHeader/config.ts
+++ b/frontend/src/container/GridCardLayout/WidgetHeader/config.ts
@@ -1,9 +1,14 @@
import { themeColors } from 'constants/theme';
+import { limit } from 'lib/getChartData';
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 +26,5 @@ 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 ${limit}. Please check the query if this is needed and contact support@signoz.io if you need to show >${limit} 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 20ad222e5520..a916534082e4 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 000000000000..3fa9e8e5697c
--- /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 000000000000..e715d7d53937
--- /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 000000000000..0d2b678af6f5
--- /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 61abbf2aa6a6..000000000000
--- a/frontend/src/container/GridGraphLayout/Graph/FullView/GraphManager.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-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 {
- FilterTableAndSaveContainer,
- FilterTableContainer,
- SaveCancelButtonContainer,
- SaveContainer,
-} from './styles';
-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 55485be5adf7..000000000000
--- 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 f40d1e41907f..000000000000
--- 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 94b9d10252ec..000000000000
--- 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 6fc6aca63f69..000000000000
--- 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 && (
- }
- >
- Add Panel
-
- )}
-
-
-
- {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 0357c7795cd9..000000000000
--- 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 fe65154fd911..000000000000
--- 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 a18fe52886bc..000000000000
--- 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/IngestionSettings/IngestionSettings.styles.scss b/frontend/src/container/IngestionSettings/IngestionSettings.styles.scss
new file mode 100644
index 000000000000..3d5f41ab33b5
--- /dev/null
+++ b/frontend/src/container/IngestionSettings/IngestionSettings.styles.scss
@@ -0,0 +1,3 @@
+.ingestion-settings-container {
+ color: white;
+}
diff --git a/frontend/src/container/IngestionSettings/IngestionSettings.tsx b/frontend/src/container/IngestionSettings/IngestionSettings.tsx
new file mode 100644
index 000000000000..0971ecc96069
--- /dev/null
+++ b/frontend/src/container/IngestionSettings/IngestionSettings.tsx
@@ -0,0 +1,82 @@
+import './IngestionSettings.styles.scss';
+
+import { Table, Typography } from 'antd';
+import type { ColumnsType } from 'antd/es/table';
+import getIngestionData from 'api/settings/getIngestionData';
+import { useQuery } from 'react-query';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
+import { IngestionDataType } from 'types/api/settings/ingestion';
+import AppReducer from 'types/reducer/app';
+
+export default function IngestionSettings(): JSX.Element {
+ const { user } = useSelector((state) => state.app);
+
+ const { data: ingestionData } = useQuery({
+ queryFn: getIngestionData,
+ queryKey: ['getIngestionData', user?.userId],
+ });
+
+ const columns: ColumnsType = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ render: (text): JSX.Element => {text} ,
+ },
+ {
+ title: 'Value',
+ dataIndex: 'value',
+ key: 'value',
+ render: (text): JSX.Element => (
+ {text}
+ ),
+ },
+ ];
+
+ const injectionDataPayload =
+ ingestionData &&
+ ingestionData.payload &&
+ Array.isArray(ingestionData.payload) &&
+ ingestionData?.payload[0];
+
+ const data: IngestionDataType[] = [
+ {
+ key: '1',
+ name: 'Ingestion URL',
+ value: injectionDataPayload?.ingestionURL,
+ },
+ {
+ key: '2',
+ name: 'Ingestion Key',
+ value: injectionDataPayload?.ingestionKey,
+ },
+ {
+ key: '3',
+ name: 'Ingestion Region',
+ value: injectionDataPayload?.dataRegion,
+ },
+ ];
+
+ return (
+
+
+ You can use the following ingestion credentials to start sending your
+ telemetry data to SigNoz
+
+
+
+
+ );
+}
diff --git a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx
index 3a7f406b4ec4..aa658d56cc3b 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 39d3c02a235e..03c0ac9912e1 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 6041b8baf4dd..87f47e54af7e 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 49a071d17e27..e47ae535a65d 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/LogDetailedView/TableView.tsx b/frontend/src/container/LogDetailedView/TableView.tsx
index 28910afbde2c..17446b011866 100644
--- a/frontend/src/container/LogDetailedView/TableView.tsx
+++ b/frontend/src/container/LogDetailedView/TableView.tsx
@@ -21,7 +21,12 @@ import { ILog } from 'types/api/logs/log';
import ActionItem, { ActionItemProps } from './ActionItem';
import FieldRenderer from './FieldRenderer';
-import { flattenObject, jsonToDataNodes, recursiveParseJSON } from './utils';
+import {
+ flattenObject,
+ jsonToDataNodes,
+ recursiveParseJSON,
+ removeEscapeCharacters,
+} from './utils';
// Fields which should be restricted from adding it to query
const RESTRICTED_FIELDS = ['timestamp'];
@@ -58,7 +63,7 @@ function TableView({
.map((key) => ({
key,
field: key,
- value: JSON.stringify(flattenLogData[key]),
+ value: removeEscapeCharacters(JSON.stringify(flattenLogData[key])),
}));
const onTraceHandler = (record: DataType) => (): void => {
@@ -164,6 +169,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 +181,7 @@ function TableView({
}
return (
-
+
{field}
);
diff --git a/frontend/src/container/LogDetailedView/config.ts b/frontend/src/container/LogDetailedView/config.ts
new file mode 100644
index 000000000000..cd3402369957
--- /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 4f080e23c13b..d5918f2bcae0 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 02890e7dc9a9..f31534ace847 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,59 @@ 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);
+};
+
+export const removeEscapeCharacters = (str: string): string =>
+ str.replace(/\\([ntfr'"\\])/g, (_: string, char: string) => {
+ const escapeMap: Record = {
+ n: '\n',
+ t: '\t',
+ f: '\f',
+ r: '\r',
+ "'": "'",
+ '"': '"',
+ '\\': '\\',
+ };
+ return escapeMap[char as keyof typeof escapeMap];
+ });
diff --git a/frontend/src/container/LogsExplorerChart/index.tsx b/frontend/src/container/LogsExplorerChart/index.tsx
index a64f8eb3820b..ec329907f3c3 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 83e6da6db282..15af9981c0bc 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 3abe8d4ca400..cda6e9275c80 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 b7ab171ccd36..d67053e1e0b9 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 b20613bae7ee..ade8a1bec35e 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 bf6297785fa8..1b2e5ba0cd8d 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 52014ac48b4f..cb124f545a07 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 903ff3a15fff..f71a0f730103 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 4ca191b68c4f..b0385e2ab0e7 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 e753a965a56f..4c895716e765 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 d9a7251745d7..f87ce66a2ace 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 da6e28cccafb..c9c1c6dde157 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 a8f1892fa405..3f6eec23b496 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({
- } onClick={onSaveHandler} type="primary">
+ }
+ onClick={onSaveHandler}
+ type="primary"
+ >
{t('save')}
@@ -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 3f90bf565b18..388dffc2865f 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 50a69495faac..5a1bc6afac3a 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 d318e684f8fd..561af111ae76 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 72a10a3f30f7..fc51efd69a4e 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 2faafd7cbb3f..3c6ca326a327 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 && }