mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-06 05:36:11 +08:00
commit
abe4940e74
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -2,5 +2,5 @@
|
||||
# Owners are automatically requested for review for PRs that changes code
|
||||
# that they own.
|
||||
* @ankitnayan
|
||||
/frontend/ @palash-signoz
|
||||
/frontend/ @palash-signoz @pranshuchittora
|
||||
/deploy/ @prashant-shahi
|
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -26,6 +26,7 @@ assignees: ''
|
||||
* **Signoz version**:
|
||||
* **Browser version**:
|
||||
* **Your OS and version**:
|
||||
* **Your CPU Architecture**(ARM/Intel):
|
||||
|
||||
## Additional context
|
||||
|
||||
|
@ -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
|
||||
|
@ -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`
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
14
frontend/cypress/CustomFunctions/uncaughtExpection.ts
Normal file
14
frontend/cypress/CustomFunctions/uncaughtExpection.ts
Normal file
@ -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;
|
35
frontend/cypress/fixtures/trace/initialAggregates.json
Normal file
35
frontend/cypress/fixtures/trace/initialAggregates.json
Normal file
@ -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 }
|
||||
}
|
||||
}
|
19
frontend/cypress/fixtures/trace/initialSpanFilter.json
Normal file
19
frontend/cypress/fixtures/trace/initialSpanFilter.json
Normal file
@ -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": {}
|
||||
}
|
105
frontend/cypress/fixtures/trace/initialSpans.json
Normal file
105
frontend/cypress/fixtures/trace/initialSpans.json
Normal file
@ -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
|
||||
}
|
154
frontend/cypress/integration/trace/index.spec.ts
Normal file
154
frontend/cypress/integration/trace/index.spec.ts
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
51
frontend/src/components/RouteTab/index.tsx
Normal file
51
frontend/src/components/RouteTab/index.tsx
Normal file
@ -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 (
|
||||
<Tabs
|
||||
onChange={onChange}
|
||||
destroyInactiveTabPane
|
||||
activeKey={activeKey}
|
||||
{...rest}
|
||||
>
|
||||
{routes.map(
|
||||
({ Component, name }): JSX.Element => (
|
||||
<TabPane tab={name} key={name}>
|
||||
<Component />
|
||||
</TabPane>
|
||||
),
|
||||
)}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
interface RouteTabProps {
|
||||
routes: {
|
||||
name: string;
|
||||
route: string;
|
||||
Component: () => JSX.Element;
|
||||
}[];
|
||||
activeKey: TabsProps['activeKey'];
|
||||
onChangeHandler?: VoidFunction;
|
||||
}
|
||||
|
||||
export default RouteTab;
|
@ -8,7 +8,7 @@ const TextToolTip = ({ text, url }: TextToolTipProps) => (
|
||||
return (
|
||||
<div>
|
||||
{`${text} `}
|
||||
<a href={url} target={'_blank'}>
|
||||
<a href={url} rel="noopener noreferrer" target={'_blank'}>
|
||||
here
|
||||
</a>
|
||||
</div>
|
||||
|
@ -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<boolean>(false);
|
||||
const [form] = Form.useForm();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const [notifications, Element] = notification.useNotification();
|
||||
|
||||
const isToggleHandler = useCallback(() => {
|
||||
setisOpen((state) => !state);
|
||||
}, []);
|
||||
|
||||
const onFinishHandler: Callbacks<Feedback>['onFinish'] = async (
|
||||
value: Feedback,
|
||||
): Promise<void> => {
|
||||
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 (
|
||||
<Container>
|
||||
{!isOpen && (
|
||||
<IconButton onClick={isToggleHandler} type="primary" size="large">
|
||||
<CommentOutlined />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
{Element}
|
||||
|
||||
{isOpen && (
|
||||
<Form onFinish={onFinishHandler} form={form}>
|
||||
<Card>
|
||||
<TitleContainer>
|
||||
<Title
|
||||
aria-label="How can we improve SigNoz?"
|
||||
style={{ margin: 0 }}
|
||||
level={5}
|
||||
>
|
||||
How can we improve SigNoz?
|
||||
</Title>
|
||||
<CloseCircleOutlined onClick={isToggleHandler} />
|
||||
</TitleContainer>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormItem name="feedback" required>
|
||||
<TextArea
|
||||
required
|
||||
rows={3}
|
||||
placeholder="Share what can we improve ( e.g. Not able to find how to see metrics... )"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="email">
|
||||
<Input type="email" placeholder="Email (optional)" />
|
||||
</FormItem>
|
||||
|
||||
<CenterText>This will just be visible to our maintainers</CenterText>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Card>
|
||||
</Form>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
interface Feedback {
|
||||
email?: string;
|
||||
feedback: string;
|
||||
}
|
||||
|
||||
export default Feedback;
|
@ -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;
|
||||
`;
|
@ -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<AppLayoutProps> = ({ children }) => {
|
||||
@ -40,8 +39,6 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||
</Content>
|
||||
<Footer>{`SigNoz Inc. © ${currentYear}`}</Footer>
|
||||
</Layout>
|
||||
|
||||
<Feedback />
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
@ -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<Partial<SlackChannel>>({
|
||||
@ -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<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
toggleSettingsTab: bindActionCreators(ToggleSettingsTab, dispatch),
|
||||
});
|
||||
|
||||
interface EditAlertChannelsProps extends DispatchProps {
|
||||
interface EditAlertChannelsProps {
|
||||
initialValue: Store;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(EditAlertChannels);
|
||||
export default EditAlertChannels;
|
||||
|
@ -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 (
|
||||
<Container>
|
||||
<Col span={16}>
|
||||
<ShowBreadcrumbs />
|
||||
</Col>
|
||||
|
||||
{!routesToSkip.includes(pathname) && (
|
||||
{!checkRouteExists(pathname) && (
|
||||
<Col span={8}>
|
||||
<DateTimeSelector />
|
||||
</Col>
|
||||
|
@ -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<AppState, AppReducer>(
|
||||
(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 (
|
||||
<Tabs
|
||||
destroyInactiveTabPane
|
||||
onChange={(value): void => onChangeHandler(value as SettingTab)}
|
||||
activeKey={defaultRoute || settingsActiveTab}
|
||||
>
|
||||
<TabPane tab="General" key="General">
|
||||
<General />
|
||||
</TabPane>
|
||||
<TabPane tab="Alert Channels" key="Alert Channels">
|
||||
<AlertChannels />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
toggleSettingsTab: (props: SettingTab) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
toggleSettingsTab: bindActionCreators(ToggleSettingsTab, dispatch),
|
||||
});
|
||||
|
||||
interface SettingsWrapperProps extends DispatchProps {
|
||||
General: () => JSX.Element;
|
||||
AlertChannels: () => JSX.Element;
|
||||
defaultRoute?: SettingTab;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(SettingsWrapper);
|
48
frontend/src/container/SideNav/Slack.tsx
Normal file
48
frontend/src/container/SideNav/Slack.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
|
||||
const Slack = (): JSX.Element => {
|
||||
return (
|
||||
<svg
|
||||
width="28"
|
||||
height="28"
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.08886 17.6001C6.08886 19.1778 4.79997 20.4667 3.2222 20.4667C1.64442 20.4667 0.35553 19.1778 0.35553 17.6001C0.35553 16.0223 1.64442 14.7334 3.2222 14.7334H6.08886V17.6001Z"
|
||||
fill="#E01E5A"
|
||||
/>
|
||||
<path
|
||||
d="M7.53333 17.6001C7.53333 16.0223 8.82221 14.7334 10.4 14.7334C11.9778 14.7334 13.2667 16.0223 13.2667 17.6001V24.7778C13.2667 26.3556 11.9778 27.6445 10.4 27.6445C8.82221 27.6445 7.53333 26.3556 7.53333 24.7778V17.6001Z"
|
||||
fill="#E01E5A"
|
||||
/>
|
||||
<path
|
||||
d="M10.4 6.08892C8.82221 6.08892 7.53333 4.80004 7.53333 3.22226C7.53333 1.64448 8.82221 0.355591 10.4 0.355591C11.9778 0.355591 13.2667 1.64448 13.2667 3.22226V6.08892H10.4Z"
|
||||
fill="#36C5F0"
|
||||
/>
|
||||
<path
|
||||
d="M10.4 7.53333C11.9778 7.53333 13.2666 8.82221 13.2666 10.4C13.2666 11.9778 11.9778 13.2667 10.4 13.2667H3.2222C1.64442 13.2667 0.35553 11.9778 0.35553 10.4C0.35553 8.82221 1.64442 7.53333 3.2222 7.53333H10.4Z"
|
||||
fill="#36C5F0"
|
||||
/>
|
||||
<path
|
||||
d="M21.9111 10.4C21.9111 8.82221 23.2 7.53333 24.7778 7.53333C26.3556 7.53333 27.6445 8.82221 27.6445 10.4C27.6445 11.9778 26.3556 13.2667 24.7778 13.2667H21.9111V10.4Z"
|
||||
fill="#2EB67D"
|
||||
/>
|
||||
<path
|
||||
d="M20.4667 10.4C20.4667 11.9778 19.1778 13.2667 17.6 13.2667C16.0222 13.2667 14.7333 11.9778 14.7333 10.4V3.22226C14.7333 1.64448 16.0222 0.355591 17.6 0.355591C19.1778 0.355591 20.4667 1.64448 20.4667 3.22226V10.4Z"
|
||||
fill="#2EB67D"
|
||||
/>
|
||||
<path
|
||||
d="M17.6 21.9111C19.1778 21.9111 20.4667 23.2 20.4667 24.7778C20.4667 26.3556 19.1778 27.6445 17.6 27.6445C16.0222 27.6445 14.7333 26.3556 14.7333 24.7778V21.9111H17.6Z"
|
||||
fill="#ECB22E"
|
||||
/>
|
||||
<path
|
||||
d="M17.6 20.4667C16.0222 20.4667 14.7333 19.1778 14.7333 17.6001C14.7333 16.0223 16.0222 14.7334 17.6 14.7334H24.7778C26.3556 14.7334 27.6444 16.0223 27.6444 17.6001C27.6444 19.1778 26.3556 20.4667 24.7778 20.4667H17.6Z"
|
||||
fill="#ECB22E"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default Slack;
|
@ -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<boolean>(false);
|
||||
@ -57,10 +63,18 @@ const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
|
||||
[pathname],
|
||||
);
|
||||
|
||||
const onClickSlackHandler = () => {
|
||||
window.open('https://signoz.io/slack', '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={200}>
|
||||
<ThemeSwitcherWrapper>
|
||||
<ToggleButton checked={isDarkMode} onChange={toggleTheme} defaultChecked={isDarkMode} />
|
||||
<ToggleButton
|
||||
checked={isDarkMode}
|
||||
onChange={toggleTheme}
|
||||
defaultChecked={isDarkMode}
|
||||
/>
|
||||
</ThemeSwitcherWrapper>
|
||||
<NavLink to={ROUTES.APPLICATION}>
|
||||
<Logo src={'/signoz.svg'} alt="SigNoz" collapsed={collapsed} />
|
||||
@ -81,6 +95,12 @@ const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
|
||||
<Typography>{name}</Typography>
|
||||
</Menu.Item>
|
||||
))}
|
||||
|
||||
<SlackMenuItemContainer collapsed={collapsed}>
|
||||
<MenuItem onClick={onClickSlackHandler} icon={<Slack />}>
|
||||
<SlackButton>Support</SlackButton>
|
||||
</MenuItem>
|
||||
</SlackMenuItemContainer>
|
||||
</Menu>
|
||||
</Sider>
|
||||
);
|
||||
|
@ -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)<DarkModeProps>`
|
||||
}
|
||||
`;
|
||||
|
||||
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<LogoProps>`
|
||||
&&& {
|
||||
li {
|
||||
${({ collapsed }) =>
|
||||
collapsed &&
|
||||
css`
|
||||
padding-left: 24px;
|
||||
`}
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-left: ${({ collapsed }) => (collapsed ? '0' : '24px')};
|
||||
|
||||
${({ collapsed }) =>
|
||||
collapsed &&
|
||||
css`
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
`}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -40,7 +40,7 @@ const TraceGraph = () => {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Graph data={ChartData} name="traceGraphph" type="line" />
|
||||
<Graph data={ChartData} name="traceGraph" type="line" />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
@ -56,6 +56,7 @@ const TraceGraphFilter = () => {
|
||||
|
||||
<SelectComponent
|
||||
dropdownMatchSelectWidth
|
||||
data-testid="selectedFunction"
|
||||
value={functions.find((e) => selectedFunction === e.key)?.displayValue}
|
||||
onChange={onClickSelectedFunctionHandler}
|
||||
>
|
||||
@ -69,6 +70,7 @@ const TraceGraphFilter = () => {
|
||||
<label>Group By</label>
|
||||
<SelectComponent
|
||||
dropdownMatchSelectWidth
|
||||
data-testid="selectedGroupBy"
|
||||
value={groupBy.find((e) => selectedGroupBy === e.key)?.displayValue}
|
||||
onChange={onClickSelectedGroupByHandler}
|
||||
>
|
||||
|
@ -43,7 +43,7 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
key: 'timestamp',
|
||||
render: (value: TableType['timestamp']) => {
|
||||
const day = dayjs(value);
|
||||
return <div>{day.format('DD/MM/YYYY HH:MM:ss A')}</div>;
|
||||
return <div>{day.format('DD/MM/YYYY hh:mm:ss A')}</div>;
|
||||
},
|
||||
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,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
|
||||
export interface DebouncedFunc<T extends (...args: any[]) => any> {
|
||||
@ -26,9 +26,13 @@ const useDebouncedFn = <T extends (...args: any) => any>(
|
||||
options: DebounceOptions = defaultOptions,
|
||||
dependencies?: ReadonlyArray<any>,
|
||||
): DebouncedFunc<T> => {
|
||||
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;
|
||||
|
@ -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 <CreateAlertChannels />;
|
||||
};
|
||||
const pathName = history.location.pathname;
|
||||
|
||||
return (
|
||||
<SettingsWrapper
|
||||
<RouteTab
|
||||
{...{
|
||||
AlertChannels,
|
||||
General: GeneralSettings,
|
||||
routes: [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: 'General Settings',
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
Component: () => {
|
||||
return <CreateAlertChannels />;
|
||||
},
|
||||
name: 'Alert Channels',
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
],
|
||||
activeKey:
|
||||
pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -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 => (
|
||||
<SettingsWrapper
|
||||
{...{
|
||||
AlertChannels,
|
||||
General: GeneralSettings,
|
||||
defaultRoute: 'Alert Channels',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const AllAlertChannels = (): JSX.Element => {
|
||||
const pathName = history.location.pathname;
|
||||
|
||||
return (
|
||||
<RouteTab
|
||||
{...{
|
||||
routes: [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: 'General Settings',
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
Component: AlertChannels,
|
||||
name: 'Alert Channels',
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
],
|
||||
activeKey:
|
||||
pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllAlertChannels;
|
||||
|
@ -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 (
|
||||
<SettingsWrapper
|
||||
<RouteTab
|
||||
{...{
|
||||
AlertChannels,
|
||||
General: GeneralSettings,
|
||||
routes: [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: 'General Settings',
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
Component: AlertChannels,
|
||||
name: 'Alert Channels',
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
],
|
||||
activeKey:
|
||||
pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General Settings',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './toggleDarkMode';
|
||||
export * from './toggleSettingsTab';
|
||||
|
@ -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<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'TOGGLE_SETTINGS_TABS',
|
||||
payload: {
|
||||
activeTab: props,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -1,6 +1,4 @@
|
||||
export type SettingTab = 'General' | 'Alert Channels';
|
||||
export default interface AppReducer {
|
||||
isDarkMode: boolean;
|
||||
isLoggedIn: boolean;
|
||||
settingsActiveTab: SettingTab;
|
||||
}
|
||||
|
@ -144,8 +144,4 @@ const config = {
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.BUNDLE_ANALYSER === 'true') {
|
||||
config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'server' }));
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
|
@ -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`
|
||||
|
||||
<!-- 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
|
||||
|
@ -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)),
|
||||
},
|
||||
}
|
||||
|
@ -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")}
|
||||
}
|
||||
|
@ -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"})
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"`
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user