diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c37e2f3284..afd70250c4 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -2,5 +2,5 @@
# Owners are automatically requested for review for PRs that changes code
# that they own.
* @ankitnayan
-/frontend/ @palash-signoz
-/deploy/ @prashant-shahi
\ No newline at end of file
+/frontend/ @palash-signoz @pranshuchittora
+/deploy/ @prashant-shahi
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 5e659eb8e5..1c959c1888 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -26,6 +26,7 @@ assignees: ''
* **Signoz version**:
* **Browser version**:
* **Your OS and version**:
+* **Your CPU Architecture**(ARM/Intel):
## Additional context
diff --git a/.gitpod.yml b/.gitpod.yml
index db4801ba35..1771de8779 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -11,7 +11,7 @@ tasks:
- name: Run Docker Images
init: |
cd ./deploy
- sudo docker-compose --env-file ./docker/clickhouse-setup/env/x86_64.env -f docker/clickhouse-setup/docker-compose.yaml up -d
+ sudo docker-compose -f docker/clickhouse-setup/docker-compose.yaml up -d
# command:
- name: Run Frontend
@@ -22,7 +22,7 @@ tasks:
yarn dev
ports:
- - port: 3000
+ - port: 3301
onOpen: open-browser
- port: 8080
onOpen: ignore
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 412afd5eab..7b63015435 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -48,8 +48,15 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/pkg/query-service](ht
- git clone https://github.com/SigNoz/signoz.git
- run `sudo make dev-setup` to configure local setup to run query-service
-- comment out frontend service section at `docker/clickhouse-setup/docker-compose.yaml#L59`
-- comment out query-service section at `docker/clickhouse-setup/docker-compose.yaml#L38`
+- comment out frontend service section at `docker/clickhouse-setup/docker-compose.yaml#L45`
+- comment out query-service section at `docker/clickhouse-setup/docker-compose.yaml#L28`
+- add below configuration to clickhouse section at `docker/clickhouse-setup/docker-compose.yaml`
+```
+ expose:
+ - 9000
+ ports:
+ - 9001:9000
+```
- Install signoz locally without the frontend and query-service
- If you are using x86_64 processors (All Intel/AMD processors) run `sudo make run-x86`
- If you are on arm64 processors (Apple M1 Macbooks) run `sudo make run-arm`
diff --git a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml
index dba80353da..f666105b32 100644
--- a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml
+++ b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml
@@ -23,7 +23,7 @@ services:
- '--storage.path=/data'
query-service:
- image: signoz/query-service:0.6.1
+ image: signoz/query-service:0.6.2
container_name: query-service
command: ["-config=/root/config/prometheus.yml"]
volumes:
@@ -40,7 +40,7 @@ services:
condition: service_healthy
frontend:
- image: signoz/frontend:0.6.1
+ image: signoz/frontend:0.6.2
container_name: frontend
depends_on:
- query-service
@@ -50,7 +50,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
- image: signoz/otelcontribcol:0.5.0
+ image: signoz/otelcontribcol:0.6.0
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
@@ -63,7 +63,7 @@ services:
condition: service_healthy
otel-collector-metrics:
- image: signoz/otelcontribcol:0.5.0
+ image: signoz/otelcontribcol:0.6.0
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--mem-ballast-size-mib=683"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml
index 13eaa5790e..3c5dbbc64d 100644
--- a/deploy/docker/clickhouse-setup/docker-compose.yaml
+++ b/deploy/docker/clickhouse-setup/docker-compose.yaml
@@ -26,7 +26,7 @@ services:
query-service:
- image: signoz/query-service:0.6.1
+ image: signoz/query-service:0.6.2
container_name: query-service
command: ["-config=/root/config/prometheus.yml"]
volumes:
@@ -43,7 +43,7 @@ services:
condition: service_healthy
frontend:
- image: signoz/frontend:0.6.1
+ image: signoz/frontend:0.6.2
container_name: frontend
depends_on:
- query-service
@@ -53,7 +53,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
- image: signoz/otelcontribcol:0.5.0
+ image: signoz/otelcontribcol:0.6.0
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
@@ -66,7 +66,7 @@ services:
condition: service_healthy
otel-collector-metrics:
- image: signoz/otelcontribcol:0.5.0
+ image: signoz/otelcontribcol:0.6.0
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--mem-ballast-size-mib=683"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
diff --git a/frontend/cypress/CustomFunctions/uncaughtExpection.ts b/frontend/cypress/CustomFunctions/uncaughtExpection.ts
new file mode 100644
index 0000000000..341ddd8cee
--- /dev/null
+++ b/frontend/cypress/CustomFunctions/uncaughtExpection.ts
@@ -0,0 +1,14 @@
+const resizeObserverLoopErrRe = /ResizeObserver loop limit exceeded/;
+
+const unCaughtExpection = () => {
+ cy.on('uncaught:exception', (err) => {
+ if (resizeObserverLoopErrRe.test(err.message)) {
+ // returning false here prevents Cypress from
+ // failing the test
+ return false;
+ }
+ return true;
+ });
+};
+
+export default unCaughtExpection;
diff --git a/frontend/cypress/fixtures/trace/initialAggregates.json b/frontend/cypress/fixtures/trace/initialAggregates.json
new file mode 100644
index 0000000000..b029e9de64
--- /dev/null
+++ b/frontend/cypress/fixtures/trace/initialAggregates.json
@@ -0,0 +1,35 @@
+{
+ "items": {
+ "1644926280000000000": { "timestamp": 1644926280000000000, "value": 787 },
+ "1644926340000000000": { "timestamp": 1644926340000000000, "value": 2798 },
+ "1644926400000000000": { "timestamp": 1644926400000000000, "value": 2828 },
+ "1644926460000000000": { "timestamp": 1644926460000000000, "value": 2926 },
+ "1644926520000000000": { "timestamp": 1644926520000000000, "value": 2932 },
+ "1644926580000000000": { "timestamp": 1644926580000000000, "value": 2842 },
+ "1644926640000000000": { "timestamp": 1644926640000000000, "value": 2966 },
+ "1644926700000000000": { "timestamp": 1644926700000000000, "value": 2782 },
+ "1644926760000000000": { "timestamp": 1644926760000000000, "value": 2843 },
+ "1644926820000000000": { "timestamp": 1644926820000000000, "value": 2864 },
+ "1644926880000000000": { "timestamp": 1644926880000000000, "value": 2777 },
+ "1644926940000000000": { "timestamp": 1644926940000000000, "value": 2820 },
+ "1644927000000000000": { "timestamp": 1644927000000000000, "value": 2579 },
+ "1644927060000000000": { "timestamp": 1644927060000000000, "value": 2681 },
+ "1644927120000000000": { "timestamp": 1644927120000000000, "value": 2828 },
+ "1644927180000000000": { "timestamp": 1644927180000000000, "value": 2975 },
+ "1644927240000000000": { "timestamp": 1644927240000000000, "value": 2934 },
+ "1644927300000000000": { "timestamp": 1644927300000000000, "value": 2793 },
+ "1644927360000000000": { "timestamp": 1644927360000000000, "value": 2913 },
+ "1644927420000000000": { "timestamp": 1644927420000000000, "value": 2621 },
+ "1644927480000000000": { "timestamp": 1644927480000000000, "value": 2631 },
+ "1644927540000000000": { "timestamp": 1644927540000000000, "value": 2924 },
+ "1644927600000000000": { "timestamp": 1644927600000000000, "value": 2576 },
+ "1644927660000000000": { "timestamp": 1644927660000000000, "value": 2878 },
+ "1644927720000000000": { "timestamp": 1644927720000000000, "value": 2737 },
+ "1644927780000000000": { "timestamp": 1644927780000000000, "value": 2621 },
+ "1644927840000000000": { "timestamp": 1644927840000000000, "value": 2823 },
+ "1644927900000000000": { "timestamp": 1644927900000000000, "value": 3081 },
+ "1644927960000000000": { "timestamp": 1644927960000000000, "value": 2883 },
+ "1644928020000000000": { "timestamp": 1644928020000000000, "value": 2823 },
+ "1644928080000000000": { "timestamp": 1644928080000000000, "value": 455 }
+ }
+}
diff --git a/frontend/cypress/fixtures/trace/initialSpanFilter.json b/frontend/cypress/fixtures/trace/initialSpanFilter.json
new file mode 100644
index 0000000000..26bed66ae9
--- /dev/null
+++ b/frontend/cypress/fixtures/trace/initialSpanFilter.json
@@ -0,0 +1,19 @@
+{
+ "serviceName": {
+ "customer": 1642,
+ "driver": 1642,
+ "frontend": 39408,
+ "mysql": 1642,
+ "redis": 22167,
+ "route": 16420
+ },
+ "status": { "error": 4105, "ok": 78816 },
+ "duration": { "maxDuration": 1253979000, "minDuration": 415000 },
+ "operation": {},
+ "httpCode": {},
+ "httpUrl": {},
+ "httpMethod": {},
+ "httpRoute": {},
+ "httpHost": {},
+ "component": {}
+}
diff --git a/frontend/cypress/fixtures/trace/initialSpans.json b/frontend/cypress/fixtures/trace/initialSpans.json
new file mode 100644
index 0000000000..9b15100752
--- /dev/null
+++ b/frontend/cypress/fixtures/trace/initialSpans.json
@@ -0,0 +1,105 @@
+{
+ "spans": [
+ {
+ "timestamp": "2022-02-15T12:16:09.542074Z",
+ "spanID": "303b39065c6f5df5",
+ "traceID": "00000000000000007fc49fab3cb75958",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 313418000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:08.84038Z",
+ "spanID": "557e8303bc802992",
+ "traceID": "000000000000000079310bd1d435a92b",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 318203000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:08.867689Z",
+ "spanID": "347113dd916dd20e",
+ "traceID": "00000000000000004c22c0409cee0f66",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 512810000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:07.060882Z",
+ "spanID": "0a8d07f72aa1339b",
+ "traceID": "0000000000000000488e11a35959de96",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 588705000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:07.134107Z",
+ "spanID": "0acd4ec344675998",
+ "traceID": "00000000000000000292efc7945d9bfa",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 801632000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:06.474095Z",
+ "spanID": "3ae72e433301822a",
+ "traceID": "00000000000000001ac3004ff1b7eefe",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 306650000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:06.996246Z",
+ "spanID": "1d765427af673039",
+ "traceID": "00000000000000002e78f59fabbcdecf",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 311469000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:05.324296Z",
+ "spanID": "0987c90d83298a1d",
+ "traceID": "0000000000000000077bcb960609a350",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 290680000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:02.458221Z",
+ "spanID": "5b0d0d403dd9acf4",
+ "traceID": "00000000000000007ae5b0aa69242556",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 262763000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ },
+ {
+ "timestamp": "2022-02-15T12:16:00.584939Z",
+ "spanID": "3beafb277a76b9b4",
+ "traceID": "00000000000000000ab44953c2fd949e",
+ "serviceName": "customer",
+ "operation": "HTTP GET /customer",
+ "durationNano": 302851000,
+ "httpCode": "200",
+ "httpMethod": "GET"
+ }
+ ],
+ "totalSpans": 82921
+}
diff --git a/frontend/cypress/integration/trace/index.spec.ts b/frontend/cypress/integration/trace/index.spec.ts
new file mode 100644
index 0000000000..3ca79ecdce
--- /dev/null
+++ b/frontend/cypress/integration/trace/index.spec.ts
@@ -0,0 +1,154 @@
+import ROUTES from 'constants/routes';
+import { TraceFilterEnum } from 'types/reducer/trace';
+import TableInitialResponse from '../../fixtures/trace/initialSpans.json';
+import FilterInitialResponse from '../../fixtures/trace/initialSpanFilter.json';
+import GraphInitialResponse from '../../fixtures/trace/initialAggregates.json';
+import { AppState } from 'store/reducers';
+
+describe('Trace', () => {
+ beforeEach(() => {
+ window.localStorage.setItem('isLoggedIn', 'yes');
+
+ cy
+ .intercept('POST', '**/aggregates', {
+ fixture: 'trace/initialAggregates',
+ })
+ .as('Graph');
+
+ cy
+ .intercept('POST', '**/getFilteredSpans', {
+ fixture: 'trace/initialSpans',
+ })
+ .as('Table');
+
+ cy
+ .intercept('POST', '**/api/v1/getSpanFilters', {
+ fixture: 'trace/initialSpanFilter',
+ })
+ .as('Filters');
+
+ cy.visit(Cypress.env('baseUrl') + `${ROUTES.TRACE}`);
+ });
+
+ it('First Initial Load should go with 3 AJAX request', () => {
+ cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
+ const [filter, graph, table] = e;
+
+ const { body: filterBody } = filter.request;
+ const { body: graphBody } = graph.request;
+ const { body: tableBody } = table.request;
+
+ expect(filterBody.exclude.length).to.equal(0);
+ expect(filterBody.getFilters.length).to.equal(3);
+ filterBody.getFilters.forEach((filter: TraceFilterEnum) => {
+ expect(filter).to.be.oneOf(['duration', 'status', 'serviceName']);
+ });
+
+ expect(graphBody.function).to.be.equal('count');
+ expect(graphBody.exclude.length).to.be.equal(0);
+ expect(typeof graphBody.exclude).to.be.equal('object');
+
+ expect(tableBody.tags.length).to.be.equal(0);
+ expect(typeof tableBody.tags).equal('object');
+
+ expect(tableBody.exclude.length).equals(0);
+ });
+ });
+
+ it('Render Time Request Response In All 3 Request', () => {
+ cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
+ const [filter, graph, table] = e;
+
+ expect(filter.response?.body).to.be.not.undefined;
+ expect(filter.response?.body).to.be.not.NaN;
+
+ expect(JSON.stringify(filter.response?.body)).to.be.equals(
+ JSON.stringify(FilterInitialResponse),
+ );
+
+ expect(JSON.stringify(graph.response?.body)).to.be.equals(
+ JSON.stringify(GraphInitialResponse),
+ );
+
+ expect(JSON.stringify(table.response?.body)).to.be.equals(
+ JSON.stringify(TableInitialResponse),
+ );
+ });
+ cy.get('@Filters.all').should('have.length', 1);
+ cy.get('@Graph.all').should('have.length', 1);
+ cy.get('@Table.all').should('have.length', 1);
+ });
+
+ it('Clear All', () => {
+ cy.wait(['@Filters', '@Graph', '@Table']);
+
+ expect(cy.findAllByText('Clear All')).not.to.be.undefined;
+
+ cy
+ .window()
+ .its('store')
+ .invoke('getState')
+ .then((e: AppState) => {
+ const { traces } = e;
+ expect(traces.isFilterExclude.get('status')).to.be.undefined;
+ expect(traces.selectedFilter.size).to.be.equals(0);
+ });
+
+ cy.findAllByText('Clear All').then((e) => {
+ const [firstStatusClear] = e;
+
+ firstStatusClear.click();
+
+ cy.wait(['@Filters', '@Graph', '@Table']);
+
+ // insuring the api get call
+ cy.get('@Filters.all').should('have.length', 2);
+ cy.get('@Graph.all').should('have.length', 2);
+ cy.get('@Table.all').should('have.length', 2);
+
+ cy
+ .window()
+ .its('store')
+ .invoke('getState')
+ .then((e: AppState) => {
+ const { traces } = e;
+
+ expect(traces.isFilterExclude.get('status')).to.be.equals(false);
+ expect(traces.userSelectedFilter.get('status')).to.be.undefined;
+ expect(traces.selectedFilter.size).to.be.equals(0);
+ });
+ });
+ });
+
+ it('Un Selecting one option from status', () => {
+ cy.wait(['@Filters', '@Graph', '@Table']);
+
+ cy.get('input[type="checkbox"]').then((e) => {
+ const [errorCheckbox] = e;
+ errorCheckbox.click();
+
+ cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
+ const [filter, graph, table] = e;
+ const filterBody = filter.request.body;
+ const graphBody = graph.request.body;
+ const tableBody = table.request.body;
+
+ expect(filterBody.exclude).not.to.be.undefined;
+ expect(filterBody.exclude.length).not.to.be.equal(0);
+ expect(filterBody.exclude[0] === 'status').to.be.true;
+
+ expect(graphBody.exclude).not.to.be.undefined;
+ expect(graphBody.exclude.length).not.to.be.equal(0);
+ expect(graphBody.exclude[0] === 'status').to.be.true;
+
+ expect(tableBody.exclude).not.to.be.undefined;
+ expect(tableBody.exclude.length).not.to.be.equal(0);
+ expect(tableBody.exclude[0] === 'status').to.be.true;
+ });
+
+ cy.get('@Filters.all').should('have.length', 2);
+ cy.get('@Graph.all').should('have.length', 2);
+ cy.get('@Table.all').should('have.length', 2);
+ });
+ });
+});
diff --git a/frontend/src/components/RouteTab/index.tsx b/frontend/src/components/RouteTab/index.tsx
new file mode 100644
index 0000000000..12ce2d2a70
--- /dev/null
+++ b/frontend/src/components/RouteTab/index.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { Tabs, TabsProps } from 'antd';
+
+const { TabPane } = Tabs;
+import history from 'lib/history';
+
+const RouteTab = ({
+ routes,
+ activeKey,
+ onChangeHandler,
+ ...rest
+}: RouteTabProps & TabsProps): JSX.Element => {
+ const onChange = (activeRoute: string) => {
+ onChangeHandler && onChangeHandler();
+
+ const selectedRoute = routes.find((e) => e.name === activeRoute);
+
+ if (selectedRoute) {
+ history.push(selectedRoute.route);
+ }
+ };
+
+ return (
+
+ {routes.map(
+ ({ Component, name }): JSX.Element => (
+
+
+
+ ),
+ )}
+
+ );
+};
+
+interface RouteTabProps {
+ routes: {
+ name: string;
+ route: string;
+ Component: () => JSX.Element;
+ }[];
+ activeKey: TabsProps['activeKey'];
+ onChangeHandler?: VoidFunction;
+}
+
+export default RouteTab;
diff --git a/frontend/src/components/TextToolTip/index.tsx b/frontend/src/components/TextToolTip/index.tsx
index 83f162fac9..470e61a642 100644
--- a/frontend/src/components/TextToolTip/index.tsx
+++ b/frontend/src/components/TextToolTip/index.tsx
@@ -8,7 +8,7 @@ const TextToolTip = ({ text, url }: TextToolTipProps) => (
return (
diff --git a/frontend/src/container/AppLayout/FeedBack/index.tsx b/frontend/src/container/AppLayout/FeedBack/index.tsx
deleted file mode 100644
index 13e0279806..0000000000
--- a/frontend/src/container/AppLayout/FeedBack/index.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import { CloseCircleOutlined, CommentOutlined } from '@ant-design/icons';
-import { Button, Divider, Form, Input, notification, Typography } from 'antd';
-import { Callbacks } from 'rc-field-form/lib/interface';
-import React, { useCallback, useState } from 'react';
-
-import {
- Button as IconButton,
- ButtonContainer,
- Card,
- CenterText,
- Container,
- TitleContainer,
- FormItem,
-} from './styles';
-const { Title } = Typography;
-const { TextArea } = Input;
-import sendFeedbackApi from 'api/userFeedback/sendFeedback';
-
-const Feedback = (): JSX.Element => {
- const [isOpen, setisOpen] = useState(false);
- const [form] = Form.useForm();
- const [isLoading, setIsLoading] = useState(false);
-
- const [notifications, Element] = notification.useNotification();
-
- const isToggleHandler = useCallback(() => {
- setisOpen((state) => !state);
- }, []);
-
- const onFinishHandler: Callbacks['onFinish'] = async (
- value: Feedback,
- ): Promise => {
- try {
- setIsLoading(true);
- const { feedback, email = '' } = value;
-
- const response = await sendFeedbackApi({
- email,
- message: feedback,
- });
-
- if (response === 200) {
- notifications.success({
- message: 'Thanks for your feedback!',
- description:
- 'We have noted down your feedback and will work on improving SIgNoz based on that!',
- });
-
- isToggleHandler();
- } else {
- notifications.error({
- message: 'Error!',
- description: 'Something went wrong',
- });
- }
- setIsLoading(false);
- } catch (error) {
- notifications.error({
- message: 'Something went wrong',
- });
- setIsLoading(false);
- }
- };
-
- return (
-
- {!isOpen && (
-
-
-
- )}
-
- {Element}
-
- {isOpen && (
-
- )}
-
- );
-};
-
-interface Feedback {
- email?: string;
- feedback: string;
-}
-
-export default Feedback;
diff --git a/frontend/src/container/AppLayout/FeedBack/styles.ts b/frontend/src/container/AppLayout/FeedBack/styles.ts
deleted file mode 100644
index 259ced79c3..0000000000
--- a/frontend/src/container/AppLayout/FeedBack/styles.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import {
- Button as ButtonComponent,
- Card as CardComponent,
- Typography,
- Form,
-} from 'antd';
-import styled from 'styled-components';
-
-export const Container = styled.div`
- position: fixed;
- bottom: 5%;
- right: 4%;
- z-index: 999999;
-`;
-
-export const CenterText = styled(Typography)`
- &&& {
- font-size: 0.75rem;
- text-align: center;
- margin-bottom: 0.5rem;
- }
-`;
-
-export const TitleContainer = styled.div`
- &&& {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
-`;
-
-export const Card = styled(CardComponent)`
- &&& {
- min-width: 400px;
- }
-`;
-
-export const ButtonContainer = styled.div`
- display: flex;
- justify-content: flex-end;
- align-items: center;
-`;
-
-export const Button = styled(ButtonComponent)`
- height: 4rem !important;
- width: 4rem !important;
- display: flex;
- justify-content: center;
- align-items: center;
-
- border-radius: 25px !important;
-
- background-color: #65b7f3;
-
- svg {
- width: 2rem;
- height: 2rem;
- }
-`;
-
-export const FormItem = styled(Form.Item)`
- margin-top: 0.75rem;
- margin-bottom: 0.75rem;
-`;
diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx
index d272ea85a6..407d57e3e8 100644
--- a/frontend/src/container/AppLayout/index.tsx
+++ b/frontend/src/container/AppLayout/index.tsx
@@ -7,7 +7,6 @@ import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
-import Feedback from './FeedBack';
import { Content, Footer, Layout } from './styles';
const AppLayout: React.FC = ({ children }) => {
@@ -40,8 +39,6 @@ const AppLayout: React.FC = ({ children }) => {
-
-
);
};
diff --git a/frontend/src/container/EditAlertChannels/index.tsx b/frontend/src/container/EditAlertChannels/index.tsx
index e947f65dd2..bd707ee2b3 100644
--- a/frontend/src/container/EditAlertChannels/index.tsx
+++ b/frontend/src/container/EditAlertChannels/index.tsx
@@ -9,17 +9,10 @@ import FormAlertChannels from 'container/FormAlertChannels';
import history from 'lib/history';
import { Store } from 'rc-field-form/lib/interface';
import React, { useCallback, useState } from 'react';
-import { connect } from 'react-redux';
import { useParams } from 'react-router';
-import { bindActionCreators } from 'redux';
-import { ThunkDispatch } from 'redux-thunk';
-import { ToggleSettingsTab } from 'store/actions';
-import AppActions from 'types/actions';
-import { SettingTab } from 'types/reducer/app';
const EditAlertChannels = ({
initialValue,
- toggleSettingsTab,
}: EditAlertChannelsProps): JSX.Element => {
const [formInstance] = Form.useForm();
const [selectedConfig, setSelectedConfig] = useState>({
@@ -52,7 +45,6 @@ const EditAlertChannels = ({
message: 'Success',
description: 'Channels Edited Successfully',
});
- toggleSettingsTab('Alert Channels');
setTimeout(() => {
history.replace(ROUTES.SETTINGS);
@@ -100,18 +92,8 @@ const EditAlertChannels = ({
);
};
-interface DispatchProps {
- toggleSettingsTab: (props: SettingTab) => void;
-}
-
-const mapDispatchToProps = (
- dispatch: ThunkDispatch,
-): DispatchProps => ({
- toggleSettingsTab: bindActionCreators(ToggleSettingsTab, dispatch),
-});
-
-interface EditAlertChannelsProps extends DispatchProps {
+interface EditAlertChannelsProps {
initialValue: Store;
}
-export default connect(null, mapDispatchToProps)(EditAlertChannels);
+export default EditAlertChannels;
diff --git a/frontend/src/container/Header/index.tsx b/frontend/src/container/Header/index.tsx
index 967ddbea74..8884b2231b 100644
--- a/frontend/src/container/Header/index.tsx
+++ b/frontend/src/container/Header/index.tsx
@@ -2,14 +2,17 @@ import { Col } from 'antd';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import React from 'react';
-import { useLocation } from 'react-router-dom';
+import { useLocation, matchPath } from 'react-router-dom';
import ShowBreadcrumbs from './Breadcrumbs';
import DateTimeSelector from './DateTimeSelection';
import { Container } from './styles';
-const routesToSkip = [ROUTES.SETTINGS, ROUTES.LIST_ALL_ALERT];
-
+const routesToSkip = [
+ ROUTES.SETTINGS,
+ ROUTES.LIST_ALL_ALERT,
+ ROUTES.TRACE_GRAPH,
+];
const TopNav = (): JSX.Element | null => {
const { pathname } = useLocation();
@@ -18,13 +21,24 @@ const TopNav = (): JSX.Element | null => {
return null;
}
+ const checkRouteExists = (currentPath: string) => {
+ for (let i = 0; i < routesToSkip.length; ++i) {
+ if (
+ matchPath(currentPath, { path: routesToSkip[i], exact: true, strict: true })
+ ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
return (
- {!routesToSkip.includes(pathname) && (
+ {!checkRouteExists(pathname) && (
diff --git a/frontend/src/container/SettingsWrapper/index.tsx b/frontend/src/container/SettingsWrapper/index.tsx
deleted file mode 100644
index afe59fe7bd..0000000000
--- a/frontend/src/container/SettingsWrapper/index.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Tabs } from 'antd';
-import ROUTES from 'constants/routes';
-import history from 'lib/history';
-import React, { useCallback } from 'react';
-import { connect, useSelector } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import { ThunkDispatch } from 'redux-thunk';
-import { ToggleSettingsTab } from 'store/actions';
-import { AppState } from 'store/reducers';
-import AppActions from 'types/actions';
-import AppReducer, { SettingTab } from 'types/reducer/app';
-const { TabPane } = Tabs;
-
-const SettingsWrapper = ({
- AlertChannels,
- General,
- toggleSettingsTab,
- defaultRoute,
-}: SettingsWrapperProps): JSX.Element => {
- const { settingsActiveTab } = useSelector(
- (state) => state.app,
- );
-
- const onChangeHandler = useCallback(
- (value: SettingTab) => {
- toggleSettingsTab(value);
- if (value === 'General') {
- history.push(ROUTES.SETTINGS);
- }
-
- if (value === 'Alert Channels') {
- history.push(ROUTES.ALL_CHANNELS);
- }
- },
- [toggleSettingsTab],
- );
-
- return (
- onChangeHandler(value as SettingTab)}
- activeKey={defaultRoute || settingsActiveTab}
- >
-
-
-
-
-
-
-
- );
-};
-
-interface DispatchProps {
- toggleSettingsTab: (props: SettingTab) => void;
-}
-
-const mapDispatchToProps = (
- dispatch: ThunkDispatch,
-): DispatchProps => ({
- toggleSettingsTab: bindActionCreators(ToggleSettingsTab, dispatch),
-});
-
-interface SettingsWrapperProps extends DispatchProps {
- General: () => JSX.Element;
- AlertChannels: () => JSX.Element;
- defaultRoute?: SettingTab;
-}
-
-export default connect(null, mapDispatchToProps)(SettingsWrapper);
diff --git a/frontend/src/container/SideNav/Slack.tsx b/frontend/src/container/SideNav/Slack.tsx
new file mode 100644
index 0000000000..d0cc536e38
--- /dev/null
+++ b/frontend/src/container/SideNav/Slack.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+
+const Slack = (): JSX.Element => {
+ return (
+
+ );
+};
+
+export default Slack;
diff --git a/frontend/src/container/SideNav/index.tsx b/frontend/src/container/SideNav/index.tsx
index c96e46b18d..6e203d4ab2 100644
--- a/frontend/src/container/SideNav/index.tsx
+++ b/frontend/src/container/SideNav/index.tsx
@@ -1,5 +1,10 @@
import { Menu, Typography } from 'antd';
-import { ToggleButton } from './styles';
+import {
+ MenuItem,
+ SlackButton,
+ SlackMenuItemContainer,
+ ToggleButton,
+} from './styles';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import React, { useCallback, useState } from 'react';
@@ -17,6 +22,7 @@ import setTheme from 'lib/theme/setTheme';
import menus from './menuItems';
import { Logo, Sider, ThemeSwitcherWrapper } from './styles';
+import Slack from './Slack';
const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
const [collapsed, setCollapsed] = useState(false);
@@ -57,10 +63,18 @@ const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
[pathname],
);
+ const onClickSlackHandler = () => {
+ window.open('https://signoz.io/slack', '_blank');
+ };
+
return (
-
+
@@ -81,6 +95,12 @@ const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
{name}
))}
+
+
+ }>
+ Support
+
+
);
diff --git a/frontend/src/container/SideNav/styles.ts b/frontend/src/container/SideNav/styles.ts
index 11583359e1..2b75d0dc5a 100644
--- a/frontend/src/container/SideNav/styles.ts
+++ b/frontend/src/container/SideNav/styles.ts
@@ -1,5 +1,5 @@
-import { Layout, Switch } from 'antd';
-import styled from 'styled-components';
+import { Layout, Menu, Switch, Typography } from 'antd';
+import styled, { css } from 'styled-components';
const { Sider: SiderComponent } = Layout;
export const ThemeSwitcherWrapper = styled.div`
@@ -23,6 +23,9 @@ export const Sider = styled(SiderComponent)`
.ant-typography {
color: white;
}
+ .ant-layout-sider-trigger {
+ background-color: #1f1f1f;
+ }
`;
interface DarkModeProps {
@@ -36,3 +39,41 @@ export const ToggleButton = styled(Switch)`
}
`;
+export const SlackButton = styled(Typography)`
+ &&& {
+ margin-left: 1rem;
+ }
+`;
+
+export const MenuItem = styled(Menu.Item)`
+ &&& {
+ position: fixed;
+ bottom: 48px;
+ width: 100%;
+ height: 48px;
+ background: #262626;
+ }
+`;
+
+export const SlackMenuItemContainer = styled.div`
+ &&& {
+ li {
+ ${({ collapsed }) =>
+ collapsed &&
+ css`
+ padding-left: 24px;
+ `}
+ }
+
+ svg {
+ margin-left: ${({ collapsed }) => (collapsed ? '0' : '24px')};
+
+ ${({ collapsed }) =>
+ collapsed &&
+ css`
+ height: 100%;
+ margin: 0 auto;
+ `}
+ }
+ }
+`;
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx
index 96fc5d5dc7..785362c25c 100644
--- a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx
+++ b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx
@@ -164,7 +164,7 @@ const PanelHeading = (props: PanelHeadingProps): JSX.Element => {
end: String(global.maxTime),
start: String(global.minTime),
getFilters: filterToFetchData,
- other: Object.fromEntries(preUserSelected),
+ other: Object.fromEntries(updatedFilter),
isFilterExclude: postIsFilterExclude,
});
diff --git a/frontend/src/container/Trace/Graph/index.tsx b/frontend/src/container/Trace/Graph/index.tsx
index a91fe78930..efc8a9d137 100644
--- a/frontend/src/container/Trace/Graph/index.tsx
+++ b/frontend/src/container/Trace/Graph/index.tsx
@@ -40,7 +40,7 @@ const TraceGraph = () => {
return (
-
+
);
};
diff --git a/frontend/src/container/Trace/TraceGraphFilter/index.tsx b/frontend/src/container/Trace/TraceGraphFilter/index.tsx
index bd06764aad..e6aad7374a 100644
--- a/frontend/src/container/Trace/TraceGraphFilter/index.tsx
+++ b/frontend/src/container/Trace/TraceGraphFilter/index.tsx
@@ -56,6 +56,7 @@ const TraceGraphFilter = () => {
selectedFunction === e.key)?.displayValue}
onChange={onClickSelectedFunctionHandler}
>
@@ -69,6 +70,7 @@ const TraceGraphFilter = () => {
selectedGroupBy === e.key)?.displayValue}
onChange={onClickSelectedGroupByHandler}
>
diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx
index ce6133a089..919f680778 100644
--- a/frontend/src/container/Trace/TraceTable/index.tsx
+++ b/frontend/src/container/Trace/TraceTable/index.tsx
@@ -43,7 +43,7 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
key: 'timestamp',
render: (value: TableType['timestamp']) => {
const day = dayjs(value);
- return {day.format('DD/MM/YYYY HH:MM:ss A')}
;
+ return {day.format('DD/MM/YYYY hh:mm:ss A')}
;
},
sorter: (a, b) => dayjs(a.timestamp).diff(dayjs(b.timestamp)),
},
@@ -129,6 +129,9 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
})}
size="middle"
rowKey={'timestamp'}
+ style={{
+ cursor: 'pointer',
+ }}
pagination={{
current: spansAggregate.currentPage,
pageSize: spansAggregate.pageSize,
diff --git a/frontend/src/hooks/useDebouncedFunction.ts b/frontend/src/hooks/useDebouncedFunction.ts
index 4a26fbc46f..e63b8c6021 100644
--- a/frontend/src/hooks/useDebouncedFunction.ts
+++ b/frontend/src/hooks/useDebouncedFunction.ts
@@ -1,4 +1,4 @@
-import { useCallback } from 'react';
+import { useMemo, useRef } from 'react';
import debounce from 'lodash-es/debounce';
export interface DebouncedFunc any> {
@@ -26,9 +26,13 @@ const useDebouncedFn = any>(
options: DebounceOptions = defaultOptions,
dependencies?: ReadonlyArray,
): DebouncedFunc => {
- const debounced = debounce(fn, wait, options);
+ const fnRef = useRef(fn);
+ fnRef.current = fn;
- return useCallback(debounced, dependencies || []);
+ return useMemo(
+ () => debounce(((...args) => fnRef.current(...args)) as T, wait, options),
+ [...(dependencies || [])],
+ );
};
export default useDebouncedFn;
diff --git a/frontend/src/pages/AlertChannelCreate/index.tsx b/frontend/src/pages/AlertChannelCreate/index.tsx
index e7063085d2..226f2ca2c2 100644
--- a/frontend/src/pages/AlertChannelCreate/index.tsx
+++ b/frontend/src/pages/AlertChannelCreate/index.tsx
@@ -1,18 +1,32 @@
+import RouteTab from 'components/RouteTab';
+import ROUTES from 'constants/routes';
import CreateAlertChannels from 'container/CreateAlertChannels';
import GeneralSettings from 'container/GeneralSettings';
-import SettingsWrapper from 'container/SettingsWrapper';
+import history from 'lib/history';
import React from 'react';
const SettingsPage = (): JSX.Element => {
- const AlertChannels = (): JSX.Element => {
- return ;
- };
+ const pathName = history.location.pathname;
return (
- {
+ return ;
+ },
+ name: 'Alert Channels',
+ route: ROUTES.ALL_CHANNELS,
+ },
+ ],
+ activeKey:
+ pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels',
}}
/>
);
diff --git a/frontend/src/pages/AllAlertChannels/index.tsx b/frontend/src/pages/AllAlertChannels/index.tsx
index 716795500c..e557f4e861 100644
--- a/frontend/src/pages/AllAlertChannels/index.tsx
+++ b/frontend/src/pages/AllAlertChannels/index.tsx
@@ -1,16 +1,33 @@
import AlertChannels from 'container/AllAlertChannels';
import GeneralSettings from 'container/GeneralSettings';
-import SettingsWrapper from 'container/SettingsWrapper';
import React from 'react';
+import RouteTab from 'components/RouteTab';
+import ROUTES from 'constants/routes';
+import history from 'lib/history';
-const AllAlertChannels = (): JSX.Element => (
-
-);
+const AllAlertChannels = (): JSX.Element => {
+ const pathName = history.location.pathname;
+
+ return (
+
+ );
+};
export default AllAlertChannels;
diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx
index 3a8687c48a..c5310107f3 100644
--- a/frontend/src/pages/Settings/index.tsx
+++ b/frontend/src/pages/Settings/index.tsx
@@ -1,14 +1,30 @@
import AlertChannels from 'container/AllAlertChannels';
import GeneralSettings from 'container/GeneralSettings';
-import SettingsWrapper from 'container/SettingsWrapper';
import React from 'react';
+import RouteTab from 'components/RouteTab';
+import ROUTES from 'constants/routes';
+import history from 'lib/history';
const SettingsPage = (): JSX.Element => {
+ const pathName = history.location.pathname;
+
return (
-
);
diff --git a/frontend/src/store/actions/app/index.ts b/frontend/src/store/actions/app/index.ts
index 308027be21..036d1024cf 100644
--- a/frontend/src/store/actions/app/index.ts
+++ b/frontend/src/store/actions/app/index.ts
@@ -1,2 +1 @@
export * from './toggleDarkMode';
-export * from './toggleSettingsTab';
diff --git a/frontend/src/store/actions/app/toggleSettingsTab.ts b/frontend/src/store/actions/app/toggleSettingsTab.ts
deleted file mode 100644
index d60ee26afe..0000000000
--- a/frontend/src/store/actions/app/toggleSettingsTab.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Dispatch } from 'redux';
-import AppActions from 'types/actions';
-import { SettingTab } from 'types/reducer/app';
-
-export const ToggleSettingsTab = (
- props: SettingTab,
-): ((dispatch: Dispatch) => void) => {
- return (dispatch: Dispatch): void => {
- dispatch({
- type: 'TOGGLE_SETTINGS_TABS',
- payload: {
- activeTab: props,
- },
- });
- };
-};
diff --git a/frontend/src/store/reducers/app.ts b/frontend/src/store/reducers/app.ts
index d0ab0fef79..8a786919b9 100644
--- a/frontend/src/store/reducers/app.ts
+++ b/frontend/src/store/reducers/app.ts
@@ -1,10 +1,5 @@
import { IS_LOGGED_IN } from 'constants/auth';
-import {
- AppAction,
- LOGGED_IN,
- SWITCH_DARK_MODE,
- TOGGLE_SETTINGS_TABS,
-} from 'types/actions/app';
+import { AppAction, LOGGED_IN, SWITCH_DARK_MODE } from 'types/actions/app';
import getTheme from 'lib/theme/getTheme';
import InitialValueTypes from 'types/reducer/app';
import getLocalStorageKey from 'api/browser/localstorage/get';
@@ -12,7 +7,6 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
const InitialValue: InitialValueTypes = {
isDarkMode: getTheme() === 'darkMode' ? true : false,
isLoggedIn: getLocalStorageKey(IS_LOGGED_IN) === 'yes',
- settingsActiveTab: 'General',
};
const appReducer = (
@@ -34,13 +28,6 @@ const appReducer = (
};
}
- case TOGGLE_SETTINGS_TABS: {
- return {
- ...state,
- settingsActiveTab: action.payload.activeTab,
- };
- }
-
default:
return state;
}
diff --git a/frontend/src/types/actions/app.ts b/frontend/src/types/actions/app.ts
index 968ee5f89f..4a8495aafe 100644
--- a/frontend/src/types/actions/app.ts
+++ b/frontend/src/types/actions/app.ts
@@ -1,8 +1,5 @@
-import { SettingTab } from 'types/reducer/app';
-
export const SWITCH_DARK_MODE = 'SWITCH_DARK_MODE';
export const LOGGED_IN = 'LOGGED_IN';
-export const TOGGLE_SETTINGS_TABS = 'TOGGLE_SETTINGS_TABS';
export interface SwitchDarkMode {
type: typeof SWITCH_DARK_MODE;
@@ -12,11 +9,4 @@ export interface LoggedInUser {
type: typeof LOGGED_IN;
}
-export interface ToggleSettingsTab {
- type: typeof TOGGLE_SETTINGS_TABS;
- payload: {
- activeTab: SettingTab;
- };
-}
-
-export type AppAction = SwitchDarkMode | LoggedInUser | ToggleSettingsTab;
+export type AppAction = SwitchDarkMode | LoggedInUser;
diff --git a/frontend/src/types/reducer/app.ts b/frontend/src/types/reducer/app.ts
index d0e82f0153..2ec974c399 100644
--- a/frontend/src/types/reducer/app.ts
+++ b/frontend/src/types/reducer/app.ts
@@ -1,6 +1,4 @@
-export type SettingTab = 'General' | 'Alert Channels';
export default interface AppReducer {
isDarkMode: boolean;
isLoggedIn: boolean;
- settingsActiveTab: SettingTab;
}
diff --git a/frontend/webpack.config.prod.js b/frontend/webpack.config.prod.js
index c90c4616d8..291fa84e15 100644
--- a/frontend/webpack.config.prod.js
+++ b/frontend/webpack.config.prod.js
@@ -144,8 +144,4 @@ const config = {
},
};
-if (process.env.BUNDLE_ANALYSER === 'true') {
- config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'server' }));
-}
-
module.exports = config;
diff --git a/pkg/query-service/README.md b/pkg/query-service/README.md
index c4c96a9757..72b69afc9a 100644
--- a/pkg/query-service/README.md
+++ b/pkg/query-service/README.md
@@ -2,27 +2,30 @@
Query service is the interface between forntend and databases. It is written in **Golang**. It will have modules for all supported databases. Query service is responsible to:
- parse the request from Frontend
-- create relevant Druid queries (and all other supported database queries)
+- create relevant Clickhouse queries (and all other supported database queries)
- parse response from databases and handle error if any
-- build response in the format accepted by Frontend
+- clickhouse response in the format accepted by Frontend
-#### Druid Queries
-Internally we use both native and sql queries to Druid.
-
#### Configuration
-Query Service needs below `env` variables to run:
+- Open ./constants/constants.go
+ - Replace ```const RELATIONAL_DATASOURCE_PATH = "/var/lib/signoz/signoz.db"``` \
+ with ```const RELATIONAL_DATASOURCE_PATH = "./signoz.db".```
+
+- Query Service needs below `env` variables to run:
+
```
- DruidClientUrl: http://signoz-druid-router:8888
- DruidDatasource: flattened_spans
+ ClickHouseUrl=tcp://localhost:9001
+ STORAGE=clickhouse
```
-The above values are the default ones used by SigNoz and are kept at `deploy/kubernetes/platform/signoz-charts/query-service/values.yaml`
+
+
#### Build and Run locally
```console
cd pkg/query-service
go build -o build/query-service main.go
-DruidClientUrl=xxxx DruidDatasource=flattened_spans build/query-service
+ClickHouseUrl=tcp://localhost:9001 STORAGE=clickhouse build/query-service
```
#### Docker Images
diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go
index 876c66fa18..d424762293 100644
--- a/pkg/query-service/app/clickhouseReader/reader.go
+++ b/pkg/query-service/app/clickhouseReader/reader.go
@@ -1537,7 +1537,7 @@ func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *mode
}
}
case "status":
- finalQuery := fmt.Sprintf("SELECT COUNT(*) as numErrors FROM %s WHERE timestamp >= ? AND timestamp <= ? AND ( ( has(tags, 'error:true') OR statusCode>=500 OR statusCode=2))", r.indexTable)
+ finalQuery := fmt.Sprintf("SELECT COUNT(*) as numErrors FROM %s WHERE timestamp >= ? AND timestamp <= ? AND hasError = 1", r.indexTable)
finalQuery += query
var dBResponse []model.DBResponseErrors
err := r.db.Select(&dBResponse, finalQuery, args...)
@@ -1582,16 +1582,16 @@ func getStatusFilters(query string, statusParams []string, excludeMap map[string
if _, ok := excludeMap["status"]; ok {
if len(statusParams) == 1 {
if statusParams[0] == "error" {
- query += " AND ((NOT ( has(tags, 'error:true')) AND statusCode<500 AND statusCode!=2))"
+ query += " AND hasError = 0"
} else if statusParams[0] == "ok" {
- query += " AND ( ( has(tags, 'error:true') OR statusCode>=500 OR statusCode=2))"
+ query += " AND hasError = 1"
}
}
} else if len(statusParams) == 1 {
if statusParams[0] == "error" {
- query += " AND ( ( has(tags, 'error:true') OR statusCode>=500 OR statusCode=2))"
+ query += " AND hasError = 1"
} else if statusParams[0] == "ok" {
- query += " AND ((NOT ( has(tags, 'error:true')) AND statusCode<500 AND statusCode!=2))"
+ query += " AND hasError = 0"
}
}
return query
@@ -1839,6 +1839,79 @@ func excludeTags(ctx context.Context, tags []model.TagFilters) []model.TagFilter
return newTags
}
+func (r *ClickHouseReader) GetTagValues(ctx context.Context, queryParams *model.TagFilterParams) (*[]model.TagValues, *model.ApiError) {
+
+ excludeMap := make(map[string]struct{})
+ for _, e := range queryParams.Exclude {
+ if e == constants.OperationRequest {
+ excludeMap[constants.OperationDB] = struct{}{}
+ continue
+ }
+ excludeMap[e] = struct{}{}
+ }
+
+ var query string
+ args := []interface{}{queryParams.TagKey, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)}
+ if len(queryParams.ServiceName) > 0 {
+ args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args)
+ }
+ if len(queryParams.HttpRoute) > 0 {
+ args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpRoute, constants.HttpRoute, &query, args)
+ }
+ if len(queryParams.HttpCode) > 0 {
+ args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpCode, constants.HttpCode, &query, args)
+ }
+ if len(queryParams.HttpHost) > 0 {
+ args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpHost, constants.HttpHost, &query, args)
+ }
+ if len(queryParams.HttpMethod) > 0 {
+ args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpMethod, constants.HttpMethod, &query, args)
+ }
+ if len(queryParams.HttpUrl) > 0 {
+ args = buildFilterArrayQuery(ctx, excludeMap, queryParams.HttpUrl, constants.HttpUrl, &query, args)
+ }
+ if len(queryParams.Component) > 0 {
+ args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Component, constants.Component, &query, args)
+ }
+ if len(queryParams.Operation) > 0 {
+ args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args)
+ }
+ if len(queryParams.MinDuration) != 0 {
+ query = query + " AND durationNano >= ?"
+ args = append(args, queryParams.MinDuration)
+ }
+ if len(queryParams.MaxDuration) != 0 {
+ query = query + " AND durationNano <= ?"
+ args = append(args, queryParams.MaxDuration)
+ }
+
+ query = getStatusFilters(query, queryParams.Status, excludeMap)
+
+ tagValues := []model.TagValues{}
+
+ finalQuery := fmt.Sprintf(`SELECT tagMap[?] as tagValues FROM %s WHERE timestamp >= ? AND timestamp <= ?`, r.indexTable)
+ finalQuery += query
+ fmt.Println(finalQuery)
+ finalQuery += "GROUP BY tagMap[?]"
+ args = append(args, queryParams.TagKey)
+ err := r.db.Select(&tagValues, finalQuery, args...)
+
+ zap.S().Info(query)
+
+ if err != nil {
+ zap.S().Debug("Error in processing sql query: ", err)
+ return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")}
+ }
+
+ cleanedTagValues := []model.TagValues{}
+ for _, e := range tagValues {
+ if e.TagValues != "" {
+ cleanedTagValues = append(cleanedTagValues, e)
+ }
+ }
+ return &cleanedTagValues, nil
+}
+
func (r *ClickHouseReader) GetServiceDBOverview(ctx context.Context, queryParams *model.GetServiceOverviewParams) (*[]model.ServiceDBOverviewItem, error) {
var serviceDBOverviewItems []model.ServiceDBOverviewItem
@@ -2093,7 +2166,7 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, traceId string) (*[
var searchScanReponses []model.SearchSpanReponseItem
- query := fmt.Sprintf("SELECT timestamp, spanID, traceID, serviceName, name, kind, durationNano, tagsKeys, tagsValues, references, events FROM %s WHERE traceID=?", r.indexTable)
+ query := fmt.Sprintf("SELECT timestamp, spanID, traceID, serviceName, name, kind, durationNano, tagsKeys, tagsValues, references, events, hasError FROM %s WHERE traceID=?", r.indexTable)
err := r.db.Select(&searchScanReponses, query, traceId)
@@ -2106,7 +2179,7 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, traceId string) (*[
searchSpansResult := []model.SearchSpansResult{
model.SearchSpansResult{
- Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events"},
+ Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events", "HasError"},
Events: make([][]interface{}, len(searchScanReponses)),
},
}
diff --git a/pkg/query-service/app/druidReader/reader.go b/pkg/query-service/app/druidReader/reader.go
index bd44ad2670..de390d98e2 100644
--- a/pkg/query-service/app/druidReader/reader.go
+++ b/pkg/query-service/app/druidReader/reader.go
@@ -173,6 +173,10 @@ func (druid *DruidReader) GetTagFilters(_ context.Context, _ *model.TagFilterPar
return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting tagFilters")}
}
+func (druid *DruidReader) GetTagValues(_ context.Context, _ *model.TagFilterParams) (*[]model.TagValues, *model.ApiError) {
+ return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting tagValues")}
+}
+
func (druid *DruidReader) GetFilteredSpans(_ context.Context, _ *model.GetFilteredSpansParams) (*model.GetFilterSpansResponse, *model.ApiError) {
return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting FilteredSpans")}
}
diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go
index d76e8c6ca1..c46988760e 100644
--- a/pkg/query-service/app/http_handler.go
+++ b/pkg/query-service/app/http_handler.go
@@ -214,6 +214,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) {
router.HandleFunc("/api/v1/getFilteredSpans", aH.getFilteredSpans).Methods(http.MethodPost)
router.HandleFunc("/api/v1/getFilteredSpans/aggregates", aH.getFilteredSpanAggregates).Methods(http.MethodPost)
+ router.HandleFunc("/api/v1/getTagValues", aH.getTagValues).Methods(http.MethodPost)
router.HandleFunc("/api/v1/errors", aH.getErrors).Methods(http.MethodGet)
router.HandleFunc("/api/v1/errorWithId", aH.getErrorForId).Methods(http.MethodGet)
router.HandleFunc("/api/v1/errorWithType", aH.getErrorForType).Methods(http.MethodGet)
@@ -682,8 +683,9 @@ func (aH *APIHandler) user(w http.ResponseWriter, r *http.Request) {
telemetry.GetInstance().IdentifyUser(user)
data := map[string]interface{}{
- "name": user.Name,
- "email": user.Email,
+ "name": user.Name,
+ "email": user.Email,
+ "organizationName": user.OrganizationName,
}
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_USER, data)
@@ -1040,6 +1042,22 @@ func (aH *APIHandler) getTagFilters(w http.ResponseWriter, r *http.Request) {
aH.writeJSON(w, r, result)
}
+func (aH *APIHandler) getTagValues(w http.ResponseWriter, r *http.Request) {
+
+ query, err := parseTagValueRequest(r)
+ if aH.handleError(w, err, http.StatusBadRequest) {
+ return
+ }
+
+ result, apiErr := (*aH.reader).GetTagValues(context.Background(), query)
+
+ if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) {
+ return
+ }
+
+ aH.writeJSON(w, r, result)
+}
+
func (aH *APIHandler) setTTL(w http.ResponseWriter, r *http.Request) {
ttlParams, err := parseDuration(r)
if aH.handleError(w, err, http.StatusBadRequest) {
@@ -1091,6 +1109,12 @@ func (aH *APIHandler) setUserPreferences(w http.ResponseWriter, r *http.Request)
return
}
+ data := map[string]interface{}{
+ "hasOptedUpdates": userParams.HasOptedUpdates,
+ "isAnonymous": userParams.IsAnonymous,
+ }
+ telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_USER_PREFERENCES, data)
+
aH.writeJSON(w, r, map[string]string{"data": "user preferences set successfully"})
}
diff --git a/pkg/query-service/app/interface.go b/pkg/query-service/app/interface.go
index de5bdec35b..158a900215 100644
--- a/pkg/query-service/app/interface.go
+++ b/pkg/query-service/app/interface.go
@@ -39,6 +39,7 @@ type Reader interface {
GetTTL(ctx context.Context, ttlParams *model.GetTTLParams) (*model.GetTTLResponseItem, *model.ApiError)
GetSpanFilters(ctx context.Context, query *model.SpanFilterParams) (*model.SpanFiltersResponse, *model.ApiError)
GetTagFilters(ctx context.Context, query *model.TagFilterParams) (*[]model.TagFilters, *model.ApiError)
+ GetTagValues(ctx context.Context, query *model.TagFilterParams) (*[]model.TagValues, *model.ApiError)
GetFilteredSpans(ctx context.Context, query *model.GetFilteredSpansParams) (*model.GetFilterSpansResponse, *model.ApiError)
GetFilteredSpansAggregates(ctx context.Context, query *model.GetFilteredSpanAggregatesParams) (*model.GetFilteredSpansAggregatesResponse, *model.ApiError)
diff --git a/pkg/query-service/app/parser.go b/pkg/query-service/app/parser.go
index 89d1a6c07f..b329c85a34 100644
--- a/pkg/query-service/app/parser.go
+++ b/pkg/query-service/app/parser.go
@@ -650,6 +650,31 @@ func parseTagFilterRequest(r *http.Request) (*model.TagFilterParams, error) {
return postData, nil
}
+
+func parseTagValueRequest(r *http.Request) (*model.TagFilterParams, error) {
+ var postData *model.TagFilterParams
+ err := json.NewDecoder(r.Body).Decode(&postData)
+
+ if err != nil {
+ return nil, err
+ }
+ if postData.TagKey == "" {
+ return nil, fmt.Errorf("%s param missing in query", postData.TagKey)
+ }
+
+ postData.Start, err = parseTimeStr(postData.StartStr, "start")
+ if err != nil {
+ return nil, err
+ }
+ postData.End, err = parseTimeMinusBufferStr(postData.EndStr, "end")
+ if err != nil {
+ return nil, err
+ }
+
+ return postData, nil
+
+}
+
func parseErrorsRequest(r *http.Request) (*model.GetErrorsParams, error) {
startTime, err := parseTime("start", r)
diff --git a/pkg/query-service/model/queryParams.go b/pkg/query-service/model/queryParams.go
index 8ceaddd9a6..71782b3ec9 100644
--- a/pkg/query-service/model/queryParams.go
+++ b/pkg/query-service/model/queryParams.go
@@ -6,8 +6,9 @@ import (
)
type User struct {
- Name string `json:"name"`
- Email string `json:"email"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ OrganizationName string `json:"organizationName"`
}
type InstantQueryMetricsParams struct {
@@ -201,6 +202,7 @@ type TagFilterParams struct {
MaxDuration string `json:"maxDuration"`
StartStr string `json:"start"`
EndStr string `json:"end"`
+ TagKey string `json:"tagKey"`
Start *time.Time
End *time.Time
}
diff --git a/pkg/query-service/model/response.go b/pkg/query-service/model/response.go
index d3e15490f1..fb5c460747 100644
--- a/pkg/query-service/model/response.go
+++ b/pkg/query-service/model/response.go
@@ -179,6 +179,7 @@ type SearchSpanReponseItem struct {
TagsKeys []string `db:"tagsKeys"`
TagsValues []string `db:"tagsValues"`
Events []string `db:"events"`
+ HasError int32 `db:"hasError"`
}
type OtelSpanRef struct {
@@ -214,7 +215,7 @@ func (item *SearchSpanReponseItem) GetValues() []interface{} {
}
}
- returnArray := []interface{}{int64(timeObj.UnixNano() / 1000000), item.SpanID, item.TraceID, item.ServiceName, item.Name, strconv.Itoa(int(item.Kind)), strconv.FormatInt(item.DurationNano, 10), item.TagsKeys, item.TagsValues, referencesStringArray, errorEvent}
+ returnArray := []interface{}{int64(timeObj.UnixNano() / 1000000), item.SpanID, item.TraceID, item.ServiceName, item.Name, strconv.Itoa(int(item.Kind)), strconv.FormatInt(item.DurationNano, 10), item.TagsKeys, item.TagsValues, referencesStringArray, errorEvent, item.HasError}
return returnArray
}
@@ -267,6 +268,10 @@ type TagItem struct {
type TagFilters struct {
TagKeys string `json:"tagKeys" db:"tagKeys"`
}
+
+type TagValues struct {
+ TagValues string `json:"tagValues" db:"tagValues"`
+}
type ServiceMapDependencyResponseItem struct {
Parent string `json:"parent,omitempty" db:"parent,omitempty"`
Child string `json:"child,omitempty" db:"child,omitempty"`
diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go
index 90632784e7..5de8392e0c 100644
--- a/pkg/query-service/telemetry/telemetry.go
+++ b/pkg/query-service/telemetry/telemetry.go
@@ -18,6 +18,7 @@ const (
TELEMETRY_EVENT_INPRODUCT_FEEDBACK = "InProduct Feeback Submitted"
TELEMETRY_EVENT_NUMBER_OF_SERVICES = "Number of Services"
TELEMETRY_EVENT_HEART_BEAT = "Heart Beat"
+ TELEMETRY_EVENT_USER_PREFERENCES = "User Preferences"
)
const api_key = "4Gmoa4ixJAUHx2BpJxsjwA1bEfnwEeRz"
@@ -61,6 +62,10 @@ func getOutboundIP() string {
ip := []byte("NA")
resp, err := http.Get("https://api.ipify.org?format=text")
+ if err != nil {
+ return string(ip)
+ }
+
defer resp.Body.Close()
if err == nil {
ipBody, err := ioutil.ReadAll(resp.Body)