ui: comparison designs
This commit is contained in:
parent
19b5addc95
commit
29fec35046
14 changed files with 677 additions and 457 deletions
File diff suppressed because one or more lines are too long
|
|
@ -4,4 +4,4 @@ enableGlobalCache: true
|
|||
|
||||
nodeLinker: pnpm
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.5.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.5.3.cjs
|
||||
|
|
|
|||
|
|
@ -1,13 +1,38 @@
|
|||
import React from "react";
|
||||
import { formatTimeOrDate } from "App/date";
|
||||
|
||||
import React from 'react';
|
||||
import { formatTimeOrDate } from 'App/date';
|
||||
import cn from 'classnames';
|
||||
import { ArrowUp, ArrowDown } from 'lucide-react'
|
||||
function CustomTooltip({ active, payload, label }) {
|
||||
if (!active) return;
|
||||
|
||||
const shownPayloads = payload.filter((p) => !p.hide);
|
||||
const shownPayloads: Record<string, any>[] = payload.filter((p) => !p.hide);
|
||||
const currentSeries: { value: number }[] = [];
|
||||
const previousSeriesMap: Record<string, any> = {};
|
||||
|
||||
shownPayloads.forEach((item) => {
|
||||
if (item.name.startsWith('Previous ')) {
|
||||
const originalName = item.name.replace('Previous ', '');
|
||||
previousSeriesMap[originalName] = item.value;
|
||||
} else {
|
||||
currentSeries.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
const transformedArray = currentSeries.map((item) => {
|
||||
const prevValue = previousSeriesMap[item.name] || null;
|
||||
return {
|
||||
...item,
|
||||
prevValue,
|
||||
};
|
||||
});
|
||||
const isHigher = (item: { value: number; prevValue: number }) => {
|
||||
return item.prevValue !== null && item.prevValue < item.value;
|
||||
};
|
||||
return (
|
||||
<div className={'flex flex-col gap-1 bg-white shadow border rounded p-2'}>
|
||||
{shownPayloads.map((p, index) => (
|
||||
<div
|
||||
className={'flex flex-col gap-1 bg-white shadow border rounded p-2 z-30'}
|
||||
>
|
||||
{transformedArray.map((p, index) => (
|
||||
<>
|
||||
<div className={'flex gap-2 items-center'}>
|
||||
<div
|
||||
|
|
@ -25,7 +50,22 @@ function CustomTooltip({ active, payload, label }) {
|
|||
<div className={'text-disabled-text text-sm'}>
|
||||
{label}, {formatTimeOrDate(p.payload.timestamp)}
|
||||
</div>
|
||||
<div className={'font-semibold'}>{p.value}</div>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<div className={'font-semibold'}>{p.value}</div>
|
||||
{p.prevValue !== null ? (
|
||||
<div
|
||||
className={cn(
|
||||
'px-2 py-1 rounded flex items-center gap-1',
|
||||
isHigher(p) ? 'bg-green2 text-xs' : 'bg-red2 text-xs'
|
||||
)}
|
||||
>
|
||||
{!isHigher(p) ? <ArrowDown size={12} /> : <ArrowUp size={12} />}
|
||||
<div>
|
||||
{p.prevValue}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
|
|
@ -33,4 +73,4 @@ function CustomTooltip({ active, payload, label }) {
|
|||
);
|
||||
}
|
||||
|
||||
export default CustomTooltip;
|
||||
export default CustomTooltip;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import cn from 'classnames';
|
|||
|
||||
interface Props {
|
||||
data: any;
|
||||
compData: any | null;
|
||||
params: any;
|
||||
colors: any;
|
||||
onClick?: (event, index) => void;
|
||||
|
|
@ -31,6 +32,7 @@ interface Props {
|
|||
function CustomMetricLineChart(props: Props) {
|
||||
const {
|
||||
data = { chart: [], namesMap: [] },
|
||||
compData,
|
||||
params,
|
||||
colors,
|
||||
onClick = () => null,
|
||||
|
|
@ -39,10 +41,14 @@ function CustomMetricLineChart(props: Props) {
|
|||
hideLegend = false,
|
||||
} = props;
|
||||
|
||||
const resultChart = data.chart.map((item, i) => {
|
||||
if (compData && compData.chart[i]) return { ...compData.chart[i], ...item }
|
||||
return item
|
||||
})
|
||||
return (
|
||||
<ResponsiveContainer height={240} width="100%">
|
||||
<LineChart
|
||||
data={data.chart}
|
||||
data={resultChart}
|
||||
margin={Styles.chartMargins}
|
||||
onClick={onClick}
|
||||
>
|
||||
|
|
@ -85,6 +91,25 @@ function CustomMetricLineChart(props: Props) {
|
|||
}}
|
||||
/>
|
||||
) : null)}
|
||||
{compData ? compData.namesMap.map((key, i) => (
|
||||
<Line
|
||||
key={key}
|
||||
name={key}
|
||||
animationDuration={0}
|
||||
type="monotone"
|
||||
dataKey={key}
|
||||
stroke={colors[i]}
|
||||
fillOpacity={1}
|
||||
strokeWidth={2}
|
||||
strokeOpacity={0.6}
|
||||
legendType={'line'}
|
||||
dot={false}
|
||||
strokeDasharray={'4 3'}
|
||||
activeDot={{
|
||||
fill: colors[i],
|
||||
}}
|
||||
/>
|
||||
)) : null}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ function WidgetChart(props: Props) {
|
|||
const prevMetricRef = useRef<any>();
|
||||
const isMounted = useIsMounted();
|
||||
const [data, setData] = useState<any>(metric.data);
|
||||
const [compData, setCompData] = useState<any>(null);
|
||||
const [enabledRows, setEnabledRows] = useState([]);
|
||||
const isTableWidget =
|
||||
metric.metricType === 'table' && metric.viewType === 'table';
|
||||
|
|
@ -121,14 +122,18 @@ function WidgetChart(props: Props) {
|
|||
metric: any,
|
||||
payload: any,
|
||||
isSaved: any,
|
||||
period: any
|
||||
period: any,
|
||||
isComparison?: boolean
|
||||
) => {
|
||||
if (!isMounted()) return;
|
||||
setLoading(true);
|
||||
dashboardStore
|
||||
.fetchMetricChartData(metric, payload, isSaved, period)
|
||||
.fetchMetricChartData(metric, payload, isSaved, period, isComparison)
|
||||
.then((res: any) => {
|
||||
if (isMounted()) setData(res);
|
||||
if (isMounted()) {
|
||||
if (isComparison) setCompData(res)
|
||||
else setData(res);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
|
@ -157,6 +162,17 @@ function WidgetChart(props: Props) {
|
|||
!isSaved ? drillDownPeriod : period
|
||||
);
|
||||
};
|
||||
const loadComparisonData = () => {
|
||||
if (!inView) return;
|
||||
if (!dashboardStore.comparisonPeriod) return setCompData(null);
|
||||
|
||||
const timestamps = dashboardStore.comparisonPeriod.toTimestamps();
|
||||
const payload = { ...metricParams, ...timestamps, ...metric.toJson() };
|
||||
fetchMetricChartData(metric, payload, isSaved, dashboardStore.comparisonPeriod, true);
|
||||
}
|
||||
useEffect(() => {
|
||||
loadComparisonData();
|
||||
}, [dashboardStore.comparisonPeriod])
|
||||
useEffect(() => {
|
||||
_metric.updateKey('page', 1);
|
||||
loadPage();
|
||||
|
|
@ -210,6 +226,7 @@ function WidgetChart(props: Props) {
|
|||
return (
|
||||
<CustomMetricLineChart
|
||||
data={chartData}
|
||||
compData={compData}
|
||||
colors={colors}
|
||||
params={params}
|
||||
onClick={onChartClick}
|
||||
|
|
|
|||
|
|
@ -95,13 +95,8 @@ function WidgetDatatable(props: Props) {
|
|||
|
||||
const rowSelection: TableProps['rowSelection'] = {
|
||||
selectedRowKeys: props.enabledRows,
|
||||
onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => {
|
||||
onChange: (selectedRowKeys: React.Key[]) => {
|
||||
props.setEnabledRows(selectedRowKeys as string[]);
|
||||
console.log(
|
||||
`selectedRowKeys: ${selectedRowKeys}`,
|
||||
'selectedRows: ',
|
||||
selectedRows
|
||||
);
|
||||
},
|
||||
getCheckboxProps: (record: any) => ({
|
||||
name: record.name,
|
||||
|
|
|
|||
|
|
@ -4,23 +4,31 @@ import { useStore } from 'App/mstore';
|
|||
import { observer } from 'mobx-react-lite';
|
||||
import { Space } from 'antd';
|
||||
|
||||
function WidgetDateRange({ label = 'Time Range', comparison = false }: any) {
|
||||
function WidgetDateRange({ label = 'Time Range', isTimeseries = false }: any) {
|
||||
const { dashboardStore } = useStore();
|
||||
const period = comparison ? dashboardStore.comparisonPeriod : dashboardStore.drillDownPeriod;
|
||||
const period = dashboardStore.drillDownPeriod;
|
||||
const compPeriod = dashboardStore.comparisonPeriod;
|
||||
const drillDownFilter = dashboardStore.drillDownFilter;
|
||||
|
||||
const onChangePeriod = (period: any) => {
|
||||
if (comparison) dashboardStore.setComparisonPeriod(period);
|
||||
else {
|
||||
dashboardStore.setDrillDownPeriod(period);
|
||||
const periodTimestamps = period.toTimestamps();
|
||||
drillDownFilter.merge({
|
||||
startTimestamp: periodTimestamps.startTimestamp,
|
||||
endTimestamp: periodTimestamps.endTimestamp,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeComparison = (period: any) => {
|
||||
dashboardStore.setComparisonPeriod(period);
|
||||
const periodTimestamps = period.toTimestamps();
|
||||
const compFilter = dashboardStore.cloneCompFilter();
|
||||
compFilter.merge({
|
||||
startTimestamp: periodTimestamps.startTimestamp,
|
||||
endTimestamp: periodTimestamps.endTimestamp,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Space>
|
||||
{label && <span className="mr-1 color-gray-medium">{label}</span>}
|
||||
|
|
@ -30,8 +38,19 @@ function WidgetDateRange({ label = 'Time Range', comparison = false }: any) {
|
|||
right={true}
|
||||
isAnt={true}
|
||||
useButtonStyle={true}
|
||||
comparison={comparison}
|
||||
/>
|
||||
{isTimeseries ? (
|
||||
<SelectDateRange
|
||||
period={period}
|
||||
compPeriod={compPeriod}
|
||||
onChange={onChangePeriod}
|
||||
onChangeComparison={onChangeComparison}
|
||||
right={true}
|
||||
isAnt={true}
|
||||
useButtonStyle={true}
|
||||
comparison={true}
|
||||
/>
|
||||
) : null}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite';
|
|||
import React from 'react';
|
||||
import WidgetDateRange from "Components/Dashboard/components/WidgetDateRange/WidgetDateRange";
|
||||
import { useStore } from 'App/mstore';
|
||||
import { TIMESERIES } from "../../../../constants/card";
|
||||
import { TIMESERIES } from "App/constants/card";
|
||||
|
||||
import WidgetWrapper from '../WidgetWrapper';
|
||||
import WidgetOptions from 'Components/Dashboard/components/WidgetOptions';
|
||||
|
|
@ -26,8 +26,7 @@ function WidgetPreview(props: Props) {
|
|||
className={cn(className, 'bg-white rounded-xl border shadow-sm mt-0')}
|
||||
>
|
||||
<div className="flex items-center gap-2 px-4 pt-2">
|
||||
<WidgetDateRange label="" />
|
||||
{metric.metricType === TIMESERIES ? <WidgetDateRange comparison label="" /> : null}
|
||||
<WidgetDateRange label="" isTimeseries={metric.metricType === TIMESERIES} />
|
||||
<div className="flex items-center ml-auto">
|
||||
<WidgetOptions />
|
||||
{/*{metric.metricType === USER_PATH && (*/}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@ import cn from 'classnames';
|
|||
import { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { components } from 'react-select';
|
||||
import { CUSTOM_RANGE, DATE_RANGE_OPTIONS, DATE_RANGE_COMPARISON_OPTIONS } from 'App/dateRange';
|
||||
import { Calendar } from 'lucide-react'
|
||||
import {
|
||||
CUSTOM_RANGE,
|
||||
DATE_RANGE_OPTIONS,
|
||||
DATE_RANGE_COMPARISON_OPTIONS,
|
||||
} from 'App/dateRange';
|
||||
import { Calendar } from 'lucide-react';
|
||||
import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup';
|
||||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
import Select from 'Shared/Select';
|
||||
|
|
@ -19,23 +23,44 @@ interface Props {
|
|||
timezone?: string;
|
||||
isAnt?: boolean;
|
||||
small?: boolean;
|
||||
useButtonStyle?: boolean; // New prop to control button style
|
||||
useButtonStyle?: boolean; // New prop to control button style
|
||||
compPeriod: any | null;
|
||||
onChangeComparison: (data: any) => void;
|
||||
comparison?: boolean;
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
function SelectDateRange(props: Props) {
|
||||
const [isCustom, setIsCustom] = React.useState(false);
|
||||
const { right = false, period, disableCustom = false, timezone, useButtonStyle = false } = props;
|
||||
const dateRangeOptions = props.comparison ? DATE_RANGE_COMPARISON_OPTIONS : DATE_RANGE_OPTIONS;
|
||||
let selectedValue = period?.rangeName ? dateRangeOptions.find(
|
||||
(obj: any) => obj.value === period?.rangeName
|
||||
) : null;
|
||||
const {
|
||||
right = false,
|
||||
period,
|
||||
disableCustom = false,
|
||||
timezone,
|
||||
useButtonStyle = false,
|
||||
} = props;
|
||||
const usedPeriod = props.comparison ? props.compPeriod : period;
|
||||
const dateRangeOptions = props.comparison
|
||||
? DATE_RANGE_COMPARISON_OPTIONS
|
||||
: DATE_RANGE_OPTIONS;
|
||||
let selectedValue = usedPeriod?.rangeName
|
||||
? dateRangeOptions.find((obj: any) => obj.value === usedPeriod?.rangeName)
|
||||
: null;
|
||||
const options = dateRangeOptions.filter((obj: any) =>
|
||||
disableCustom ? obj.value !== CUSTOM_RANGE : true
|
||||
);
|
||||
|
||||
const onChange = (value: any) => {
|
||||
if (props.comparison) {
|
||||
const newPeriod = new Period({
|
||||
start: props.period.start,
|
||||
end: props.period.end,
|
||||
substract: value
|
||||
});
|
||||
props.onChangeComparison(newPeriod);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === CUSTOM_RANGE) {
|
||||
setTimeout(() => {
|
||||
setIsCustom(true);
|
||||
|
|
@ -56,101 +81,27 @@ function SelectDateRange(props: Props) {
|
|||
};
|
||||
|
||||
const isCustomRange = period ? period.rangeName === CUSTOM_RANGE : false;
|
||||
const isUSLocale = navigator.language === 'en-US' || navigator.language.startsWith('en-US');
|
||||
const customRange = isCustomRange ? period.rangeFormatted(isUSLocale ? "MMM dd yyyy, hh:mm a" : "MMM dd yyyy, HH:mm") : '';
|
||||
const isUSLocale =
|
||||
navigator.language === 'en-US' || navigator.language.startsWith('en-US');
|
||||
const customRange = isCustomRange
|
||||
? period.rangeFormatted(
|
||||
isUSLocale ? 'MMM dd yyyy, hh:mm a' : 'MMM dd yyyy, HH:mm'
|
||||
)
|
||||
: '';
|
||||
|
||||
if (props.isAnt) {
|
||||
const menuProps = {
|
||||
items: options.map((opt) => ({
|
||||
label: opt.label,
|
||||
key: opt.value,
|
||||
})),
|
||||
selectedKeys: selectedValue?.value ? [selectedValue.value] : undefined,
|
||||
onClick: (e: any) => {
|
||||
onChange(e.key);
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'relative'}>
|
||||
{props.comparison ? (
|
||||
<div className={'flex items-center gap-0'}>
|
||||
<Dropdown menu={menuProps} className={'px-2 py-1'}>
|
||||
<div className={"cursor-pointer flex items-center gap-2 border-l border-t border-b border-gray-light rounded-l !border-r-0"}>
|
||||
<span>
|
||||
{isCustomRange
|
||||
? customRange
|
||||
: `Compare to ${selectedValue ? selectedValue?.label : ''}`}
|
||||
</span>
|
||||
<DownOutlined />
|
||||
</div>
|
||||
</Dropdown>
|
||||
<div
|
||||
className={"flex items-center justify-center border border-gray-light p-2 hover:border-main rounded-r"}
|
||||
style={{ height: 30 }}
|
||||
onClick={() => props.onChange(null)}
|
||||
>
|
||||
<CloseOutlined />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Dropdown menu={menuProps} className={'px-2 py-1'}>
|
||||
{useButtonStyle ? (
|
||||
<div className={'flex items-center gap-2 border border-gray-light rounded cursor-pointer'}>
|
||||
<Calendar size={16} />
|
||||
<span>
|
||||
{isCustomRange ? customRange : selectedValue?.label}
|
||||
</span>
|
||||
<DownOutlined />
|
||||
</div>
|
||||
) : (
|
||||
<div className={'cursor-pointer flex items-center gap-2'}>
|
||||
<span>
|
||||
{isCustomRange ? customRange : selectedValue?.label}
|
||||
</span>
|
||||
<DownOutlined />
|
||||
</div>
|
||||
)}
|
||||
</Dropdown>
|
||||
)}
|
||||
{isCustom && (
|
||||
<OutsideClickDetectingDiv
|
||||
onClickOutside={(e: any) => {
|
||||
if (
|
||||
e.target.className.includes('react-calendar') ||
|
||||
e.target.parentElement.parentElement.classList.contains(
|
||||
'rc-time-picker-panel-select'
|
||||
) ||
|
||||
e.target.parentElement.parentElement.classList[0]?.includes(
|
||||
'-menu'
|
||||
) ||
|
||||
e.target.className.includes('ant-picker')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
setIsCustom(false);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn('absolute top-0 mt-10 z-40', { 'right-0': right })}
|
||||
style={{
|
||||
width: isUSLocale ? '542px' : '500px',
|
||||
fontSize: '14px',
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<DateRangePopup
|
||||
timezone={timezone}
|
||||
onApply={onApplyDateRange}
|
||||
onCancel={() => setIsCustom(false)}
|
||||
selectedDateRange={period.range}
|
||||
className="h-fit"
|
||||
/>
|
||||
</div>
|
||||
</OutsideClickDetectingDiv>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return <AndDateRange
|
||||
{...props}
|
||||
options={options}
|
||||
selectedValue={selectedValue}
|
||||
onChange={onChange}
|
||||
isCustomRange={isCustomRange}
|
||||
customRange={customRange}
|
||||
setIsCustom={setIsCustom}
|
||||
onApplyDateRange={onApplyDateRange}
|
||||
isUSLocale={isUSLocale}
|
||||
useButtonStyle={useButtonStyle}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -194,7 +145,7 @@ function SelectDateRange(props: Props) {
|
|||
style={{
|
||||
width: isUSLocale ? '542px' : '520px',
|
||||
fontSize: '14px',
|
||||
textAlign: 'left'
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<DateRangePopup
|
||||
|
|
@ -208,6 +159,121 @@ function SelectDateRange(props: Props) {
|
|||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function AndDateRange({
|
||||
options,
|
||||
selectedValue,
|
||||
onChange,
|
||||
period,
|
||||
timezone,
|
||||
right = false,
|
||||
useButtonStyle = false,
|
||||
comparison = false,
|
||||
isCustomRange,
|
||||
customRange,
|
||||
setIsCustom,
|
||||
isCustom,
|
||||
isUSLocale,
|
||||
onApplyDateRange,
|
||||
}: Props) {
|
||||
const menuProps = {
|
||||
items: options.map((opt) => ({
|
||||
label: opt.label,
|
||||
key: opt.value,
|
||||
})),
|
||||
selectedKeys: selectedValue?.value ? [selectedValue.value] : undefined,
|
||||
onClick: (e: any) => {
|
||||
onChange(e.key);
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'relative'}>
|
||||
{comparison ? (
|
||||
<div className={'flex items-center gap-0'}>
|
||||
<Dropdown menu={menuProps} className={'px-2 py-1'}>
|
||||
<div
|
||||
className={
|
||||
'cursor-pointer flex items-center gap-2 border-l border-t border-b border-gray-light rounded-l !border-r-0'
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{isCustomRange
|
||||
? customRange
|
||||
: `Compare to ${selectedValue ? selectedValue?.label : ''}`}
|
||||
</span>
|
||||
<DownOutlined />
|
||||
</div>
|
||||
</Dropdown>
|
||||
<div
|
||||
className={
|
||||
'flex items-center justify-center border border-gray-light p-2 hover:border-main rounded-r'
|
||||
}
|
||||
style={{ height: 30 }}
|
||||
onClick={() => onChange(null)}
|
||||
>
|
||||
<CloseOutlined />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Dropdown menu={menuProps} className={'px-2 py-1'}>
|
||||
{useButtonStyle ? (
|
||||
<div
|
||||
className={
|
||||
'flex items-center gap-2 border border-gray-light rounded cursor-pointer'
|
||||
}
|
||||
>
|
||||
<Calendar size={16} />
|
||||
<span>{isCustomRange ? customRange : selectedValue?.label}</span>
|
||||
<DownOutlined />
|
||||
</div>
|
||||
) : (
|
||||
<div className={'cursor-pointer flex items-center gap-2'}>
|
||||
<span>{isCustomRange ? customRange : selectedValue?.label}</span>
|
||||
<DownOutlined />
|
||||
</div>
|
||||
)}
|
||||
</Dropdown>
|
||||
)}
|
||||
{isCustom && (
|
||||
<OutsideClickDetectingDiv
|
||||
onClickOutside={(e: any) => {
|
||||
if (
|
||||
e.target.className.includes('react-calendar') ||
|
||||
e.target.parentElement.parentElement.classList.contains(
|
||||
'rc-time-picker-panel-select'
|
||||
) ||
|
||||
e.target.parentElement.parentElement.classList[0]?.includes(
|
||||
'-menu'
|
||||
) ||
|
||||
e.target.className.includes('ant-picker')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
setIsCustom(false);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn('absolute top-0 mt-10 z-40', { 'right-0': right })}
|
||||
style={{
|
||||
width: isUSLocale ? '542px' : '500px',
|
||||
fontSize: '14px',
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<DateRangePopup
|
||||
timezone={timezone}
|
||||
onApply={onApplyDateRange}
|
||||
onCancel={() => setIsCustom(false)}
|
||||
selectedDateRange={period.range}
|
||||
className="h-fit"
|
||||
/>
|
||||
</div>
|
||||
</OutsideClickDetectingDiv>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(SelectDateRange);
|
||||
|
|
|
|||
|
|
@ -18,16 +18,11 @@ const DATE_RANGE_LABELS = {
|
|||
};
|
||||
|
||||
const COMPARISON_DATE_RANGE_LABELS = {
|
||||
// LAST_30_MINUTES: '30 Minutes',
|
||||
// TODAY: 'Today',
|
||||
LAST_24_HOURS: "Previous Day",
|
||||
// YESTERDAY: 'Yesterday',
|
||||
LAST_7_DAYS: "Previous Week",
|
||||
LAST_30_DAYS: "Previous Month",
|
||||
//THIS_MONTH: 'This Month',
|
||||
//LAST_MONTH: 'Previous Month',
|
||||
//THIS_YEAR: 'This Year',
|
||||
[CUSTOM_RANGE]: "Custom Range",
|
||||
PREV_24_HOURS: "Previous Day",
|
||||
PREV_7_DAYS: "Previous Week",
|
||||
PREV_30_DAYS: "Previous Month",
|
||||
PREV_QUARTER: "Previous Quarter",
|
||||
CUSTOM_RANGE: "Custom",
|
||||
}
|
||||
|
||||
const DATE_RANGE_VALUES = {};
|
||||
|
|
@ -86,6 +81,18 @@ export function getDateRangeFromValue(value) {
|
|||
now.minus({ days: 30 }).startOf('day'),
|
||||
now.endOf('day')
|
||||
);
|
||||
case COMPARISON_DATE_RANGE_LABELS.PREV_24_HOURS:
|
||||
return Interval.fromDateTimes(now.minus({ hours: 48 }), now.minus({ hours: 24 }));
|
||||
case COMPARISON_DATE_RANGE_LABELS.PREV_7_DAYS:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ days: 14 }).startOf('day'),
|
||||
now.minus({ days: 7 }).endOf('day')
|
||||
);
|
||||
case COMPARISON_DATE_RANGE_LABELS.PREV_30_DAYS:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ days: 60 }).startOf('day'),
|
||||
now.minus({ days: 30 }).endOf('day')
|
||||
);
|
||||
// case DATE_RANGE_VALUES.THIS_MONTH:
|
||||
// return Interval.fromDateTimes(now.startOf('month'), now.endOf('month'));
|
||||
// case DATE_RANGE_VALUES.LAST_MONTH:
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default class DashboardStore {
|
|||
widgets: Widget[] = [];
|
||||
period: Record<string, any> = Period({ rangeName: LAST_24_HOURS });
|
||||
drillDownFilter: Filter = new Filter();
|
||||
comparisonFilter: Filter = new Filter();
|
||||
drillDownPeriod: Record<string, any> = Period({ rangeName: LAST_7_DAYS });
|
||||
comparisonPeriod: Record<string, any> | null = null
|
||||
startTimestamp: number = 0;
|
||||
|
|
@ -420,6 +421,13 @@ export default class DashboardStore {
|
|||
});
|
||||
}
|
||||
|
||||
cloneCompFilter() {
|
||||
const filterData = this.drillDownFilter.toData()
|
||||
this.comparisonFilter = new Filter().fromData(filterData);
|
||||
|
||||
return this.comparisonFilter;
|
||||
}
|
||||
|
||||
toggleAlertModal(val: boolean) {
|
||||
this.showAlertModal = val;
|
||||
}
|
||||
|
|
@ -436,12 +444,13 @@ export default class DashboardStore {
|
|||
metric: Widget,
|
||||
data: any,
|
||||
isSaved: boolean = false,
|
||||
period: Record<string, any>
|
||||
period: Record<string, any>,
|
||||
isComparison?: boolean
|
||||
): Promise<any> {
|
||||
period = period.toTimestamps();
|
||||
const params = { ...period, ...data, key: metric.predefinedKey };
|
||||
|
||||
if (metric.page && metric.limit) {
|
||||
if (!isComparison && metric.page && metric.limit) {
|
||||
params['page'] = metric.page;
|
||||
params['limit'] = metric.limit;
|
||||
}
|
||||
|
|
@ -449,13 +458,13 @@ export default class DashboardStore {
|
|||
return new Promise(async (resolve, reject) => {
|
||||
this.upPendingRequests()
|
||||
|
||||
if (metric.metricType === 'table' && metric.metricOf === 'jsException') {
|
||||
if (!isComparison && metric.metricType === 'table' && metric.metricOf === 'jsException') {
|
||||
params.limit = 5;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await metricService.getMetricChartData(metric, params, isSaved);
|
||||
resolve(metric.setData(data, period));
|
||||
resolve(metric.setData(data, period, isComparison));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -293,8 +293,18 @@ export default class Widget {
|
|||
this.page = page;
|
||||
}
|
||||
|
||||
setData(data: any, period: any) {
|
||||
const _data: any = {...data};
|
||||
setData(data: { timestamp: number, [seriesName: string]: number}[], period: any, isComparison?: boolean) {
|
||||
const _data: any = {};
|
||||
if (isComparison) {
|
||||
console.log(data)
|
||||
data.forEach((point, i) => {
|
||||
Object.keys(point).forEach((key) => {
|
||||
if (key === 'timestamp') return;
|
||||
point[`Previous ${key}`] = point[key];
|
||||
delete point[key];
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (this.metricType === USER_PATH) {
|
||||
const _data = processData(data);
|
||||
|
|
@ -350,7 +360,9 @@ export default class Widget {
|
|||
}
|
||||
}
|
||||
|
||||
Object.assign(this.data, _data);
|
||||
if (!isComparison) {
|
||||
Object.assign(this.data, _data);
|
||||
}
|
||||
return _data;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ export const THIS_MONTH = "THIS_MONTH";
|
|||
export const LAST_MONTH = "LAST_MONTH";
|
||||
export const THIS_YEAR = "THIS_YEAR";
|
||||
export const CUSTOM_RANGE = "CUSTOM_RANGE";
|
||||
export const PREV_24_HOURS = "PREV_24_HOURS";
|
||||
export const PREV_7_DAYS = "PREV_7_DAYS";
|
||||
export const PREV_30_DAYS = "PREV_30_DAYS";
|
||||
export const PREV_QUARTER = "PREV_QUARTER";
|
||||
|
||||
function getRange(rangeName, offset) {
|
||||
const now = DateTime.now().setZone(offset);
|
||||
|
|
@ -47,33 +51,60 @@ function getRange(rangeName, offset) {
|
|||
return Interval.fromDateTimes(lastMonth.startOf("month"), lastMonth.endOf("month"));
|
||||
case THIS_YEAR:
|
||||
return Interval.fromDateTimes(now.startOf("year"), now.endOf("year"));
|
||||
case PREV_24_HOURS:
|
||||
return Interval.fromDateTimes(now.minus({ hours: 48 }), now.minus({ hours: 24 }));
|
||||
case PREV_7_DAYS:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ days: 14 }).startOf("day"),
|
||||
now.minus({ days: 7 }).endOf("day")
|
||||
);
|
||||
case PREV_30_DAYS:
|
||||
return Interval.fromDateTimes(
|
||||
now.minus({ days: 60 }).startOf("day"),
|
||||
now.minus({ days: 30 }).endOf("day")
|
||||
);
|
||||
default:
|
||||
return Interval.fromDateTimes(now, now);
|
||||
}
|
||||
}
|
||||
|
||||
const substractValues = {
|
||||
[PREV_24_HOURS]: { hours: 24 },
|
||||
[PREV_7_DAYS]: { days: 7 },
|
||||
[PREV_30_DAYS]: { days: 30 },
|
||||
[PREV_QUARTER]: { months: 3 },
|
||||
}
|
||||
|
||||
export default Record(
|
||||
{
|
||||
start: 0,
|
||||
end: 0,
|
||||
rangeName: CUSTOM_RANGE,
|
||||
range: Interval.fromDateTimes(DateTime.now(), DateTime.now()),
|
||||
substract: null,
|
||||
},
|
||||
{
|
||||
// type substractors = 'PREV_24_HOURS' | 'PREV_7_DAYS' | 'PREV_30_DAYS' | 'PREV_QUARTER';
|
||||
fromJS: (period) => {
|
||||
const offset = period.timezoneOffset || DateTime.now().offset;
|
||||
if (!period.rangeName || period.rangeName === CUSTOM_RANGE) {
|
||||
const isLuxon = DateTime.isDateTime(period.start);
|
||||
const start = isLuxon
|
||||
let start = isLuxon
|
||||
? period.start : DateTime.fromMillis(period.start || 0, { zone: Settings.defaultZone });
|
||||
const end = isLuxon
|
||||
let end = isLuxon
|
||||
? period.end : DateTime.fromMillis(period.end || 0, { zone: Settings.defaultZone });
|
||||
if (period.substract) {
|
||||
const delta = substractValues[period.substract]
|
||||
start = start.minus(delta);
|
||||
end = end.minus(delta);
|
||||
}
|
||||
const range = Interval.fromDateTimes(start, end);
|
||||
return {
|
||||
...period,
|
||||
range,
|
||||
start: range.start.toMillis(),
|
||||
end: range.end.toMillis(),
|
||||
rangeName: period.substract ? period.substract : undefined
|
||||
};
|
||||
}
|
||||
const range = getRange(period.rangeName, offset);
|
||||
|
|
|
|||
|
|
@ -148,5 +148,5 @@
|
|||
"engines": {
|
||||
"node": ">=20.18.0"
|
||||
},
|
||||
"packageManager": "yarn@4.5.1"
|
||||
"packageManager": "yarn@4.5.3"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue