Merge pull request #102 from SigNoz/issue-92

Change time range in api call of Service Map to 1 min from latest
This commit is contained in:
Ankit Nayan 2021-05-16 19:47:45 +05:30 committed by GitHub
commit f25edf1e29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 233 additions and 112 deletions

View File

@ -1,3 +1,3 @@
export enum LOCAL_STORAGE { export enum LOCAL_STORAGE {
METRICS_TIME_IN_DURATION = "metricsTimeDuration", METRICS_TIME_IN_DURATION = "metricsTimeDurations",
} }

View File

@ -1,17 +1,11 @@
import React, { Suspense } from "react"; import React, { Suspense } from "react";
import { Layout, Spin } from "antd"; import { Spin } from "antd";
import { useThemeSwitcher } from "react-css-theme-switcher"; import { useThemeSwitcher } from "react-css-theme-switcher";
import ROUTES from "Src/constants/routes"; import ROUTES from "Src/constants/routes";
import { IS_LOGGED_IN } from "Src/constants/auth"; import { IS_LOGGED_IN } from "Src/constants/auth";
import { import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
BrowserRouter as Router,
Route,
Switch,
Redirect,
} from "react-router-dom";
import SideNav from "./Nav/SideNav"; import BaseLayout from "./BaseLayout";
import TopNav from "./Nav/TopNav";
import { import {
ServiceMetrics, ServiceMetrics,
ServiceMap, ServiceMap,
@ -24,8 +18,6 @@ import {
IntstrumentationPage, IntstrumentationPage,
} from "Src/pages"; } from "Src/pages";
const { Content, Footer } = Layout;
const App = () => { const App = () => {
const { status } = useThemeSwitcher(); const { status } = useThemeSwitcher();
@ -34,47 +26,44 @@ const App = () => {
} }
return ( return (
<Router basename="/"> <BrowserRouter>
<Layout style={{ minHeight: "100vh" }}> <Suspense fallback={<Spin size="large" />}>
<SideNav /> <Route path={"/"}>
<Layout className="site-layout"> <Switch>
<Content style={{ margin: "0 16px" }}> <BaseLayout>
<TopNav /> <Route path={ROUTES.SIGN_UP} exact component={Signup} />
<Suspense fallback={<Spin size="large" />}> <Route path={ROUTES.APPLICATION} exact component={ServicesTable} />
<Switch> <Route path={ROUTES.SERVICE_METRICS} exact component={ServiceMetrics} />
<Route path={ROUTES.SIGN_UP} component={Signup} /> <Route path={ROUTES.SERVICE_MAP} exact component={ServiceMap} />
<Route path={ROUTES.SERVICE_METRICS} component={ServiceMetrics} /> <Route path={ROUTES.TRACES} exact component={TraceDetail} />
<Route path={ROUTES.SERVICE_MAP} component={ServiceMap} /> <Route path={ROUTES.TRACE_GRAPH} exact component={TraceGraph} />
<Route path={ROUTES.TRACES} exact component={TraceDetail} /> <Route path={ROUTES.SETTINGS} exact component={SettingsPage} />
<Route path={ROUTES.TRACE_GRAPH} component={TraceGraph} /> <Route
<Route path={ROUTES.SETTINGS} exact component={SettingsPage} /> path={ROUTES.INSTRUMENTATION}
<Route exact
path={ROUTES.INSTRUMENTATION} component={IntstrumentationPage}
exact />
component={IntstrumentationPage} <Route
/> path={ROUTES.USAGE_EXPLORER}
<Route path={ROUTES.USAGE_EXPLORER} component={UsageExplorer} /> exactexact
<Route path={ROUTES.APPLICATION} exact component={ServicesTable} /> component={UsageExplorer}
<Route />
path="/" <Route
exact path="/"
render={() => { exact
return localStorage.getItem(IS_LOGGED_IN) === "yes" ? ( render={() => {
<Redirect to={ROUTES.APPLICATION} /> return localStorage.getItem(IS_LOGGED_IN) === "yes" ? (
) : ( <Redirect to={ROUTES.APPLICATION} />
<Redirect to={ROUTES.SIGN_UP} /> ) : (
); <Redirect to={ROUTES.SIGN_UP} />
}} );
/> }}
</Switch> />
</Suspense> </BaseLayout>
</Content> </Switch>
<Footer style={{ textAlign: "center", fontSize: 10 }}> </Route>
SigNoz Inc. ©2020{" "} </Suspense>
</Footer> </BrowserRouter>
</Layout>
</Layout>
</Router>
); );
}; };

View File

