Merge pull request #777 from SigNoz/release/v0.6.2

Release/v0.6.2
This commit is contained in:
Ankit Nayan 2022-02-25 22:15:05 +05:30 committed by GitHub
commit abe4940e74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 783 additions and 404 deletions

4
.github/CODEOWNERS vendored
View File

@ -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
/frontend/ @palash-signoz @pranshuchittora
/deploy/ @prashant-shahi

View File

@ -26,6 +26,7 @@ assignees: ''
* **Signoz version**:
* **Browser version**:
* **Your OS and version**:
* **Your CPU Architecture**(ARM/Intel):
## Additional context

View File

@ -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

View File

@ -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`

View File

@ -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

View File

@ -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

View 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;

View 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 }
}
}

View 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": {}
}

View 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
}

View 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);
});
});
});

View 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;

View File

@ -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>

View File

@ -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;

View File

@ -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;
`;

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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>

View File

@ -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);

View 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;

View File

@ -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>
);

View File

@ -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;
`}
}
}
`;

View File

@ -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,
});

View File

@ -40,7 +40,7 @@ const TraceGraph = () => {
return (
<Container>
<Graph data={ChartData} name="traceGraphph" type="line" />
<Graph data={ChartData} name="traceGraph" type="line" />
</Container>
);
};

View File

@ -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}
>

View File

@ -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,

View File

@ -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;

View File

@ -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',
}}
/>
);

View File

@ -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;

View File

@ -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',
}}
/>
);

View File

@ -1,2 +1 @@
export * from './toggleDarkMode';
export * from './toggleSettingsTab';

View File

@ -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,
},
});
};
};

View File

@ -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;
}

View File

@ -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;

View File

@ -1,6 +1,4 @@
export type SettingTab = 'General' | 'Alert Channels';
export default interface AppReducer {
isDarkMode: boolean;
isLoggedIn: boolean;
settingsActiveTab: SettingTab;
}

View File

@ -144,8 +144,4 @@ const config = {
},
};
if (process.env.BUNDLE_ANALYSER === 'true') {
config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'server' }));
}
module.exports = config;

View File

@ -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

View File

@ -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)),
},
}

View File

@ -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")}
}

View File

@ -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"})
}

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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"`

View File

@ -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)