* -------------------------------------------------------------------------
* This file is part of the MindStudio project.
* Copyright (c) 2026 Huawei Technologies Co.,Ltd.
*
* MindStudio is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*/
import { runInAction } from 'mobx';
import { message } from 'antd';
import i18n from '@insight/lib/i18n';
import { register } from './register';
import type { Session } from '../entity/session';
import type { ThreadMetaData } from '../entity/data';
import type { InsightUnit } from '../entity/insight';
import { getTimeOffsetKey } from '../insight/units/utils';
import { queryTimelineOffset } from '../api/request';
import type {
QueryTimelineOffsetParams,
TimelineAlignmentType,
TimelineOffsetItem,
} from '../api/interface';
const ALIGN_TYPE = {
LEFT: 'LEFT',
RIGHT: 'RIGHT',
} as const;
function getSelectedUnit(session: Session): InsightUnit | undefined {
if (session.selectedUnits.length === 1) {
return session.selectedUnits[0];
}
const selectedData = session.selectedData;
if (!selectedData?.cardId) {
return undefined;
}
if (!selectedData.processId) {
return undefined;
}
return session.units.find((unit) => {
const metadata = unit.metadata as Partial<ThreadMetaData>;
return metadata.cardId === selectedData.cardId && metadata.processId === selectedData.processId;
});
}
function applyOffsetItems(session: Session, items: TimelineOffsetItem[], offsetDelta: number): void {
const nextOffset = { ...session.unitsConfig.offsetConfig.timestampOffset };
items.forEach(({ rankId, offset, processId }) => {
if (!rankId) {
return;
}
if (!Number.isFinite(offset)) {
return;
}
if (!Array.isArray(processId)) {
return;
}
if (processId.length === 0) {
return;
}
processId.forEach((pid) => {
const key = getTimeOffsetKey(session, { cardId: rankId, processId: pid });
nextOffset[key] = offset + offsetDelta;
});
});
session.setTimestampOffsetAll(nextOffset);
}
function getSelectedOffsetDelta(session: Session, params: QueryTimelineOffsetParams, baseOffset: number): number {
if (!Number.isFinite(baseOffset)) {
return 0;
}
const offsetKey = getTimeOffsetKey(session, { cardId: params.rankId, processId: params.pid });
const currentOffset = session.unitsConfig.offsetConfig.timestampOffset[offsetKey] ?? 0;
return currentOffset - baseOffset;
}
function buildRequestParams(session: Session, alignType: TimelineAlignmentType): QueryTimelineOffsetParams | undefined {
const selectedData = session.selectedData;
const selectedUnit = getSelectedUnit(session);
const metadata = selectedUnit?.metadata as Partial<ThreadMetaData> | undefined;
const sliceName = selectedData?.name;
const rankId = metadata?.cardId;
const fileId = metadata?.dbPath;
const pid = selectedData?.processId ?? metadata?.processId;
const metaType = selectedData?.metaType ?? metadata?.metaType;
const startTime = selectedData?.rawStartTime;
const duration = selectedData?.duration;
if (!sliceName) {
return undefined;
}
if (!rankId) {
return undefined;
}
if (!fileId) {
return undefined;
}
if (!pid) {
return undefined;
}
if (!metaType) {
return undefined;
}
if (!startTime) {
return undefined;
}
if (duration === undefined || !Number.isFinite(duration)) {
return undefined;
}
return { sliceName, rankId, fileId, pid, metaType, startTime, duration, alignType };
}
async function alignByOperator(session: Session, alignType: TimelineAlignmentType): Promise<void> {
const params = buildRequestParams(session, alignType);
if (!params) {
message.warning(i18n.t('timeline:contextMenu.AlignOperatorRawStartNotReady'));
return;
}
const hide = message.loading(i18n.t('timeline:contextMenu.Calculating Offset'), 0);
try {
const res = await queryTimelineOffset(params);
runInAction(() => {
const offsetDelta = getSelectedOffsetDelta(session, params, res.baseOffset);
applyOffsetItems(session, res.result, offsetDelta);
});
} catch {
} finally {
hide();
}
}
const isOperatorAlignVisible = (session: Session): boolean => {
if (session.isSimulation || session.isIE) {
return false;
}
return Boolean(session.selectedData?.name) && getSelectedUnit(session) !== undefined;
};
export const actionAlignByOperatorLeft = register({
name: 'alignByOperatorLeft',
label: 'timeline:contextMenu.Align Left',
parentMenuKey: 'alignByOperator',
visible: isOperatorAlignVisible,
perform: (session): void => {
void alignByOperator(session, ALIGN_TYPE.LEFT);
},
});
export const actionAlignByOperatorRight = register({
name: 'alignByOperatorRight',
label: 'timeline:contextMenu.Align Right',
parentMenuKey: 'alignByOperator',
visible: isOperatorAlignVisible,
perform: (session): void => {
void alignByOperator(session, ALIGN_TYPE.RIGHT);
},
});
export const actionAlignByOperator = register({
name: 'alignByOperator',
label: 'timeline:contextMenu.Time Alignment',
visible: isOperatorAlignVisible,
perform: (): void => {},
subMode: true,
subMenus: () => [actionAlignByOperatorLeft, actionAlignByOperatorRight],
});