Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ public/bound
.worktrees/
coverage/
.playwright-mcp/
docs/
midscene_run/
62 changes: 55 additions & 7 deletions src/components/PromGraphCpt/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*
*/
import React, { useState, useEffect, useContext } from 'react';
import React, { useState, useEffect, useContext, useRef, useCallback } from 'react';
import moment from 'moment';
import _ from 'lodash';
import { Space, InputNumber, Radio, Button, Popover, Tooltip } from 'antd';
Expand Down Expand Up @@ -57,6 +57,7 @@ interface IProps {
graphStandardOptionsPlacement?: TooltipPlacement;
defaultUnit?: string;
panelWidth?: number; // 用于 Graph 组件的宽度计算
refetchOnZoom?: boolean;
}

enum ChartType {
Expand Down Expand Up @@ -100,8 +101,17 @@ export default function Graph(props: IProps) {
graphStandardOptionsPlacement = 'left',
defaultUnit,
panelWidth,
refetchOnZoom = false,
} = props;
const [data, setData] = useState<any[]>([]);
const [zoomRangeState, setZoomRangeState] = useState<{ range: IRawTimeRange; sourceKey: string }>();
const [resetZoomVersion, setResetZoomVersion] = useState(0);
const querySeqRef = useRef(0);
const rangeKey = JSON.stringify(range);
const zoomSourceKey = JSON.stringify({ datasourceValue, promql, range });
const activeZoomRange = refetchOnZoom && zoomRangeState?.sourceKey === zoomSourceKey ? zoomRangeState.range : undefined;
const effectiveRange = activeZoomRange || range;
const effectiveRangeKey = JSON.stringify(effectiveRange);
const [highLevelConfig, setHighLevelConfig] = useState({
shared: false,
sharedSortDirection: 'desc',
Expand Down Expand Up @@ -144,9 +154,35 @@ export default function Graph(props: IProps) {
}
}, [defaultUnit]);

useEffect(() => {
if (refetchOnZoom) {
setZoomRangeState(undefined);
setResetZoomVersion((v) => v + 1);
}
}, [refetchOnZoom, zoomSourceKey]);

const handleZoomWithoutDefault = useCallback(
(times?: Date[]) => {
if (!refetchOnZoom) return;
if (times?.length === 2) {
setZoomRangeState({
range: {
start: moment(times[0]),
end: moment(times[1]),
},
sourceKey: zoomSourceKey,
});
} else {
setZoomRangeState(undefined);
setResetZoomVersion((v) => v + 1);
}
},
[refetchOnZoom, zoomSourceKey],
);

useEffect(() => {
if (datasourceValue && promql) {
const parsedRange = parseRange(range);
const parsedRange = parseRange(effectiveRange);
const start = moment(parsedRange.start).unix();
const end = moment(parsedRange.end).unix();
const realStep = getRealStep({
Expand All @@ -156,11 +192,12 @@ export default function Graph(props: IProps) {
toUnix: end,
});
const queryStart = Date.now();
const querySeq = ++querySeqRef.current;
setLoading(true);
getPromData(`${url}/${datasourceValue}/api/v1/query_range`, {
query: interpolateString({
query: promql,
range,
range: effectiveRange,
minStep,
maxDataPoints: maxDataPoints || panelWidth,
}),
Expand All @@ -169,6 +206,7 @@ export default function Graph(props: IProps) {
step: realStep,
})
.then((res) => {
if (querySeq !== querySeqRef.current) return;
const series = _.map(res?.result, (item) => {
return {
id: _.uniqueId('series_'),
Expand All @@ -187,17 +225,19 @@ export default function Graph(props: IProps) {
setErrorContent('');
})
.catch((err) => {
if (querySeq !== querySeqRef.current) return;
const msg = _.get(err, 'message');
setErrorContent(`Error executing query: ${msg}`);
})
.finally(() => {
if (querySeq !== querySeqRef.current) return;
setLoading(false);
});
}
}, [JSON.stringify(range), minStep, maxDataPoints, datasourceValue, promql, refreshFlag]);
}, [effectiveRangeKey, minStep, maxDataPoints, datasourceValue, promql, refreshFlag]);

return (
<div className='prom-graph-graph-container'>
<div className={activeZoomRange ? 'prom-graph-graph-container prom-graph-graph-zoom-owner' : 'prom-graph-graph-container'}>
<div className='prom-graph-graph-controls'>
<Space wrap>
<TimeRangePicker value={range} onChange={setRange} dateFormat='YYYY-MM-DD HH:mm:ss' />
Expand Down Expand Up @@ -294,7 +334,7 @@ export default function Graph(props: IProps) {
version: DASHBOARD_VERSION,
name: promql,
step: minStep,
range,
range: effectiveRange,
...lineGraphProps,
targets: [
{
Expand Down Expand Up @@ -336,7 +376,15 @@ export default function Graph(props: IProps) {
)}
</Space>
</div>
<Timeseries inDashboard={false} values={lineGraphProps as any} series={data} time={range} themeMode={darkMode ? 'dark' : undefined} />
<Timeseries
key={refetchOnZoom ? `${resetZoomVersion}-${zoomSourceKey}` : undefined}
inDashboard={false}
values={lineGraphProps as any}
series={data}
time={effectiveRange}
themeMode={darkMode ? 'dark' : undefined}
onZoomWithoutDefult={refetchOnZoom ? (handleZoomWithoutDefault as any) : undefined}
/>
</div>
);
}
3 changes: 3 additions & 0 deletions src/components/PromGraphCpt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ interface IProps {
promQLInputTooltip?: string;
extra?: React.ReactElement;
showExportButton?: boolean; // 是否显示导出按钮
refetchOnZoom?: boolean;
}

const TabPane = Tabs.TabPane;
Expand Down Expand Up @@ -98,6 +99,7 @@ export default function index(props: IProps) {
extra,
defaultRange,
showExportButton,
refetchOnZoom = false,
} = props;
const [value, setValue] = useState<string | undefined>(promQL); // for promQLInput
const [queryStats, setQueryStats] = useState<QueryStats | null>(null);
Expand Down Expand Up @@ -292,6 +294,7 @@ export default function index(props: IProps) {
graphStandardOptionsType={graphStandardOptionsType}
graphStandardOptionsPlacement={graphStandardOptionsPlacement}
defaultUnit={defaultUnit}
refetchOnZoom={refetchOnZoom}
/>
</Panel>
</TabPane>
Expand Down
8 changes: 7 additions & 1 deletion src/components/PromGraphCpt/style.less
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@
display: flex;
flex-direction: column;

&.prom-graph-graph-zoom-owner {
.ts-graph-zoom-resetBtn {
display: block !important;
}
}

.renderer-timeseries-container {
display: flex !important;
flex-direction: column;
Expand Down Expand Up @@ -148,4 +154,4 @@
&:hover {
background-color: var(--fc-fill-3);
}
}
}
2 changes: 1 addition & 1 deletion src/locales/common/locale/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const zh_CN = {
'403_admin': '管理员:',
'403_back': '返回上一页',
404: '你访问的页面不存在!',
'404_btn': '回到首页',
'404_btn': '返回首页',
},
business_group: '业务组',
business_groups: '业务组',
Expand Down
2 changes: 1 addition & 1 deletion src/locales/common/locale/zh_HK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const zh_HK = {
'403_admin': '管理員:',
'403_back': '返回上一頁',
404: '您訪問的頁面不存在!',
'404_btn': '回到首頁',
'404_btn': '返回首頁',
},
business_group: '業務組',
business_groups: '業務組',
Expand Down
4 changes: 4 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ import { i18nInit } from './i18n'; // loaded and initialized first
import App from './App';
import { I18nextProvider } from 'react-i18next';
import { initTheme } from './utils/darkMode';
import { initFlashcatFrom } from './utils/flashcatFrom';

// 在页面渲染前初始化主题,避免样式闪烁
initTheme();

// 指向官网/Flashduty 的链接统一携带 from=n9e-user 渠道参数
initFlashcatFrom();

ReactDOM.render(
<I18nextProvider i18n={i18nInit}>
<App />
Expand Down
29 changes: 29 additions & 0 deletions src/pages/dashboard/Editor/Fields/Legend/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,35 @@ export default function index() {
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label={t('panel.options.legend.sortBy')} name={[...namePrefix, 'sortBy']} tooltip={t('panel.options.legend.sortBy_tip')} hidden={displayMode === 'hidden'}>
<Select allowClear placeholder={t('panel.options.legend.sortBy_tip')}>
{_.map(tableColumn, (item) => {
return (
<Select.Option key={item} value={item}>
{t(`panel.options.legend.${item}`)}
</Select.Option>
);
})}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label={t('panel.options.legend.sortDir')} name={[...namePrefix, 'sortDir']} initialValue='asc' hidden={displayMode === 'hidden'}>
<Select
options={[
{
label: t('panel.options.legend.sortDirAsc'),
value: 'asc',
},
{
label: t('panel.options.legend.sortDirDesc'),
value: 'desc',
},
]}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label={t('panel.options.legend.behaviour.label')} name={[...namePrefix, 'behaviour']} initialValue='showItem' hidden={displayMode === 'hidden'}>
<Select
Expand Down
2 changes: 2 additions & 0 deletions src/pages/dashboard/Editor/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export const defaultOptionsValues = {
},
legend: {
displayMode: 'hidden',
sortBy: '',
sortDir: 'asc',
},
thresholds: {
steps: [defaultThreshold],
Expand Down
Loading