Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion packages/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@axonivy/ui-icons": "~14.0.0-next",
"@tanstack/react-query": "^5.64",
"@tanstack/react-query-devtools": "^5.64",
"@tanstack/react-table": "^8.20",
"@tanstack/react-table": "^9.0.0",
"i18next": "^25.0.0 || ^26.0.0",
"inversify": "^6.2",
"react": "^19.0.0",
Expand Down
8 changes: 4 additions & 4 deletions packages/editor/src/ui-tools/history/History.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const HistoryContent = ({ actionDispatcher, togglePinned, closeHistory, a
updater => {
setExpandedByPid(current => ({
...current,
[pid]: typeof updater === 'function' ? updater(expanded) : updater
[pid]: typeof updater === 'function' ? updater(current[pid] ?? expanded) : updater
}));
},
[expanded, pid]
Expand All @@ -92,17 +92,17 @@ export const HistoryContent = ({ actionDispatcher, togglePinned, closeHistory, a
async (node: HistoryNode) => {
setExpandedByPid(current => ({
...current,
[pid]: setExpandedNode(current[pid] ?? (data ? lastLeafPathExpandedState(data) : {}), node.id, true)
[pid]: setExpandedNode(current[pid] ?? expanded, node.id, true)
}));
const result = await loadLazyNodeData(node);
if (result === 'error' || result === 'invalid') {
setExpandedByPid(current => ({
...current,
[pid]: setExpandedNode(current[pid] ?? (data ? lastLeafPathExpandedState(data) : {}), node.id, false)
[pid]: setExpandedNode(current[pid] ?? expanded, node.id, false)
}));
}
},
[data, loadLazyNodeData, pid]
[expanded, loadLazyNodeData, pid]
);

const successActions: Array<ButtonProps> = [
Expand Down
156 changes: 94 additions & 62 deletions packages/editor/src/ui-tools/history/HistoryTree.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
// @jsxRuntime automatic
import { type HistoryNode } from '@axonivy/process-editor-protocol';
import { BasicTooltip, ExpandableCell, IvyIcon, Table, TableBody, TableCell, TableRow, useTableGlobalFilter } from '@axonivy/ui-components';
import { IvyIcons } from '@axonivy/ui-icons';
import {
flexRender,
getCoreRowModel,
getExpandedRowModel,
useReactTable,
type ColumnDef,
type ExpandedState,
type OnChangeFn
} from '@tanstack/react-table';
BasicTooltip,
dataTreeHelper,
ExpandableCell,
IvyIcon,
Table,
TableBody,
TableCell,
TableGlobalFilter,
TableRow,
type DataTableFeatures
} from '@axonivy/ui-components';
import { IvyIcons } from '@axonivy/ui-icons';
import { flexRender, useTable, type ExpandedState, type OnChangeFn } from '@tanstack/react-table';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { isExpandableDataNode, isHistoryNodeLoaded, type HistoryLazyState } from './history-tree-state';
import { isExpandableDataNode, isHistoryNodeLoaded, lastLeafPathExpandedState, type HistoryLazyState } from './history-tree-state';

export type HistoryTreeProps = {
data: Array<HistoryNode>;
Expand All @@ -24,65 +27,70 @@ export type HistoryTreeProps = {
onLoadLazyNode: (node: HistoryNode) => void;
};

const { columnHelper, tableOptions } = dataTreeHelper<HistoryNode>();
const loadingChildIdPrefix = 'history-loading-';

export const HistoryTree = ({ data, searchActive, expanded, onExpandedChange, lazyState, onLoadLazyNode }: HistoryTreeProps) => {
const globalFilter = useTableGlobalFilter({ searchAutoFocus: true });
const columns: ColumnDef<HistoryNode, string>[] = useMemo(
() => [
{
accessorKey: 'description',
cell: cell => {
const node = cell.row.original;
const label =
node.type === 'EXECUTION'
? new Date(cell.getValue()).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3
})
: cell.getValue();
const treeData = useMemo(() => addLoadingChildren(data, lazyState.loadingById), [data, lazyState.loadingById]);
const columns = useMemo(
() =>
columnHelper.columns([
columnHelper.accessor('description', {
cell: cell => {
const node = cell.row.original;
const loadingParentId = getLoadingParentId(node.id);
const label =
node.type === 'EXECUTION'
? new Date(cell.getValue()).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3
})
: cell.getValue();

return (
<ExpandableCell
cell={cell}
icon={historyNodeIcon(node)}
lazy={
isExpandableDataNode(node)
? {
isLoaded: isHistoryNodeLoaded(node, lazyState),
loadChildren: () => onLoadLazyNode(node)
}
: undefined
}
>
<LazyStatus lazyState={lazyState} node={node} />
<span>{label}</span>
</ExpandableCell>
);
}
}
],
return (
<ExpandableCell
cell={cell}
icon={loadingParentId ? undefined : historyNodeIcon(node)}
lazy={
isExpandableDataNode(node)
? {
isLoaded: isHistoryNodeLoaded(node, lazyState),
loadChildren: () => onLoadLazyNode(node)
}
: undefined
}
>
{loadingParentId ? (
<LazyStatus lazyState={lazyState} nodeId={loadingParentId} />
) : (
<>
<LazyStatus lazyState={lazyState} nodeId={node.id} />
<span>{label}</span>
</>
)}
</ExpandableCell>
);
}
})
]),
[lazyState, onLoadLazyNode]
);
const table = useReactTable({
...globalFilter.options,
data,
const table = useTable<DataTableFeatures, HistoryNode>({
...tableOptions,
data: treeData,
columns,
getCoreRowModel: getCoreRowModel(),
getExpandedRowModel: getExpandedRowModel(),
getSubRows: row => row.children,
getRowId: row => row.id,
getRowCanExpand: row => row.original.children.length > 0,
initialState: { expanded: lastLeafPathExpandedState(treeData) },
autoResetExpanded: false,
onExpandedChange,
state: {
expanded,
...globalFilter.tableState
}
state: { expanded }
});

return (
<>
{searchActive && globalFilter.filter}
<TableGlobalFilter table={table} autoFocus={true} active={searchActive} />
<Table>
<TableBody>
{table.getRowModel().rows.map(row => (
Expand All @@ -98,16 +106,16 @@ export const HistoryTree = ({ data, searchActive, expanded, onExpandedChange, la
);
};

const LazyStatus = ({ lazyState, node }: { lazyState: HistoryLazyState; node: HistoryNode }) => {
const LazyStatus = ({ lazyState, nodeId }: { lazyState: HistoryLazyState; nodeId: string }) => {
const { t } = useTranslation();
if (lazyState.loadingById[node.id]) {
if (lazyState.loadingById[nodeId]) {
return (
<BasicTooltip content={t('history.loadingNode')}>
<IvyIcon icon={IvyIcons.Spinner} spin className='lazy-state-icon' role='status' aria-label={t('history.loadingNode')} />
</BasicTooltip>
);
}
const error = lazyState.errorById[node.id];
const error = lazyState.errorById[nodeId];
if (error) {
return (
<BasicTooltip content={error}>
Expand All @@ -117,6 +125,30 @@ const LazyStatus = ({ lazyState, node }: { lazyState: HistoryLazyState; node: Hi
}
};

const addLoadingChildren = (nodes: Array<HistoryNode>, loadingById: Record<string, boolean>): Array<HistoryNode> =>
nodes.map(node => {
const children = addLoadingChildren(node.children, loadingById);
if (!loadingById[node.id]) {
return children === node.children ? node : { ...node, children };
}

return {
...node,
children: [
{
id: `${loadingChildIdPrefix}${node.id}`,
type: 'DATA',
description: 'Loading...',
expandable: false,
children: []
}
]
};
});

const getLoadingParentId = (nodeId: string) =>
nodeId.startsWith(loadingChildIdPrefix) ? nodeId.slice(loadingChildIdPrefix.length) : undefined;

const historyNodeIcon = (node: HistoryNode) => {
switch (node.type) {
case 'REQUEST_FINISHED':
Expand Down
2 changes: 1 addition & 1 deletion packages/inscription-view/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@radix-ui/react-tabs": "^1.1",
"@tanstack/react-query": "^5.64",
"@tanstack/react-query-devtools": "^5.64",
"@tanstack/react-table": "^8.20",
"@tanstack/react-table": "^9.0.0",
"downshift": "^9.0",
"i18next": "^25.0.0 || ^26.0.0",
"react": "^19.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { SelectRow, TableCell, TableRow, type BrowserNode } from '@axonivy/ui-components';
import { flexRender, type Row } from '@tanstack/react-table';
import { type DataTableFeatures, SelectRow, TableCell, TableRow } from '@axonivy/ui-components';
import { flexRender, type Row, type RowData } from '@tanstack/react-table';

const BrowserTableRow = <T,>({ row, onDoubleClick }: { row: Row<T>; onDoubleClick: () => void }) => (
const BrowserTableRow = <TData extends RowData & { notSelectable?: boolean },>({
row,
onDoubleClick
}: {
row: Row<DataTableFeatures, TData>;
onDoubleClick: () => void;
}) => (
<>
{(row.original as BrowserNode).notSelectable ? (
{row.original.notSelectable ? (
<TableRow>
{row.getVisibleCells().map(cell => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
Expand Down
Loading