* -------------------------------------------------------------------------
* 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 React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled from '@emotion/styled';
import { observer } from 'mobx-react';
import { SelectedDataBase } from './details/base/SelectedData';
import type { Session } from '../entity/session';
import type { SingleDataDesc } from '../entity/insight';
import { useSelectedDataDetailUpdater } from './details/hooks';
import type { AscendSliceDetail } from '../entity/data';
import { CaretDownIcon } from '@insight/lib/icon';
import { safeJSONParse } from '@insight/lib/utils';
import { ResizeTable } from '@insight/lib/resize';
import { getDefaultColumData, getPageData, queryTableDataDetails } from './detailViews/Common';
import { DragDirection, useDraggableContainer } from '@insight/lib';
import { ChartErrorBoundary } from './error/ChartErrorBoundary';
import { MoreContainer, StyledMoreCard } from './BottomPanel';
import { DETAIL_HEADER_HEIGHT_ETC_PX } from './detailViews/SystemView';
import { runInAction } from 'mobx';
interface DetailProps<T extends Record<string, unknown>> {
session: Session;
detail: SingleDataDesc<Record<string, unknown>, unknown>;
children: React.FC<{data: T; session: Session}>;
height: number;
}
const StyledSliceDetailDiv = styled.div`
width: 100%;
color: ${(props): string => props.theme.tableTextColor};
display: flex;
font-size: 12px;
`;
const StyledSliceArgsDiv = styled.div`
width: 100%;
color: ${(props): string => props.theme.tableTextColor};
text-align: left;
font-size: 12px;
`;
const StyledSliceArgsRow = styled.div`
display: flex;
margin-left: 24px;
padding: 8px 0;
word-break: break-all;
.key {
flex: none;
width: 180px;
margin-right: 20px;
}
`;
const createContentWithBreaks = (content: string): React.ReactNode => {
return content?.split('\n').map((line, index) => (
<React.Fragment key={index}>
{line}
<br />
</React.Fragment>
));
};
const createContentNormal = (content: number | string | Array<number | string> | object[], setDataLink?: (data: string) => void): string | React.ReactNode => {
if (Array.isArray(content)) {
if (content.length > 0 && typeof content[0] === 'object' && content[0] !== null) {
const headers = Object.keys(content[0]);
if (headers.length === 0) {
return '';
}
const columns = headers.map((key: string) => ({
title: key,
dataIndex: key,
...getDefaultColumData(key),
sorter: (a: any, b: any) => {
const numA = Number(a[key]);
const numB = Number(b[key]);
if (!isNaN(numA) && !isNaN(numB)) {
return numA - numB;
}
return a[key].localeCompare(b[key]);
},
}));
const dataSource = content as object[];
return <ResizeTable
onRow={(record: any): { onClick: () => void } => ({
onClick: (): void => {
if (record.rid !== undefined && setDataLink !== undefined) {
setDataLink(record.rid);
}
},
})}
columns={columns}
dataSource={dataSource}
pagination={false}/>;
}
return createContentWithArray(content as string[], setDataLink);
}
if (content === undefined || content === '') {
return '""';
}
if (content === null) {
return 'null';
}
return Number.isFinite(Number(content)) ? `${content}` : `"${content}"`;
};
const createContentWithArray = (content: Array<number | string>, setDataLink?: (data: string) => void): string => {
return `[${content.map((item) => createContentNormal(item, setDataLink)).filter((x) => typeof x === 'string').join(', ')}]`;
};
interface RidMoreProps {
card: string | undefined;
value: string;
bottomHeight: number;
}
interface EqualCondition {
col: string;
content: string;
}
interface TableData {
columnAttr: any[];
tableData: any[];
totalNum: number;
}
const RidMoreTable = observer(({ card, value, bottomHeight }: RidMoreProps) => {
const [dataSource, setDataSource] = useState<any[]>([]);
const defaultPage = { current: 1, pageSize: 10, total: 0 };
const [page, setPage] = useState(defaultPage);
const defaultSorter = { field: '', order: '' };
const [sorter, setSorter] = useState(defaultSorter);
const [column, setColumn] = useState<any[]>([]);
const [isLoading, setLoading] = useState(false);
const [condition, setCondition] = useState({ page, sorter });
useTranslation();
useEffect(() => {
setCondition({ page, sorter });
}, [sorter, page.current, page.pageSize]);
useEffect(() => {
const equalCondition: EqualCondition = { col: 'rid', content: String(value) };
const equalConditions: EqualCondition[] = [];
equalConditions.push(equalCondition);
const param = {
rankId: card as string,
pageSize: condition.page.pageSize,
currentPage: condition.page.current,
order: condition.sorter.order,
orderBy: condition.sorter.field,
selectKey: 0,
type: '1',
equalConditions,
};
if (param.rankId === '' || param.rankId === undefined) {
return;
}
setLoading(true);
queryTableDataDetails(param).then((res) => {
const data = res as TableData;
const cols = data.columnAttr.map((item) => {
return { title: item.key as string, dataIndex: item.key, ...getDefaultColumData(item.key) };
});
setColumn(cols);
setDataSource(data.tableData);
setPage((prevPage: any) => ({ ...prevPage, total: res.totalNum }));
setLoading(false);
});
}, [condition.page.current, condition.page.pageSize,
condition.sorter.field, condition.sorter.order, card, value]);
return <ResizeTable
onChange={(pagination: unknown, filters: any, newsorter: unknown, extra: {action: string}): void => {
if (extra.action === 'sort') {
setSorter(newsorter as typeof sorter);
}
}}
rowClassName={(): string => {
return 'click-able';
}}
pagination={getPageData(page, setPage)} dataSource={dataSource} columns={column} size="small" loading={isLoading}
scroll={{ y: bottomHeight - DETAIL_HEADER_HEIGHT_ETC_PX }}
/>;
});
const ArgsData = observer(({ data, linkUpdater }: { data: AscendSliceDetail; linkUpdater?: (data: string) => void}): JSX.Element => {
const argsJson = data.args;
const [isHiddenArgs, setHidden] = useState(false);
const { t } = useTranslation('timeline', { keyPrefix: 'sliceDetail' });
if (argsJson === undefined) {
return <></>;
}
const args = safeJSONParse(argsJson);
if (args === null) {
return <></>;
}
const breakKeys = ['Call stack', 'code'];
return <div>
<StyledSliceArgsDiv>
<CaretDownIcon
onClick={ (): void => setHidden(!isHiddenArgs) } style={{ margin: '-2px 0 0 8px', float: 'left', transform: `rotate(${!isHiddenArgs ? 0 : '-90deg'}) translate(${!isHiddenArgs ? '-2' : '1'}px, ${!isHiddenArgs ? '0' : '-2'}px)`, cursor: 'pointer' }}/>
<div style={{ fontWeight: 'bold', margin: '8px 0 0 8px' }}>{t('Args')}</div>
{!isHiddenArgs
? Object.keys(args).map(key => {
return <StyledSliceArgsRow key={key}>
<div className="key">{key}</div>
<div className="value">
{ breakKeys.includes(key) ? createContentWithBreaks(args[key]) : createContentNormal(args[key], linkUpdater) }
</div>
</StyledSliceArgsRow>;
})
: <></>}
</StyledSliceArgsDiv>
</div>;
});
export const SelectedDataBottomPanel = observer(<T extends Record<string, unknown>>(props: DetailProps<T>): JSX.Element => {
const { session, detail, height } = props;
const { renderFields, data } = useSelectedDataDetailUpdater(session, detail, session.selectedData);
const sliceDetailData = data as AscendSliceDetail;
const [view] = useDraggableContainer({ dragDirection: DragDirection.RIGHT, draggableWH: 800 });
const [dataLink, setDataLink] = useState<string>('');
useEffect(() => {
runInAction(() => {
session.drawLineMode = 'all';
session.ridLineType = dataLink;
session.renderTrigger = !session.renderTrigger;
});
}, [dataLink]);
useEffect(() => {
setDataLink('');
}, [renderFields]);
if (!session.isIE) {
return <div style={{ height: '100%', overflow: 'scroll' }}><StyledSliceDetailDiv>
<SelectedDataBase renderer={renderFields}/>
<div></div>
</StyledSliceDetailDiv>
{(data as AscendSliceDetail).args === undefined ? <></> : <ArgsData data={sliceDetailData}/>}
</div>;
} else {
return view({
mainContainer: <div style={{ height: '100%', overflow: 'scroll' }}><StyledSliceDetailDiv>
<SelectedDataBase renderer={renderFields}/>
<div></div>
</StyledSliceDetailDiv>
{(data as AscendSliceDetail).args === undefined ? <></> : <ArgsData linkUpdater={setDataLink} data={sliceDetailData}/>}
</div>,
draggableContainer: <StyledMoreCard
className="moreContainer"
bordered={false}>
<ChartErrorBoundary className={'more-error'}>
<MoreContainer>
<RidMoreTable bottomHeight={height} card={session.selectedData?.cardId} value={dataLink}></RidMoreTable>
</MoreContainer>
</ChartErrorBoundary>
</StyledMoreCard>,
id: 'SelectedDataBottomPanel',
});
}
});