Merge branch 'develop' into release/v0.15.0

This commit is contained in:
Prashant Shahi 2023-01-31 00:20:08 +05:30
commit e6ce80213b
12 changed files with 174 additions and 81 deletions

View File

@ -511,13 +511,15 @@ else
echo "" echo ""
echo -e "🟢 Your frontend is running on http://localhost:3301" echo -e "🟢 Your frontend is running on http://localhost:3301"
echo "" echo ""
echo " By default, retention period is set to 7 days for logs and traces, and 30 days for metrics."
echo -e "To change this, navigate to the General tab on the Settings page of SigNoz UI. For more details, refer to https://signoz.io/docs/userguide/retention-period \n"
echo " To bring down SigNoz and clean volumes : $sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml down -v" echo " To bring down SigNoz and clean volumes : $sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml down -v"
echo "" echo ""
echo "+++++++++++++++++++++++++++++++++++++++++++++++++" echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
echo "" echo ""
echo "👉 Need help Getting Started?" echo "👉 Need help in Getting Started?"
echo -e "Join us on Slack https://signoz.io/slack" echo -e "Join us on Slack https://signoz.io/slack"
echo "" echo ""
echo -e "\n📨 Please share your email to receive support & updates about SigNoz!" echo -e "\n📨 Please share your email to receive support & updates about SigNoz!"

View File

@ -76,6 +76,7 @@
"react-graph-vis": "^1.0.5", "react-graph-vis": "^1.0.5",
"react-grid-layout": "^1.3.4", "react-grid-layout": "^1.3.4",
"react-i18next": "^11.16.1", "react-i18next": "^11.16.1",
"react-intersection-observer": "9.4.1",
"react-query": "^3.34.19", "react-query": "^3.34.19",
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",

View File