@ -0,0 +1,29 @@
import React, { ReactNode } from "react";
import { Layout } from "antd";
import SideNav from "./Nav/SideNav";
import TopNav from "./Nav/TopNav";
const { Content, Footer } = Layout;
interface BaseLayoutProps {
children: ReactNode;
}
const BaseLayout: React.FC<BaseLayoutProps> = ({ children }) => {
return (
<Layout style={{ minHeight: "100vh" }}>
<SideNav />
<Layout className="site-layout">
<Content style={{ margin: "0 16px" }}>
<TopNav />
{children}
</Content>
<Footer style={{ textAlign: "center", fontSize: 10 }}>
SigNoz Inc. ©2020{" "}
</Footer>
</Layout>
</Layout>
);
};
export default BaseLayout;

View File

@ -1,27 +1,32 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Select, Button, Space, Form } from "antd"; import { cloneDeep } from "lodash";
import { Select as DefaultSelect, Button, Space, Form } from "antd";
import styled from "styled-components"; import styled from "styled-components";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { getLocalStorageRouteKey } from "./utils";
import { RouteComponentProps, useLocation } from "react-router-dom"; import { RouteComponentProps, useLocation } from "react-router-dom";
import { connect } from "react-redux"; import { connect } from "react-redux";
import ROUTES from "Src/constants/routes"; import ROUTES from "Src/constants/routes";
import CustomDateTimeModal from "./CustomDateTimeModal"; import CustomDateTimeModal from "./CustomDateTimeModal";
import { GlobalTime, updateTimeInterval } from "../../../store/actions"; import { GlobalTime, updateTimeInterval } from "../../../store/actions";
import { StoreState } from "../../../store/reducers"; import { StoreState } from "../../../store/reducers";
import FormItem from "antd/lib/form/FormItem"; import FormItem from "antd/lib/form/FormItem";
import {
Options,
ServiceMapOptions,
DefaultOptionsBasedOnRoute,
} from "./config";
import { DateTimeRangeType } from "../../../store/actions"; import { DateTimeRangeType } from "../../../store/actions";
import { METRICS_PAGE_QUERY_PARAM } from "Src/constants/query"; import { METRICS_PAGE_QUERY_PARAM } from "Src/constants/query";
import { LOCAL_STORAGE } from "Src/constants/localStorage"; import { LOCAL_STORAGE } from "Src/constants/localStorage";
import moment from "moment"; import moment from "moment";
const { Option } = Select; const { Option } = DefaultSelect;
const DateTimeWrapper = styled.div` const DateTimeWrapper = styled.div`
margin-top: 20px; margin-top: 20px;
justify-content: flex-end !important; justify-content: flex-end !important;
`; `;
const Select = styled(DefaultSelect)``;
interface DateTimeSelectorProps extends RouteComponentProps<any> { interface DateTimeSelectorProps extends RouteComponentProps<any> {
currentpath?: string; currentpath?: string;
updateTimeInterval: Function; updateTimeInterval: Function;
@ -32,21 +37,34 @@ interface DateTimeSelectorProps extends RouteComponentProps<any> {
This components is mounted all the time. Use event listener to track changes. This components is mounted all the time. Use event listener to track changes.
*/ */
const _DateTimeSelector = (props: DateTimeSelectorProps) => { const _DateTimeSelector = (props: DateTimeSelectorProps) => {
const defaultTime = "30min"; const location = useLocation();
const LocalStorageRouteKey: string = getLocalStorageRouteKey(
location.pathname,
);
const timeDurationInLocalStorage =
JSON.parse(localStorage.getItem(LOCAL_STORAGE.METRICS_TIME_IN_DURATION)) ||
{};
const options =
location.pathname === ROUTES.SERVICE_MAP ? ServiceMapOptions : Options;
let defaultTime = DefaultOptionsBasedOnRoute[LocalStorageRouteKey]
? DefaultOptionsBasedOnRoute[LocalStorageRouteKey]
: DefaultOptionsBasedOnRoute.default;
if (timeDurationInLocalStorage[LocalStorageRouteKey]) {
defaultTime = timeDurationInLocalStorage[LocalStorageRouteKey];
}
const [currentLocalStorageRouteKey, setCurrentLocalStorageRouteKey] = useState(
LocalStorageRouteKey,
);
const [customDTPickerVisible, setCustomDTPickerVisible] = useState(false); const [customDTPickerVisible, setCustomDTPickerVisible] = useState(false);
const [timeInterval, setTimeInterval] = useState(defaultTime); const [timeInterval, setTimeInterval] = useState(defaultTime);
const [startTime, setStartTime] = useState<moment.Moment | null>(null); const [startTime, setStartTime] = useState<moment.Moment | null>(null);
const [endTime, setEndTime] = useState<moment.Moment | null>(null); const [endTime, setEndTime] = useState<moment.Moment | null>(null);
const [refreshButtonHidden, setRefreshButtonHidden] = useState(false); const [refreshButtonHidden, setRefreshButtonHidden] = useState(false);
const [refreshText, setRefreshText] = useState(""); const [refreshText, setRefreshText] = useState("");
const [refreshButtonClick, setRefreshButtoClick] = useState(0); const [refreshButtonClick, setRefreshButtonClick] = useState(0);
const [form_dtselector] = Form.useForm(); const [form_dtselector] = Form.useForm();
const location = useLocation();
const updateTimeOnQueryParamChange = () => { const updateTimeOnQueryParamChange = () => {
const timeDurationInLocalStorage = localStorage.getItem(
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
);
const urlParams = new URLSearchParams(location.search); const urlParams = new URLSearchParams(location.search);
const intervalInQueryParam = urlParams.get(METRICS_PAGE_QUERY_PARAM.interval); const intervalInQueryParam = urlParams.get(METRICS_PAGE_QUERY_PARAM.interval);
const startTimeString = urlParams.get(METRICS_PAGE_QUERY_PARAM.startTime); const startTimeString = urlParams.get(METRICS_PAGE_QUERY_PARAM.startTime);
@ -62,36 +80,46 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => {
const startTime = moment(Number(startTimeString)); const startTime = moment(Number(startTimeString));
const endTime = moment(Number(endTimeString)); const endTime = moment(Number(endTimeString));
setCustomTime(startTime, endTime, true); setCustomTime(startTime, endTime, true);
} else if (currentLocalStorageRouteKey !== LocalStorageRouteKey) {
setMetricsTimeInterval(defaultTime);
setCurrentLocalStorageRouteKey(LocalStorageRouteKey);
} }
// first pref: handle intervalInQueryParam // first pref: handle intervalInQueryParam
else if (intervalInQueryParam) { else if (intervalInQueryParam) {
window.localStorage.setItem(
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
intervalInQueryParam,
);
setMetricsTimeInterval(intervalInQueryParam); setMetricsTimeInterval(intervalInQueryParam);
} else if (timeDurationInLocalStorage) {
setMetricsTimeInterval(timeDurationInLocalStorage);
} }
}; };
const setToLocalStorage = (val: string) => {
let timeDurationInLocalStorageObj = cloneDeep(timeDurationInLocalStorage);
if (timeDurationInLocalStorageObj) {
timeDurationInLocalStorageObj[LocalStorageRouteKey] = val;
} else {
timeDurationInLocalStorageObj = {
[LocalStorageRouteKey]: val,
};
}
window.localStorage.setItem(
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
JSON.stringify(timeDurationInLocalStorageObj),
);
};
useEffect(() => {
setMetricsTimeInterval(defaultTime);
}, []);
// On URL Change // On URL Change
useEffect(() => { useEffect(() => {
updateTimeOnQueryParamChange(); updateTimeOnQueryParamChange();
}, [location]); }, [location]);
//On mount
useEffect(() => {
updateTimeOnQueryParamChange();
}, []);
const setMetricsTimeInterval = (value: string) => { const setMetricsTimeInterval = (value: string) => {
props.updateTimeInterval(value); props.updateTimeInterval(value);
setTimeInterval(value); setTimeInterval(value);
setEndTime(null); setEndTime(null);
setStartTime(null); setStartTime(null);
setToLocalStorage(value);
window.localStorage.setItem(LOCAL_STORAGE.METRICS_TIME_IN_DURATION, value);
}; };
const setCustomTime = ( const setCustomTime = (
startTime: moment.Moment, startTime: moment.Moment,
@ -173,7 +201,7 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => {
}; };
const handleRefresh = () => { const handleRefresh = () => {
setRefreshButtoClick(refreshButtonClick + 1); setRefreshButtonClick(refreshButtonClick + 1);
setMetricsTimeInterval(timeInterval); setMetricsTimeInterval(timeInterval);
}; };
@ -187,15 +215,6 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => {
}; };
}, [props.location, refreshButtonClick]); }, [props.location, refreshButtonClick]);
const options = [
{ value: "custom", label: "Custom" },
{ value: "15min", label: "Last 15 min" },
{ value: "30min", label: "Last 30 min" },
{ value: "1hr", label: "Last 1 hour" },
{ value: "6hr", label: "Last 6 hour" },
{ value: "1day", label: "Last 1 day" },
{ value: "1week", label: "Last 1 week" },
];
if (props.location.pathname.startsWith(ROUTES.USAGE_EXPLORER)) { if (props.location.pathname.startsWith(ROUTES.USAGE_EXPLORER)) {
return null; return null;
} else { } else {
@ -205,6 +224,7 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => {
"YYYY/MM/DD HH:mm", "YYYY/MM/DD HH:mm",
)}` )}`
: timeInterval; : timeInterval;
return ( return (
<DateTimeWrapper> <DateTimeWrapper>
<Space style={{ float: "right", display: "block" }}> <Space style={{ float: "right", display: "block" }}>
@ -256,8 +276,10 @@ const mapStateToProps = (state: StoreState): { globalTime: GlobalTime } => {
return { globalTime: state.globalTime }; return { globalTime: state.globalTime };
}; };
export const DateTimeSelector = connect(mapStateToProps, { export const DateTimeSelector = withRouter(
updateTimeInterval: updateTimeInterval, connect(mapStateToProps, {
})(_DateTimeSelector); updateTimeInterval: updateTimeInterval,
})(_DateTimeSelector),
);
export default withRouter(DateTimeSelector); export default DateTimeSelector;

View File

@ -0,0 +1,24 @@
import ROUTES from "Src/constants/routes";
export const Options = [
{ value: "5min", label: "Last 5 min" },
{ value: "15min", label: "Last 15 min" },
{ value: "30min", label: "Last 30 min" },
{ value: "1hr", label: "Last 1 hour" },
{ value: "6hr", label: "Last 6 hour" },
{ value: "1day", label: "Last 1 day" },
{ value: "1week", label: "Last 1 week" },
{ value: "custom", label: "Custom" },
];
export const ServiceMapOptions = [
{ value: "1min", label: "Last 1 min" },
{ value: "5min", label: "Last 5 min" },
];
export const DefaultOptionsBasedOnRoute = {
[ROUTES.SERVICE_MAP]: ServiceMapOptions[0].value,
[ROUTES.APPLICATION]: Options[0].value,
[ROUTES.SERVICE_METRICS]: Options[2].value,
default: Options[2].value,
};

View File

@ -0,0 +1,18 @@
import ROUTES from "Src/constants/routes";
export const getLocalStorageRouteKey = (pathName: string) => {
let localStorageKey = "";
const pathNameSplit = pathName.split("/");
if (!pathNameSplit[2]) {
localStorageKey = pathName;
} else {
Object.keys(ROUTES).forEach((key) => {
if (ROUTES[key].indexOf(":") > -1) {
if (ROUTES[key].indexOf(pathNameSplit[1]) > -1) {
localStorageKey = ROUTES[key];
}
}
});
}
return localStorageKey;
};

View File

@ -10,7 +10,7 @@ import {
import { Spin } from "antd"; import { Spin } from "antd";
import styled from "styled-components"; import styled from "styled-components";
import { StoreState } from "../../store/reducers"; import { StoreState } from "../../store/reducers";
import { getGraphData } from "./utils"; import { getZoomPx, getGraphData, getTooltip } from "./utils";
import SelectService from "./SelectService"; import SelectService from "./SelectService";
import { ForceGraph2D } from "react-force-graph"; import { ForceGraph2D } from "react-force-graph";
@ -62,7 +62,7 @@ const ServiceMap = (props: ServiceMapProps) => {
useEffect(() => { useEffect(() => {
getServiceMapItems(globalTime); getServiceMapItems(globalTime);
getDetailedServiceMapItems(globalTime); getDetailedServiceMapItems(globalTime);
}, []); }, [globalTime]);
useEffect(() => { useEffect(() => {
fgRef.current && fgRef.current.d3Force("charge").strength(-400); fgRef.current && fgRef.current.d3Force("charge").strength(-400);
@ -72,7 +72,7 @@ const ServiceMap = (props: ServiceMapProps) => {
} }
const zoomToService = (value: string) => { const zoomToService = (value: string) => {
fgRef && fgRef.current.zoomToFit(700, 380, (e) => e.id === value); fgRef && fgRef.current.zoomToFit(700, getZoomPx(), (e) => e.id === value);
}; };
const { nodes, links } = getGraphData(serviceMap); const { nodes, links } = getGraphData(serviceMap);
@ -90,7 +90,7 @@ const ServiceMap = (props: ServiceMapProps) => {
fgRef.current.zoomToFit(100, 120); fgRef.current.zoomToFit(100, 120);
}} }}
graphData={graphData} graphData={graphData}
nodeLabel="id" nodeLabel={getTooltip}
linkAutoColorBy={(d) => d.target} linkAutoColorBy={(d) => d.target}
linkDirectionalParticles="value" linkDirectionalParticles="value"
linkDirectionalParticleSpeed={(d) => d.value} linkDirectionalParticleSpeed={(d) => d.value}
@ -112,21 +112,7 @@ const ServiceMap = (props: ServiceMapProps) => {
onNodeClick={(node) => { onNodeClick={(node) => {
const tooltip = document.querySelector(".graph-tooltip"); const tooltip = document.querySelector(".graph-tooltip");
if (tooltip && node) { if (tooltip && node) {
tooltip.innerHTML = `<div style="color:#333333;padding:12px;background: white;border-radius: 2px;"> tooltip.innerHTML = getTooltip(node);
<div style="font-weight:bold; margin-bottom:16px;">${node.id}</div>
<div class="keyval">
<div class="key">P99 latency:</div>
<div class="val">${node.p99 / 1000000}ms</div>
</div>
<div class="keyval">
<div class="key">Request:</div>
<div class="val">${node.callRate}/sec</div>
</div>
<div class="keyval">
<div class="key">Error Rate:</div>
<div class="val">${node.errorRate}%</div>
</div>
</div>`;
} }
}} }}
nodePointerAreaPaint={(node, color, ctx) => { nodePointerAreaPaint={(node, color, ctx) => {

View File

@ -73,3 +73,37 @@ export const getGraphData = (serviceMap: serviceMapStore): graphDataType => {
links, links,
}; };
}; };
export const getZoomPx = (): number => {
const width = window.screen.width;
if (width < 1400) {
return 190;
} else if (width > 1400 && width < 2500) {
return 380;
} else if (width > 2500) {
return 360;
}
};
export const getTooltip = (node: {
p99: number;
errorRate: number;
callRate: number;
id: string;
}) => {
return `<div style="color:#333333;padding:12px;background: white;border-radius: 2px;">
<div style="font-weight:bold; margin-bottom:16px;">${node.id}</div>
<div class="keyval">
<div class="key">P99 latency:</div>
<div class="val">${node.p99 / 1000000}ms</div>
</div>
<div class="keyval">
<div class="key">Request:</div>
<div class="val">${node.callRate}/sec</div>
</div>
<div class="keyval">
<div class="key">Error Rate:</div>
<div class="val">${node.errorRate}%</div>
</div>
</div>`;
};

View File

@ -23,6 +23,15 @@ export const updateTimeInterval = (
// set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element // set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element
switch (interval) { switch (interval) {
case "1min":
maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 1 * 60 * 1000) * 1000000;
break;
case "5min":
maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 5 * 60 * 1000) * 1000000;
break;
case "15min": case "15min":
maxTime = Date.now() * 1000000; // in nano sec maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 15 * 60 * 1000) * 1000000; minTime = (Date.now() - 15 * 60 * 1000) * 1000000;

View File

@ -38,6 +38,11 @@ export interface servicesAction {
export const getServiceMapItems = (globalTime: GlobalTime) => { export const getServiceMapItems = (globalTime: GlobalTime) => {
return async (dispatch: Dispatch) => { return async (dispatch: Dispatch) => {
dispatch<serviceMapItemAction>({
type: ActionTypes.getServiceMapItems,
payload: [],
});
let request_string = let request_string =
"/serviceMapDependencies?start=" + "/serviceMapDependencies?start=" +
globalTime.minTime + globalTime.minTime +
@ -45,7 +50,7 @@ export const getServiceMapItems = (globalTime: GlobalTime) => {
globalTime.maxTime; globalTime.maxTime;
const response = await api.get<servicesMapItem[]>(apiV1 + request_string); const response = await api.get<servicesMapItem[]>(apiV1 + request_string);
dispatch<serviceMapItemAction>({ dispatch<serviceMapItemAction>({
type: ActionTypes.getServiceMapItems, type: ActionTypes.getServiceMapItems,
payload: response.data, payload: response.data,
@ -55,11 +60,16 @@ export const getServiceMapItems = (globalTime: GlobalTime) => {
export const getDetailedServiceMapItems = (globalTime: GlobalTime) => { export const getDetailedServiceMapItems = (globalTime: GlobalTime) => {
return async (dispatch: Dispatch) => { return async (dispatch: Dispatch) => {
dispatch<servicesAction>({
type: ActionTypes.getServices,
payload: [],
});
let request_string = let request_string =
"/services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime; "/services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime;
const response = await api.get<servicesItem[]>(apiV1 + request_string); const response = await api.get<servicesItem[]>(apiV1 + request_string);
dispatch<servicesAction>({ dispatch<servicesAction>({
type: ActionTypes.getServices, type: ActionTypes.getServices,
payload: response.data, payload: response.data,