* -------------------------------------------------------------------------
* This file is part of the MindStudio project.
* Copyright (c) 2025 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 FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*/
import { makeAutoObservable, observable, runInAction } from 'mobx';
import type React from 'react';
import type { CommonStateProto } from '../components/details/base/Tabs';
import type { ChartConfig, ChartDecorator, ChartReaction, ChartType, GetChartConfig, MapFunc, RenderTooltip } from './chart';
import type { ElementType, TreeNode } from './common';
import type { Session } from './session';
import type { TabState } from './tabDependency';
import { MetaDataBase } from './data';
* chart description
*
* @template T: chart type
* @member dataFields fields you want in this unit, @see {@link DataFields}
* @member mapFunc a mapping from Data Insight Core type to chart data type
*/
export interface ChartDesc<T extends ChartType> {
type: T;
offline?: boolean;
mapFunc: MapFunc<T>;
config: ChartConfig<T> | GetChartConfig<T>;
height: UnitHeight;
renderTooltip?: RenderTooltip<T>;
onHover?: ChartReaction<T>;
onClick?: ChartReaction<T>;
decorator?: ChartDecorator<T>;
error?: boolean;
}
export enum ProjectType {
DB = 0,
BIN = 1,
IPYNB = 2,
TEXT_CLUSTER = 3,
SIMULATION = 4,
TRACE = 5,
DB_CLUSTER = 6,
IE = 7,
DB_NPUMONITOR = 8,
OTHER = 9,
}
export const isGetChartConfig = <T extends ChartType>(config: ChartDesc<T>['config']): config is GetChartConfig<T> => {
return typeof config === 'function';
};
type FixedType = 'scroll' | 'right' | 'left';
type ColumnWidth = number | 'max-content' | 'auto';
export const chart = <T extends ChartType>(param: ChartDesc<T>): ChartDesc<ChartType> => ({ offline: false, ...param }) as unknown as ChartDesc<ChartType>;
export type MoreDescriptor = MoreDesc<Record<string, any[]>, string>;
interface MoreDesc<DataType extends object, Field extends keyof DataType> {
field: Field;
columns: Array<ColumnDef<ElementType<DataType[Field]>>>;
childrenColumnName?: string;
rowKey?: (data: ElementType<DataType[Field]>) => string;
onExpand?: (session: Session, data: TreeNode<DataType>) => Promise<TreeNode<DataType> | undefined>;
};
type CellRenderer<DataType> = (d: DataType, session: Session, tabState: TabState | undefined) => (string | JSX.Element);
export type ColumnDef<DataType> =
[string, CellRenderer<DataType>] |
[string, CellRenderer<DataType>, ColumnWidth] |
[string, CellRenderer<DataType>, ColumnWidth, FixedType] |
[string, CellRenderer<DataType>, ColumnWidth, FixedType | undefined, (p: any) => boolean];
export type SummaryFunction<DataType> = (dataSource: DataType[]) => (string | JSX.Element);
export interface TableDataAdapter<DataType extends Record<string, unknown>> {
columns: Array<ColumnDef<DataType>>;
actions?: Array<FilterDef<DataType> & SorterDef<DataType>>;
summaries?: Map<string, SummaryFunction<DataType>>;
};
interface FilterDef<DataType extends Record<string, unknown>> {
filterKey?: keyof DataType | Array<keyof DataType>;
};
export interface SorterDef<DataType extends Record<string, unknown>> {
sorter?: (a: DataType, b: DataType) => number | boolean;
};
export { type MetaDataBase as MetaData };
interface TabularClickCacallbackArgs<CommonState extends CommonStateProto, DataType> {
row: DataType;
session: Session;
detail: DetailDescriptor<MetaDataBase>;
commonState?: CommonState;
unit?: InsightUnit;
};
interface TabularEnterCacallbackArgs<DataType> {
row: DataType;
session: Session;
};
type DetailDesc<
DataType extends Record<string, unknown>,
ExtraDataType extends Record<string, unknown>,
MoreDataType extends Record<string, unknown>,
MetaData,
Field extends keyof DataType,
> = {
childrenColumnName?: string;
name?: string;
fetchData: (session: Session, metadata: MetaData) => Promise<DataType[]>;
fetchExtraData?: (session: Session, metadata: MetaData) => Promise<ExtraDataType[]>;
fetchMoreData?: (session: Session, metadata: MetaData) => Promise<MoreDataType[]>;
onExpand?: (session: Session, data: TreeNode<DataType>) => Promise<TreeNode<DataType> | undefined>;
rowKey?: (data: DataType) => string;
more?: MoreDesc<DataType, Field>;
clickCallback?: <CommonState extends CommonStateProto>(args: TabularClickCacallbackArgs<CommonState, DataType>) =>
void;
doubleClickCallback?: <CommonState extends CommonStateProto>(args: TabularClickCacallbackArgs<CommonState, DataType>) => void;
mouseEnterCallback?: (args: TabularEnterCacallbackArgs<DataType>) => void;
mouseLeaveCallback?: (args: TabularEnterCacallbackArgs<DataType>) => void;
} & TableDataAdapter<DataType>;
export type renderFieldsType<DataType> =
[string, (data: DataType, session: Session, metadata?: unknown) => (string | JSX.Element), (data: DataType, session?: Session,) => boolean ] |
[string, (data: DataType, session: Session, metadata?: unknown) => (string | JSX.Element) ];
export interface SingleDataDesc<DataType extends Record<string, unknown>, MetaData> {
name?: string;
fetchData: (session: Session, metadata: MetaData) => Promise<DataType>;
renderFields: Array<renderFieldsType<DataType>>;
clickCallback?: (args: SingleDataDesc<Record<string, unknown>, unknown>) => void;
};
export interface LinkDataDesc<DataType extends Record<string, unknown>> {
fetchData: (session: Session, metadata: MetaDataBase) => Promise<DataType[] | DataType>;
templateField?: renderFieldsType<DataType>;
renderFields: Array<renderFieldsType<DataType>>;
onDestroy?: (session: Session) => void;
};
export const linkData = <T extends Record<string, unknown>>(desc: LinkDataDesc<T>): LinkDataDesc<Record<string, unknown>> =>
desc as unknown as LinkDataDesc<Record<string, unknown>>;
export const singleData = <T extends Record<string, unknown>, MetaData>(desc: SingleDataDesc<T, MetaData>): SingleDataDesc<Record<string, unknown>, unknown> =>
desc as unknown as SingleDataDesc<Record<string, unknown>, unknown>;
export type DetailDescriptor<UnitInfo> = DetailDesc<Record<string, unknown>, Record<string, unknown>, Record<string, unknown>, UnitInfo, string>;
export function detail<
T extends Record<string, unknown>,
K extends Record<string, unknown>,
V extends Record<string, unknown>,
MetaData,
>(desc: DetailDesc<T, K, V, MetaData, keyof T>): DetailDescriptor<MetaData> {
return {
...desc,
childrenColumnName: desc.childrenColumnName ?? 'children',
} as unknown as DetailDescriptor<MetaData>;
}
export type BottomPanelSingleRender = <Metadata>(session: Session, metadata: Metadata) => {
DetailTitle?: React.FC<{ session: Session }> | string;
Detail?: React.FC<{ session: Session; height: number }>;
More?: React.FC<{ session: Session; height: number }>;
Toolbar?: React.FC<{ session: Session }>;
MoreTitle?: React.FC<{ session: Session }> | string;
moreWh?: number;
open?: boolean;
};
export type BottomPanelRender = <Metadata>(session: Session, metadata: Metadata) => Array<{
DetailTitle?: React.FC<{ session: Session }> | string;
Detail?: React.FC<{ session: Session; height: number }>;
More?: React.FC<{ session: Session; height: number }>;
Toolbar?: React.FC<{ session: Session }>;
MoreTitle?: React.FC<{ session: Session }> | string;
moreWh?: number;
open?: boolean;
}>;
export interface MenuType {
value?: string;
showValue?: string;
key?: string;
showKey?: string;
children?: MenuType[];
isTrigger?: boolean;
type?: 'number' | 'string';
mode?: 'key' | 'value' | 'input' | 'keyValue';
};
export interface InsightUnitParams<
MetaData extends MetaDataBase,
DetailType extends Record<string, unknown>,
ExtraDataType extends Record<string, unknown>,
MoreDataType extends Record<string, unknown>,
> {
name: string;
pinType?: 'move' | 'copied';
configBar?: (session: Session, metadata: MetaData, onClick?: () => void, isHovered?: boolean, isSelected?: boolean) => JSX.Element | null;
tag?: string | ((session: Session, metadata: MetaData) => string | null);
description?: string;
chart?: ChartDesc<ChartType> | Array<ChartDesc<ChartType>>;
renderInfo?: (session: Session, metadata: MetaData, thisUnit: InsightUnit) => JSX.Element | string | null;
detail?: DetailDesc<DetailType, ExtraDataType, MoreDataType, MetaData, keyof DetailType>;
bottomPanelRender?: BottomPanelRender;
metadata: MetaData;
tabState?: TabState;
spreadUnits?: SpreadDesc;
notifications?: Array<(metaData: MetaData) => (false | string)>;
searchConfig?: MenuType;
buttons?: Array<React.FC<{ session: Session }>>;
collapseAction?: (unit: InsightUnit) => void;
collapsible?: boolean;
alignStartTimestamp?: number;
}
export interface InsightUnitClass { new(metadata: never): InsightUnit };
export enum UnitHeight {
SUPER_UPPER = 120,
HIGHTER_UPPER = 105,
UPPER = 40,
COLL = 25,
STANDARD = 20,
LOWER = 10,
SUPER_LOWER = 6,
}
* Defines some runtime states that are kept in insight unit instances.
*/
export interface InsightUnit extends InsightUnitParams<MetaDataBase, Record<string, unknown>, Record<string, unknown>, Record<string, unknown>> {
expandable: boolean;
isExpanded: boolean;
hasExpanded: boolean;
height: () => number;
isMultiDeviceHidden: boolean;
isDisplay: boolean;
isOverlapAnalysisLoading: boolean;
children?: InsightUnit[];
type: 'basic' | 'transparent';
phase: string;
isUnitVisible: boolean;
isMerged: boolean;
isSummaryLoading: boolean;
isTraceLoading: boolean;
parent?: InsightUnit;
isParseLoading: boolean;
shouldParse: boolean;
progress: number;
showProgress: boolean;
havePythonFunction?: boolean;
onceExpand?: boolean;
projectType?: ProjectType;
}
export type UnitPhase = 'configuring' | 'initializing' | 'recording' | 'analyzing' | 'rendering' | 'download' | 'error' | 'loading';
const heightOf = (chartDesc: InsightUnit['chart'], name: string): number => {
if (chartDesc === undefined) {
if (name === 'Root') {
return UnitHeight.UPPER;
}
return UnitHeight.STANDARD;
}
return Array.isArray(chartDesc) ? chartDesc.reduce((prev, cur) => prev + cur.height + 1, 0) - 1 : chartDesc.height;
};
export type SpreadPhase = 'create' | 'analyze' | 'expand';
interface SpreadDesc {
phase: SpreadPhase;
action: (self: InsightUnit, session?: Session) => Promise<void>;
};
export const on = (phase: SpreadPhase, action: (self: InsightUnit, session?: Session) => Promise<void>): SpreadDesc => ({ phase, action });
const transformDetail = <T>(detailDesc?: DetailDesc<Record<string, unknown>, Record<string, unknown>, Record<string, unknown>, T, string>):
DetailDesc<Record<string, unknown>, Record<string, unknown>, Record<string, unknown>, unknown, string> | undefined => {
return detailDesc
? {
...detailDesc,
fetchData: detailDesc.fetchData as (session: Session, metadata: unknown) => Promise<Array<Record<string, unknown>>>,
fetchExtraData: detailDesc?.fetchExtraData as (session: Session, metadata: unknown) => Promise<Array<Record<string, unknown>>>,
fetchMoreData: detailDesc?.fetchMoreData as (session: Session, metadata: unknown) => Promise<Array<Record<string, unknown>>>,
}
: undefined;
};
const wrapSpread = (original?: SpreadDesc): SpreadDesc | undefined => {
if (!original) { return undefined; }
let isSpread = false;
return {
...original,
action: async (self: InsightUnit, session?: Session): Promise<void> => {
if (isSpread) { return; }
await original?.action(self, session);
isSpread = true;
},
};
};
export const unitBase = <T extends MetaDataBase = MetaDataBase>(params:
Omit<InsightUnitParams<T, Record<string, unknown>, Record<string, unknown>, Record<string, unknown>>, 'metadata'>): typeof basicUnitClass => {
const basicUnitClass = class implements InsightUnit {
parent?: InsightUnit;
_children?: InsightUnit[];
isMultiDeviceHidden = false;
isUnitVisible = true;
isMerged = false;
isSummaryLoading: boolean = false;
isTraceLoading: boolean = false;
type = 'basic' as const;
pinType = params.pinType ?? 'copied';
name = params.name;
tag = params.tag as string | ((session: Session, metadata: unknown) => string | null);
buttons = params.buttons ?? [];
description = params.description;
configBar = params.configBar as ((session: Session, metadata: unknown, onClick?: () => void, isHovered?: boolean, isSelected?: boolean) => JSX.Element | null);
metadata: T;
chart = params.chart;
tabState = params.tabState ?? undefined;
notifications = params.notifications as Array<(metaData: unknown) => (false | string)>;
expandable = false;
isExpanded = false;
hasExpanded = false;
isOverlapAnalysisLoading = true;
isDisplay = true;
detail = transformDetail(params.detail);
bottomPanelRender = params.bottomPanelRender;
collapseAction = params.collapseAction;
spreadUnits = wrapSpread(params.spreadUnits);
phase: UnitPhase = 'configuring';
searchConfig = params.searchConfig;
collapsible = params.collapsible ?? true;
isParseLoading: boolean = false;
shouldParse: boolean = false;
progress: number = 0;
showProgress: boolean = false;
havePythonFunction: boolean = false;
projectType?: ProjectType;
constructor(metadata: T, projectType?: ProjectType) {
const excludeAttrs = ['searchConfig', 'parent', 'renderInfo', 'height', 'type', 'configBar', 'projectType', 'hasExpanded'];
makeAutoObservable(this, { _children: observable.shallow, ...Object.fromEntries(excludeAttrs.map(k => [k, false])) });
this.metadata = metadata;
if (projectType !== undefined) {
this.projectType = projectType;
}
this.children = params.spreadUnits ? [] : undefined;
const spreadUnits = params.spreadUnits;
if (spreadUnits?.phase === 'create') { spreadUnits.action(this); }
}
get children(): InsightUnit[] | undefined {
if (Array.isArray(this._children)) {
for (const child of this._children) {
child.parent = this;
}
}
return this._children;
}
set children(ch: InsightUnit[] | undefined) {
if (Array.isArray(ch)) {
for (const child of ch) {
child.parent = this;
}
}
this._children = ch;
}
renderInfo = (session: Session): (string | JSX.Element | null) => {
return params.renderInfo?.(session, this.metadata, this) ?? null;
};
height = (): number => heightOf(this.chart, this.name);
};
return basicUnitClass;
};
export { unitBase as unit };
export interface UnitMatcher {
target: (ele: InsightUnit) => boolean;
onSuccess: (ele: InsightUnit) => void;
showDetail?: boolean;
};
export type LinkLine = Array<Record<string, unknown>>;
export interface LinkLines {
[x: string]: LinkLine | undefined;
};
* @member source the source file defining this template
*/
export interface InsightTemplate {
id: string;
name: string;
source: '<internal>' | string;
icon?: JSX.Element;
description: string;
units: InsightUnitClass[];
availableUnits: InsightUnitClass[];
imgColor?: string;
isNsMode: boolean;
hasTraceAnalyzing?: boolean;
buttons?: React.FC<{ session: Session }>;
};
* Recursive set unit
*
* @param unit unit
* @param unitPhase unitPhase
*/
const MAX_RECURSIVE_COUNT = 10;
export const recursiveSetUnits = (unit: InsightUnit, unitPhase: string, count = 1): void => {
if (!unit.children || count > MAX_RECURSIVE_COUNT) {
return;
}
for (let index = 0; index < unit.children.length; index++) {
runInAction(() => {
if (unit.children !== undefined) {
unit.children[index].phase = unitPhase;
}
});
recursiveSetUnits(unit.children[index], unitPhase, count + 1);
}
};
* Compatible with older version exports that do not include the unit phase attribute.
* This method is also used in the setting of off-line lane unit phase for real-time recording.
*
* @param units session units
* @param unitPhase unitPhase
*/
export const processUnits = (units: InsightUnit[], unitPhase: string): void => {
runInAction(() => {
units.forEach(unit => {
if (unit.phase === 'error' || unit.phase === 'download') {
return;
}
unit.phase = unitPhase;
recursiveSetUnits(unit, unitPhase);
});
});
};
* Set the unit phase corresponding to the plugin.
*
* @param unit unit
* @param phase unit phase
*/
export function setUnitPhase(unit: InsightUnit, phase: UnitPhase): void {
if (unit.phase === 'error') {
return;
}
unit.phase = phase;
recursiveSetUnits(unit, phase);
}
* Set the unit phase corresponding to the plugin.
*
* @param cardId 待设置状态的cardId
* @param session session
* @param phase unit phase
*/
export function setUnitPhaseByCardId(cardId: string, session: Session, phase: UnitPhase): void {
session.units.forEach(unit => {
if (unit.metadata.cardId !== cardId) {
return unit;
}
setUnitPhase(unit, phase);
return unit;
});
}
* 设置每个卡的进度条组件是否显示
* @param unitData
* @param session
*/
export function setUnitProgressByFileId(unitData: any, session: Session): void {
const targetUnit = session.units.find(
(unit) => unit.metadata.cardId === unitData.unit.metadata.cardId,
);
if (!targetUnit) return;
targetUnit.progress = 100;
targetUnit.showProgress = false;
scheduleShouldParseUpdate(targetUnit);
}
function scheduleShouldParseUpdate(unit: any): void {
setTimeout(() => {
runInAction(() => {
unit.shouldParse = false;
});
}, 300);
}