diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts index 26512b627d..5dd89c624c 100644 --- a/frontend/src/constants/localStorage.ts +++ b/frontend/src/constants/localStorage.ts @@ -1,3 +1,3 @@ export enum LOCAL_STORAGE { - METRICS_TIME_IN_DURATION = "metricsTimeDuration", + METRICS_TIME_IN_DURATION = "metricsTimeDurations", } diff --git a/frontend/src/modules/AppWrapper.tsx b/frontend/src/modules/AppWrapper.tsx index 716345594b..b261ad3e32 100644 --- a/frontend/src/modules/AppWrapper.tsx +++ b/frontend/src/modules/AppWrapper.tsx @@ -1,17 +1,11 @@ import React, { Suspense } from "react"; -import { Layout, Spin } from "antd"; +import { Spin } from "antd"; import { useThemeSwitcher } from "react-css-theme-switcher"; import ROUTES from "Src/constants/routes"; import { IS_LOGGED_IN } from "Src/constants/auth"; -import { - BrowserRouter as Router, - Route, - Switch, - Redirect, -} from "react-router-dom"; +import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom"; -import SideNav from "./Nav/SideNav"; -import TopNav from "./Nav/TopNav"; +import BaseLayout from "./BaseLayout"; import { ServiceMetrics, ServiceMap, @@ -24,8 +18,6 @@ import { IntstrumentationPage, } from "Src/pages"; -const { Content, Footer } = Layout; - const App = () => { const { status } = useThemeSwitcher(); @@ -34,47 +26,44 @@ const App = () => { } return ( - - - - - - - }> - - - - - - - - - - - { - return localStorage.getItem(IS_LOGGED_IN) === "yes" ? ( - - ) : ( - - ); - }} - /> - - - - - - - + + }> + + + + + + + + + + + + + { + return localStorage.getItem(IS_LOGGED_IN) === "yes" ? ( + + ) : ( + + ); + }} + /> + + + + + ); }; diff --git a/frontend/src/modules/BaseLayout.tsx b/frontend/src/modules/BaseLayout.tsx new file mode 100644 index 0000000000..a36ac96e66 --- /dev/null +++ b/frontend/src/modules/BaseLayout.tsx @@ -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 = ({ children }) => { + return ( + + + + + + {children} + +
+ SigNoz Inc. ©2020{" "} +
+
+
+ ); +}; + +export default BaseLayout; diff --git a/frontend/src/modules/Nav/TopNav/DateTimeSelector.tsx b/frontend/src/modules/Nav/TopNav/DateTimeSelector.tsx index c3b890673b..03758461d4 100644 --- a/frontend/src/modules/Nav/TopNav/DateTimeSelector.tsx +++ b/frontend/src/modules/Nav/TopNav/DateTimeSelector.tsx @@ -1,27 +1,32 @@ 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 { withRouter } from "react-router"; +import { getLocalStorageRouteKey } from "./utils"; import { RouteComponentProps, useLocation } from "react-router-dom"; import { connect } from "react-redux"; import ROUTES from "Src/constants/routes"; - import CustomDateTimeModal from "./CustomDateTimeModal"; import { GlobalTime, updateTimeInterval } from "../../../store/actions"; import { StoreState } from "../../../store/reducers"; import FormItem from "antd/lib/form/FormItem"; - +import { + Options, + ServiceMapOptions, + DefaultOptionsBasedOnRoute, +} from "./config"; import { DateTimeRangeType } from "../../../store/actions"; import { METRICS_PAGE_QUERY_PARAM } from "Src/constants/query"; import { LOCAL_STORAGE } from "Src/constants/localStorage"; import moment from "moment"; -const { Option } = Select; +const { Option } = DefaultSelect; const DateTimeWrapper = styled.div` margin-top: 20px; justify-content: flex-end !important; `; - +const Select = styled(DefaultSelect)``; interface DateTimeSelectorProps extends RouteComponentProps { currentpath?: string; updateTimeInterval: Function; @@ -32,21 +37,34 @@ interface DateTimeSelectorProps extends RouteComponentProps { This components is mounted all the time. Use event listener to track changes. */ 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 [timeInterval, setTimeInterval] = useState(defaultTime); const [startTime, setStartTime] = useState(null); const [endTime, setEndTime] = useState(null); const [refreshButtonHidden, setRefreshButtonHidden] = useState(false); const [refreshText, setRefreshText] = useState(""); - const [refreshButtonClick, setRefreshButtoClick] = useState(0); + const [refreshButtonClick, setRefreshButtonClick] = useState(0); const [form_dtselector] = Form.useForm(); - const location = useLocation(); const updateTimeOnQueryParamChange = () => { - const timeDurationInLocalStorage = localStorage.getItem( - LOCAL_STORAGE.METRICS_TIME_IN_DURATION, - ); const urlParams = new URLSearchParams(location.search); const intervalInQueryParam = urlParams.get(METRICS_PAGE_QUERY_PARAM.interval); const startTimeString = urlParams.get(METRICS_PAGE_QUERY_PARAM.startTime); @@ -62,36 +80,46 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => { const startTime = moment(Number(startTimeString)); const endTime = moment(Number(endTimeString)); setCustomTime(startTime, endTime, true); + } else if (currentLocalStorageRouteKey !== LocalStorageRouteKey) { + setMetricsTimeInterval(defaultTime); + setCurrentLocalStorageRouteKey(LocalStorageRouteKey); } // first pref: handle intervalInQueryParam else if (intervalInQueryParam) { - window.localStorage.setItem( - LOCAL_STORAGE.METRICS_TIME_IN_DURATION, - 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 useEffect(() => { updateTimeOnQueryParamChange(); }, [location]); - //On mount - useEffect(() => { - updateTimeOnQueryParamChange(); - }, []); - const setMetricsTimeInterval = (value: string) => { props.updateTimeInterval(value); setTimeInterval(value); setEndTime(null); setStartTime(null); - - window.localStorage.setItem(LOCAL_STORAGE.METRICS_TIME_IN_DURATION, value); + setToLocalStorage(value); }; const setCustomTime = ( startTime: moment.Moment, @@ -173,7 +201,7 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => { }; const handleRefresh = () => { - setRefreshButtoClick(refreshButtonClick + 1); + setRefreshButtonClick(refreshButtonClick + 1); setMetricsTimeInterval(timeInterval); }; @@ -187,15 +215,6 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => { }; }, [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)) { return null; } else { @@ -205,6 +224,7 @@ const _DateTimeSelector = (props: DateTimeSelectorProps) => { "YYYY/MM/DD HH:mm", )}` : timeInterval; + return ( @@ -256,8 +276,10 @@ const mapStateToProps = (state: StoreState): { globalTime: GlobalTime } => { return { globalTime: state.globalTime }; }; -export const DateTimeSelector = connect(mapStateToProps, { - updateTimeInterval: updateTimeInterval, -})(_DateTimeSelector); +export const DateTimeSelector = withRouter( + connect(mapStateToProps, { + updateTimeInterval: updateTimeInterval, + })(_DateTimeSelector), +); -export default withRouter(DateTimeSelector); +export default DateTimeSelector; diff --git a/frontend/src/modules/Nav/TopNav/config.ts b/frontend/src/modules/Nav/TopNav/config.ts new file mode 100644 index 0000000000..1cad032461 --- /dev/null +++ b/frontend/src/modules/Nav/TopNav/config.ts @@ -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, +}; diff --git a/frontend/src/modules/Nav/TopNav/utils.ts b/frontend/src/modules/Nav/TopNav/utils.ts new file mode 100644 index 0000000000..e46503312e --- /dev/null +++ b/frontend/src/modules/Nav/TopNav/utils.ts @@ -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; +}; diff --git a/frontend/src/modules/Servicemap/ServiceMap.tsx b/frontend/src/modules/Servicemap/ServiceMap.tsx index e2db06ad70..e9cf6c2425 100644 --- a/frontend/src/modules/Servicemap/ServiceMap.tsx +++ b/frontend/src/modules/Servicemap/ServiceMap.tsx @@ -10,7 +10,7 @@ import { import { Spin } from "antd"; import styled from "styled-components"; import { StoreState } from "../../store/reducers"; -import { getGraphData } from "./utils"; +import { getZoomPx, getGraphData, getTooltip } from "./utils"; import SelectService from "./SelectService"; import { ForceGraph2D } from "react-force-graph"; @@ -62,7 +62,7 @@ const ServiceMap = (props: ServiceMapProps) => { useEffect(() => { getServiceMapItems(globalTime); getDetailedServiceMapItems(globalTime); - }, []); + }, [globalTime]); useEffect(() => { fgRef.current && fgRef.current.d3Force("charge").strength(-400); @@ -72,7 +72,7 @@ const ServiceMap = (props: ServiceMapProps) => { } 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); @@ -90,7 +90,7 @@ const ServiceMap = (props: ServiceMapProps) => { fgRef.current.zoomToFit(100, 120); }} graphData={graphData} - nodeLabel="id" + nodeLabel={getTooltip} linkAutoColorBy={(d) => d.target} linkDirectionalParticles="value" linkDirectionalParticleSpeed={(d) => d.value} @@ -112,21 +112,7 @@ const ServiceMap = (props: ServiceMapProps) => { onNodeClick={(node) => { const tooltip = document.querySelector(".graph-tooltip"); if (tooltip && node) { - tooltip.innerHTML = `
-
${node.id}
-
-
P99 latency:
-
${node.p99 / 1000000}ms
-
-
-
Request:
-
${node.callRate}/sec
-
-
-
Error Rate:
-
${node.errorRate}%
-
-
`; + tooltip.innerHTML = getTooltip(node); } }} nodePointerAreaPaint={(node, color, ctx) => { diff --git a/frontend/src/modules/Servicemap/utils.ts b/frontend/src/modules/Servicemap/utils.ts index 296cd05060..bec9c16a68 100644 --- a/frontend/src/modules/Servicemap/utils.ts +++ b/frontend/src/modules/Servicemap/utils.ts @@ -73,3 +73,37 @@ export const getGraphData = (serviceMap: serviceMapStore): graphDataType => { 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 `
+
${node.id}
+
+
P99 latency:
+
${node.p99 / 1000000}ms
+
+
+
Request:
+
${node.callRate}/sec
+
+
+
Error Rate:
+
${node.errorRate}%
+
+
`; +}; diff --git a/frontend/src/store/actions/global.ts b/frontend/src/store/actions/global.ts index 4af5e7adce..25c25852dc 100644 --- a/frontend/src/store/actions/global.ts +++ b/frontend/src/store/actions/global.ts @@ -23,6 +23,15 @@ export const updateTimeInterval = ( // set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element 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": maxTime = Date.now() * 1000000; // in nano sec minTime = (Date.now() - 15 * 60 * 1000) * 1000000; diff --git a/frontend/src/store/actions/serviceMap.ts b/frontend/src/store/actions/serviceMap.ts index a8a660d423..fbad4148c3 100644 --- a/frontend/src/store/actions/serviceMap.ts +++ b/frontend/src/store/actions/serviceMap.ts @@ -38,6 +38,11 @@ export interface servicesAction { export const getServiceMapItems = (globalTime: GlobalTime) => { return async (dispatch: Dispatch) => { + dispatch({ + type: ActionTypes.getServiceMapItems, + payload: [], + }); + let request_string = "/serviceMapDependencies?start=" + globalTime.minTime + @@ -45,7 +50,7 @@ export const getServiceMapItems = (globalTime: GlobalTime) => { globalTime.maxTime; const response = await api.get(apiV1 + request_string); - + dispatch({ type: ActionTypes.getServiceMapItems, payload: response.data, @@ -55,11 +60,16 @@ export const getServiceMapItems = (globalTime: GlobalTime) => { export const getDetailedServiceMapItems = (globalTime: GlobalTime) => { return async (dispatch: Dispatch) => { + dispatch({ + type: ActionTypes.getServices, + payload: [], + }); + let request_string = "/services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime; const response = await api.get(apiV1 + request_string); - + dispatch({ type: ActionTypes.getServices, payload: response.data,