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 -e "🟢 Your frontend is running on http://localhost:3301"
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 ""
echo "+++++++++++++++++++++++++++++++++++++++++++++++++"
echo ""
echo "👉 Need help Getting Started?"
echo "👉 Need help in Getting Started?"
echo -e "Join us on Slack https://signoz.io/slack"
echo ""
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-grid-layout": "^1.3.4",
"react-i18next": "^11.16.1",
"react-intersection-observer": "9.4.1",
"react-query": "^3.34.19",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",

View File

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

View File

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

View File

@ -1,12 +1,6 @@
import { grey } from '@ant-design/colors';
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 }>`
width: 100%;
text-align: center;

View File

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

View File

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

View File

@ -1,3 +1,5 @@
import { DashboardData } from 'types/api/dashboard/getAll';
export function downloadObjectAsJson(
exportObj: unknown,
exportName: string,
@ -12,3 +14,18 @@ export function downloadObjectAsJson(
downloadAnchorNode.click();
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 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) || '{}');
@ -132,6 +135,11 @@ function AutoRefresh({ disabled = false }: AutoRefreshProps): JSX.Element {
[localStorageData, pathname],
);
if (globalTime.selectedTime === 'custom') {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
return (
<Popover
placement="bottomLeft"

View File

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

View File

@ -11104,6 +11104,11 @@ react-i18next@^11.16.1:
"@babel/runtime" "^7.14.5"
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:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"