@ -8,6 +8,7 @@ import getChartData from 'lib/getChartData';
import isEmpty from 'lodash-es/isEmpty'; import isEmpty from 'lodash-es/isEmpty';
import React, { memo, useCallback, useMemo, useState } from 'react'; import React, { memo, useCallback, useMemo, useState } from 'react';
import { Layout } from 'react-grid-layout'; import { Layout } from 'react-grid-layout';
import { useInView } from 'react-intersection-observer';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { connect, useSelector } from 'react-redux'; import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
@ -39,6 +40,11 @@ function GridCardGraph({
setLayout, setLayout,
onDragSelect, onDragSelect,
}: GridCardGraphProps): JSX.Element { }: GridCardGraphProps): JSX.Element {
const { ref: graphRef, inView: isGraphVisible } = useInView({
threshold: 0,
triggerOnce: true,
});
const [errorMessage, setErrorMessage] = useState<string | undefined>(''); const [errorMessage, setErrorMessage] = useState<string | undefined>('');
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const [modal, setModal] = useState(false); const [modal, setModal] = useState(false);
@ -79,6 +85,7 @@ function GridCardGraph({
}), }),
{ {
keepPreviousData: true, keepPreviousData: true,
enabled: isGraphVisible,
refetchOnMount: false, refetchOnMount: false,
onError: (error) => { onError: (error) => {
if (error instanceof Error) { if (error instanceof Error) {
@ -160,7 +167,7 @@ function GridCardGraph({
if (queryResponse.isError && !isEmptyLayout) { if (queryResponse.isError && !isEmptyLayout) {
return ( return (
<span> <span ref={graphRef}>
{getModals()} {getModals()}
{!isEmpty(widget) && prevChartDataSetRef && ( {!isEmpty(widget) && prevChartDataSetRef && (
<> <>
@ -192,7 +199,7 @@ function GridCardGraph({
if (prevChartDataSetRef?.labels === undefined && queryResponse.isLoading) { if (prevChartDataSetRef?.labels === undefined && queryResponse.isLoading) {
return ( return (
<span> <span ref={graphRef}>
{!isEmpty(widget) && prevChartDataSetRef?.labels ? ( {!isEmpty(widget) && prevChartDataSetRef?.labels ? (
<> <>
<div className="drag-handle"> <div className="drag-handle">
@ -225,6 +232,7 @@ function GridCardGraph({
return ( return (
<span <span
ref={graphRef}
onMouseOver={(): void => { onMouseOver={(): void => {
setHovered(true); setHovered(true);
}} }}
@ -260,7 +268,7 @@ function GridCardGraph({
data={chartData} data={chartData}
isStacked={widget.isStacked} isStacked={widget.isStacked}
opacity={widget.opacity} opacity={widget.opacity}
title={' '} // empty title to accommodate absolutely positioned widget header title={' '} // `empty title to accommodate absolutely positioned widget header
name={name} name={name}
yAxisUnit={yAxisUnit} yAxisUnit={yAxisUnit}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}

View File

@ -10,4 +10,13 @@ export const tooltipStyles = {
right: '0.313rem', right: '0.313rem',
color: themeColors.errorColor, color: themeColors.errorColor,
}; };
export const errorTooltipPosition = 'top'; export const errorTooltipPosition = 'top';
export const overlayStyles: React.CSSProperties = {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
};

View File

@ -5,11 +5,12 @@ import {
ExclamationCircleOutlined, ExclamationCircleOutlined,
FullscreenOutlined, FullscreenOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Dropdown, Menu, Tooltip, Typography } from 'antd'; import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history'; import history from 'lib/history';
import React, { useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { UseQueryResult } from 'react-query'; import { UseQueryResult } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@ -18,12 +19,16 @@ import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { errorTooltipPosition, spinnerStyles, tooltipStyles } from './config'; import {
errorTooltipPosition,
overlayStyles,
spinnerStyles,
tooltipStyles,
} from './config';
import { import {
ArrowContainer, ArrowContainer,
HeaderContainer, HeaderContainer,
HeaderContentContainer, HeaderContentContainer,
MenuItemContainer,
} from './styles'; } from './styles';
type TWidgetOptions = 'view' | 'edit' | 'delete' | string; type TWidgetOptions = 'view' | 'edit' | 'delete' | string;
@ -48,33 +53,45 @@ function WidgetHeader({
errorMessage, errorMessage,
}: IWidgetHeaderProps): JSX.Element { }: IWidgetHeaderProps): JSX.Element {
const [localHover, setLocalHover] = useState(false); const [localHover, setLocalHover] = useState(false);
const [isOpen, setIsOpen] = useState<boolean>(false);
const onEditHandler = (): void => { const onEditHandler = useCallback((): void => {
const widgetId = widget.id; const widgetId = widget.id;
history.push( history.push(
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`, `${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`,
); );
}; }, [widget.id, widget.panelTypes]);
const keyMethodMapping: { const keyMethodMapping: {
[K in TWidgetOptions]: { key: TWidgetOptions; method: VoidFunction }; [K in TWidgetOptions]: { key: TWidgetOptions; method: VoidFunction };
} = { } = useMemo(
view: { () => ({
key: 'view', view: {
method: onView, key: 'view',
method: onView,
},
edit: {
key: 'edit',
method: onEditHandler,
},
delete: {
key: 'delete',
method: onDelete,
},
}),
[onDelete, onEditHandler, onView],
);
const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback(
({ key }: { key: TWidgetOptions }): void => {
const functionToCall = keyMethodMapping[key]?.method;
if (functionToCall) {
functionToCall();
setIsOpen(false);
}
}, },
edit: { [keyMethodMapping],
key: 'edit', );
method: onEditHandler,
},
delete: {
key: 'delete',
method: onDelete,
},
};
const onMenuItemSelectHandler = ({ key }: { key: TWidgetOptions }): void => {
keyMethodMapping[key]?.method();
};
const { role } = useSelector<AppState, AppReducer>((state) => state.app); const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const [deleteWidget, editWidget] = useComponentPermission( const [deleteWidget, editWidget] = useComponentPermission(
@ -82,49 +99,67 @@ function WidgetHeader({
role, role,
); );
const menu = ( const menuList: MenuItemType[] = useMemo(
<Menu onClick={onMenuItemSelectHandler}> () => [
<Menu.Item key={keyMethodMapping.view.key}> {
<MenuItemContainer> key: keyMethodMapping.view.key,
<span>View</span> <FullscreenOutlined /> icon: <FullscreenOutlined />,
</MenuItemContainer> disabled: queryResponse.isLoading,
</Menu.Item> label: 'View',
},
{
key: keyMethodMapping.edit.key,
icon: <EditFilled />,
disabled: !editWidget,
label: 'Edit',
},
{
key: keyMethodMapping.delete.key,
icon: <DeleteOutlined />,
disabled: !deleteWidget,
danger: true,
label: 'Delete',
},
],
[
deleteWidget,
editWidget,
keyMethodMapping.delete.key,
keyMethodMapping.edit.key,
keyMethodMapping.view.key,
queryResponse.isLoading,
],
);
{editWidget && ( const onClickHandler = useCallback(() => {
<Menu.Item key={keyMethodMapping.edit.key}> setIsOpen((open) => !open);
<MenuItemContainer> }, []);
<span>Edit</span> <EditFilled />
</MenuItemContainer>
</Menu.Item>
)}
{deleteWidget && ( const menu = useMemo(
<> () => ({
<Menu.Divider /> items: menuList,
<Menu.Item key={keyMethodMapping.delete.key} danger> onClick: onMenuItemSelectHandler,
<MenuItemContainer> }),
<span>Delete</span> <DeleteOutlined /> [menuList, onMenuItemSelectHandler],
</MenuItemContainer>
</Menu.Item>
</>
)}
</Menu>
); );
return ( return (
<Dropdown <div>
overlay={menu} <Dropdown
trigger={['click']} destroyPopupOnHide
overlayStyle={{ minWidth: 100 }} open={isOpen}
placement="bottom" onOpenChange={setIsOpen}
> menu={menu}
<> trigger={['click']}
overlayStyle={overlayStyles}
>
<HeaderContainer <HeaderContainer
onMouseOver={(): void => setLocalHover(true)} onMouseOver={(): void => setLocalHover(true)}
onMouseOut={(): void => setLocalHover(false)} onMouseOut={(): void => setLocalHover(false)}
hover={localHover} hover={localHover}
onClick={onClickHandler}
> >
<HeaderContentContainer onClick={(e): void => e.preventDefault()}> <HeaderContentContainer>
<Typography.Text style={{ maxWidth: '80%' }} ellipsis> <Typography.Text style={{ maxWidth: '80%' }} ellipsis>
{title} {title}
</Typography.Text> </Typography.Text>
@ -133,16 +168,16 @@ function WidgetHeader({
</ArrowContainer> </ArrowContainer>
</HeaderContentContainer> </HeaderContentContainer>
</HeaderContainer> </HeaderContainer>
{queryResponse.isFetching && !queryResponse.isError && ( </Dropdown>
<Spinner height="5vh" style={spinnerStyles} /> {queryResponse.isFetching && !queryResponse.isError && (
)} <Spinner height="5vh" style={spinnerStyles} />
{queryResponse.isError && ( )}
<Tooltip title={errorMessage} placement={errorTooltipPosition}> {queryResponse.isError && (
<ExclamationCircleOutlined style={tooltipStyles} /> <Tooltip title={errorMessage} placement={errorTooltipPosition}>
</Tooltip> <ExclamationCircleOutlined style={tooltipStyles} />
)} </Tooltip>
</> )}
</Dropdown> </div>
); );
} }

View File

@ -1,12 +1,6 @@
import { grey } from '@ant-design/colors'; import { grey } from '@ant-design/colors';
import styled from 'styled-components'; import styled from 'styled-components';
export const MenuItemContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`;
export const HeaderContainer = styled.div<{ hover: boolean }>` export const HeaderContainer = styled.div<{ hover: boolean }>`
width: 100%; width: 100%;
text-align: center; text-align: center;

View File

@ -47,6 +47,11 @@ function SearchFields({
} }
}, [parsedQuery]); }, [parsedQuery]);
const updateFieldsQuery = (updated: QueryFields[][]): void => {
setFieldsQuery(updated);
keyPrefixRef.current = hashCode(JSON.stringify(updated));
};
const addSuggestedField = useCallback( const addSuggestedField = useCallback(
(name: string): void => { (name: string): void => {
if (!name) { if (!name) {
@ -97,7 +102,7 @@ function SearchFields({
keyPrefix={keyPrefixRef.current} keyPrefix={keyPrefixRef.current}
onDropDownToggleHandler={onDropDownToggleHandler} onDropDownToggleHandler={onDropDownToggleHandler}
fieldsQuery={fieldsQuery} fieldsQuery={fieldsQuery}
setFieldsQuery={setFieldsQuery} setFieldsQuery={updateFieldsQuery}
/> />
<SearchFieldsActionBar <SearchFieldsActionBar
applyUpdate={applyUpdate} applyUpdate={applyUpdate}

View File

@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { DashboardData } from 'types/api/dashboard/getAll'; import { DashboardData } from 'types/api/dashboard/getAll';
import { downloadObjectAsJson } from './util'; import { cleardQueryData, downloadObjectAsJson } from './util';
function ShareModal({ function ShareModal({
isJSONModalVisible, isJSONModalVisible,
@ -51,6 +51,7 @@ function ShareModal({
} }
}, [state.error, state.value, t]); }, [state.error, state.value, t]);
const selectedDataCleaned = cleardQueryData(selectedData);
const GetFooterComponent = useMemo(() => { const GetFooterComponent = useMemo(() => {
if (!isViewJSON) { if (!isViewJSON) {
return ( return (
@ -66,7 +67,7 @@ function ShareModal({
<Button <Button
type="primary" type="primary"
onClick={(): void => { onClick={(): void => {
downloadObjectAsJson(selectedData, selectedData.title); downloadObjectAsJson(selectedDataCleaned, selectedData.title);
}} }}
> >
{t('download_json')} {t('download_json')}
@ -79,7 +80,7 @@ function ShareModal({
{t('copy_to_clipboard')} {t('copy_to_clipboard')}
</Button> </Button>
); );
}, [isViewJSON, jsonValue, selectedData, setCopy, t]); }, [isViewJSON, jsonValue, selectedData, selectedDataCleaned, setCopy, t]);
return ( return (
<Modal <Modal

View File

@ -1,3 +1,5 @@
import { DashboardData } from 'types/api/dashboard/getAll';
export function downloadObjectAsJson( export function downloadObjectAsJson(
exportObj: unknown, exportObj: unknown,
exportName: string, exportName: string,
@ -12,3 +14,18 @@ export function downloadObjectAsJson(
downloadAnchorNode.click(); downloadAnchorNode.click();
downloadAnchorNode.remove(); downloadAnchorNode.remove();
} }
export function cleardQueryData(param: DashboardData): DashboardData {
return {
...param,
widgets: param.widgets?.map((widget) => ({
...widget,
queryData: {
...widget.queryData,
data: {
queryData: [],
},
},
})),
};
}

View File

@ -37,8 +37,11 @@ function AutoRefresh({ disabled = false }: AutoRefreshProps): JSX.Element {
const { pathname } = useLocation(); const { pathname } = useLocation();
const isDisabled = useMemo( const isDisabled = useMemo(
() => disabled || globalTime.isAutoRefreshDisabled, () =>
[globalTime.isAutoRefreshDisabled, disabled], disabled ||
globalTime.isAutoRefreshDisabled ||
globalTime.selectedTime === 'custom',
[globalTime.isAutoRefreshDisabled, disabled, globalTime.selectedTime],
); );
const localStorageData = JSON.parse(get(DASHBOARD_TIME_IN_DURATION) || '{}'); const localStorageData = JSON.parse(get(DASHBOARD_TIME_IN_DURATION) || '{}');
@ -132,6 +135,11 @@ function AutoRefresh({ disabled = false }: AutoRefreshProps): JSX.Element {
[localStorageData, pathname], [localStorageData, pathname],
); );
if (globalTime.selectedTime === 'custom') {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
return ( return (
<Popover <Popover
placement="bottomLeft" placement="bottomLeft"

View File

@ -88,6 +88,14 @@ function DateTimeSelection({
return timeInterval; return timeInterval;
}; };
useEffect(() => {
if (selectedTime === 'custom') {
setRefreshButtonHidden(true);
} else {
setRefreshButtonHidden(false);
}
}, [selectedTime]);
const getDefaultTime = (pathName: string): Time => { const getDefaultTime = (pathName: string): Time => {
const defaultSelectedOption = getDefaultOption(pathName); const defaultSelectedOption = getDefaultOption(pathName);

View File

@ -11104,6 +11104,11 @@ react-i18next@^11.16.1:
"@babel/runtime" "^7.14.5" "@babel/runtime" "^7.14.5"
html-parse-stringify "^3.0.1" html-parse-stringify "^3.0.1"
react-intersection-observer@9.4.1:
version "9.4.1"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.1.tgz#4ccb21e16acd0b9cf5b28d275af7055bef878f6b"
integrity sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"