Skip to content

Commit 17af14c

Browse files
authored
feat: DH-19382: Add actions and timing info on console history hover (#2526)
Adds new actions when hovering over console history items: copy, rerun, and a contextual help with timing (from the same source as the extant time in our command history but formatted differently to meet spec) as well as new server timing.
1 parent 089eabd commit 17af14c

17 files changed

+582
-99
lines changed

package-lock.json

Lines changed: 83 additions & 69 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
"@deephaven/jsapi-components": "file:packages/jsapi-components",
178178
"@deephaven/jsapi-nodejs": "file:packages/jsapi-nodejs",
179179
"@deephaven/jsapi-shim": "file:packages/jsapi-shim",
180-
"@deephaven/jsapi-types": "^1.0.0-dev0.40.4",
180+
"@deephaven/jsapi-types": "^1.0.0-dev0.40.6",
181181
"@deephaven/jsapi-utils": "file:packages/jsapi-utils",
182182
"@deephaven/log": "file:packages/log",
183183
"@deephaven/mocks": "file:packages/mocks",

packages/console/src/Console.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ export class Console extends PureComponent<ConsoleProps, ConsoleState> {
347347
error?: string;
348348
changes: DhType.ide.VariableChanges;
349349
cancel: () => unknown;
350+
startTimestamp?: DhType.LongWrapper;
351+
endTimestamp?: DhType.LongWrapper;
350352
};
351353
}>
352354
): void {
@@ -400,16 +402,22 @@ export class Console extends PureComponent<ConsoleProps, ConsoleState> {
400402
message: string;
401403
error?: string;
402404
changes: DhType.ide.VariableChanges;
405+
startTimestamp?: DhType.LongWrapper;
406+
endTimestamp?: DhType.LongWrapper;
403407
}
404408
| undefined,
405409
historyItem: ConsoleHistoryActionItem,
406410
workspaceItemPromise: Promise<CommandHistoryStorageItem>
407411
): void {
412+
const serverStartTime = result?.startTimestamp?.asNumber();
413+
const serverEndTime = result?.endTimestamp?.asNumber();
408414
const newHistoryItem = {
409415
...historyItem,
410416
wrappedResult: undefined,
411417
cancelResult: undefined,
412418
result: result ?? historyItem.result,
419+
serverStartTime,
420+
serverEndTime,
413421
};
414422

415423
this.setState(({ consoleHistory }) => {
@@ -432,7 +440,11 @@ export class Console extends PureComponent<ConsoleProps, ConsoleState> {
432440
this.updateHistory(result, newHistoryItem);
433441
this.updateKnownObjects(newHistoryItem);
434442
this.updateWorkspaceHistoryItem(
435-
{ error: result.error },
443+
{
444+
error: result.error,
445+
serverStartTime,
446+
serverEndTime,
447+
},
436448
workspaceItemPromise
437449
);
438450

@@ -655,11 +667,16 @@ export class Console extends PureComponent<ConsoleProps, ConsoleState> {
655667

656668
/**
657669
* Updates an existing workspace CommandHistoryItem
658-
* @param result The result to store with the history item. Could be empty object for success
670+
* @param result The result to store with the history item.
671+
* Possibly contains an error message if there was a failure, server start time, and server end time.
659672
* @param workspaceItemPromise The workspace data row promise for the workspace item to be updated
660673
*/
661674
updateWorkspaceHistoryItem(
662-
result: { error?: string },
675+
result: {
676+
error?: string;
677+
serverStartTime?: number;
678+
serverEndTime?: number;
679+
},
663680
workspaceItemPromise: Promise<CommandHistoryStorageItem>
664681
): void {
665682
const promise = this.pending.add(workspaceItemPromise);
@@ -1107,6 +1124,7 @@ export class Console extends PureComponent<ConsoleProps, ConsoleState> {
11071124
supportsType={supportsType}
11081125
iconForType={iconForType}
11091126
ref={this.consoleHistoryContent}
1127+
onCommandSubmit={this.handleCommandSubmit}
11101128
/>
11111129
{historyChildren}
11121130
</div>

packages/console/src/command-history/CommandHistoryItemTooltip.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function makeCommandStarted() {
3030
command: 'started',
3131
result: null,
3232
startTime: Date.now(),
33+
endTime: Date.now(),
3334
};
3435
}
3536

@@ -99,14 +100,14 @@ describe('different command results', () => {
99100
expect(cleanup).toHaveBeenCalled();
100101
});
101102

102-
it('renders <1s elapsed time for a command that was just started', () => {
103+
it('renders correct time for a command that was just started', () => {
103104
callback(makeCommandStarted());
104-
expect(screen.getByText('<1s')).toBeTruthy();
105+
expect(screen.getByText('0.00s')).toBeTruthy();
105106
});
106107

107108
it('renders correct time for completed command', () => {
108109
callback(makeCommandCompletedSuccess());
109-
expect(screen.getByText('5s')).toBeTruthy();
110+
expect(screen.getByText('5.00s')).toBeTruthy();
110111
});
111112

112113
it('shows error message', () => {

packages/console/src/command-history/CommandHistoryItemTooltip.tsx

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,6 @@ export class CommandHistoryItemTooltip extends Component<
3838
onUpdate: (): void => undefined,
3939
};
4040

41-
static getTimeString(
42-
startTime: string | undefined,
43-
endTime: string | number
44-
): string | null {
45-
if (startTime == null || endTime === '' || endTime === 0) {
46-
return null;
47-
}
48-
49-
const deltaTime = Math.round(
50-
(new Date(endTime).valueOf() - new Date(startTime).valueOf()) / 1000
51-
);
52-
53-
if (deltaTime < 1) return '<1s';
54-
55-
return TimeUtils.formatElapsedTime(deltaTime);
56-
}
57-
5841
constructor(props: CommandHistoryItemTooltipProps) {
5942
super(props);
6043

@@ -167,7 +150,7 @@ export class CommandHistoryItemTooltip extends Component<
167150

168151
const errorMessage = result?.error ?? error;
169152

170-
const timeString = CommandHistoryItemTooltip.getTimeString(
153+
const timeString = TimeUtils.formatConvertedDuration(
171154
startTime,
172155
endTime ?? currentTime
173156
);

packages/console/src/command-history/CommandHistoryStorage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface CommandHistoryStorageData {
1010
command: string;
1111
startTime: string;
1212
endTime?: string;
13-
result?: { error?: string };
13+
result?: { error?: string; serverStartTime?: number; serverEndTime?: number };
1414
}
1515

1616
export interface CommandHistoryStorageItem extends StorageItem {

packages/console/src/console-history/ConsoleHistory.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,11 @@ $console-gutter-width: 30px;
4444
}
4545
}
4646
}
47+
48+
.console-history-item-tooltip {
49+
display: grid;
50+
grid-template-columns: auto auto;
51+
gap: $spacer-1 $spacer-3;
52+
text-align: left;
53+
margin-bottom: $spacer-2;
54+
}

packages/console/src/console-history/ConsoleHistory.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ interface ConsoleHistoryProps {
1515
disabled?: boolean;
1616
supportsType: (type: string) => boolean;
1717
iconForType: (type: string) => ReactElement;
18+
onCommandSubmit: (command: string) => void;
1819
}
1920

2021
function itemKey(i: number, item: ConsoleHistoryActionItem): string {
@@ -37,6 +38,7 @@ const ConsoleHistory = React.forwardRef(function ConsoleHistory(
3738
openObject,
3839
supportsType,
3940
iconForType,
41+
onCommandSubmit,
4042
} = props;
4143
const historyElements = [];
4244
for (let i = 0; i < items.length; i += 1) {
@@ -50,6 +52,7 @@ const ConsoleHistory = React.forwardRef(function ConsoleHistory(
5052
language={language}
5153
supportsType={supportsType}
5254
iconForType={iconForType}
55+
onCommandSubmit={onCommandSubmit}
5356
/>
5457
);
5558
historyElements.push(historyElement);

packages/console/src/console-history/ConsoleHistoryItem.scss

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,38 @@ $button-vert-margin: ConsoleVariables.$button-vert-margin;
1212
}
1313

1414
.console-history-item-command {
15+
position: relative;
1516
white-space: pre-line;
1617
}
1718

19+
.console-history-actions {
20+
position: absolute;
21+
gap: $spacer-1;
22+
top: 0;
23+
right: $spacer-2;
24+
display: none;
25+
align-items: center;
26+
background-color: var(--dh-color-neutral-hover-bg);
27+
border-radius: $border-radius;
28+
padding: $spacer-1;
29+
}
30+
31+
.console-history-item-command-tooltip-active {
32+
background-color: var(--dh-color-neutral-hover-bg);
33+
34+
.console-history-actions {
35+
display: flex;
36+
}
37+
}
38+
39+
.console-history-item-command:hover {
40+
background-color: var(--dh-color-neutral-hover-bg);
41+
42+
.console-history-actions {
43+
display: flex;
44+
}
45+
}
46+
1847
.console-history-item-result .log-message {
1948
white-space: pre-wrap;
2049
word-wrap: break-word;
@@ -95,3 +124,42 @@ $button-vert-margin: ConsoleVariables.$button-vert-margin;
95124
}
96125
}
97126
}
127+
128+
.console-history-actions-1 {
129+
// actions are centered over the line
130+
// note that the actions are much wider than the line itself
131+
top: -($spacer-3 * 0.875);
132+
}
133+
134+
.console-history-actions-2 {
135+
// actions are centered over the line
136+
top: -($spacer-1 * 0.5);
137+
}
138+
139+
.console-command-result:last-child .console-history-actions-1 {
140+
// pushed down so that the actions are visible
141+
top: -$spacer-4;
142+
}
143+
144+
.console-command-result:first-child .console-history-actions-1 {
145+
// pushed up to prevent layout shifts
146+
top: $spacer-0;
147+
}
148+
149+
.console-history-item-contextual-help-content {
150+
display: grid;
151+
grid-template-columns: auto auto;
152+
gap: $spacer-1 $spacer-3;
153+
text-align: left;
154+
margin-bottom: $spacer-2;
155+
white-space: nowrap;
156+
}
157+
158+
.console-history-item-contextual-help {
159+
section[class*='spectrum-ContextualHelp-dialog'] {
160+
// The default size of the contextual help is only 250px and is too small.
161+
// Just set a size automatically based on the content.
162+
width: fit-content;
163+
min-width: 150px;
164+
}
165+
}

packages/console/src/console-history/ConsoleHistoryItem.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import ConsoleHistoryResultInProgress from './ConsoleHistoryResultInProgress';
1212
import ConsoleHistoryResultErrorMessage from './ConsoleHistoryResultErrorMessage';
1313
import './ConsoleHistoryItem.scss';
1414
import { type ConsoleHistoryActionItem } from './ConsoleHistoryTypes';
15+
import ConsoleHistoryItemActions from './ConsoleHistoryItemActions';
1516

1617
const log = Log.module('ConsoleHistoryItem');
1718

@@ -24,11 +25,16 @@ interface ConsoleHistoryItemProps {
2425
// eslint-disable-next-line react/no-unused-prop-types
2526
supportsType: (type: string) => boolean;
2627
iconForType: (type: string) => ReactElement;
28+
onCommandSubmit: (command: string) => void;
29+
}
30+
31+
interface ConsoleHistoryItemState {
32+
isTooltipVisible: boolean;
2733
}
2834

2935
class ConsoleHistoryItem extends PureComponent<
3036
ConsoleHistoryItemProps,
31-
Record<string, never>
37+
ConsoleHistoryItemState
3238
> {
3339
static defaultProps = {
3440
disabled: false,
@@ -37,6 +43,10 @@ class ConsoleHistoryItem extends PureComponent<
3743
constructor(props: ConsoleHistoryItemProps) {
3844
super(props);
3945

46+
this.state = {
47+
isTooltipVisible: false,
48+
};
49+
4050
this.handleCancelClick = this.handleCancelClick.bind(this);
4151
this.handleObjectClick = this.handleObjectClick.bind(this);
4252
}
@@ -57,17 +67,29 @@ class ConsoleHistoryItem extends PureComponent<
5767
}
5868

5969
render(): ReactElement {
60-
const { disabled, item, language, iconForType } = this.props;
70+
const { isTooltipVisible } = this.state;
71+
const { disabled, item, language, iconForType, onCommandSubmit } =
72+
this.props;
6173
const { disabledObjects, result } = item;
6274
const hasCommand = item.command != null && item.command !== '';
63-
6475
let commandElement = null;
6576
if (hasCommand) {
6677
commandElement = (
67-
<div className="console-history-item-command">
78+
<div
79+
className={classNames('console-history-item-command', {
80+
'console-history-item-command-tooltip-active': isTooltipVisible,
81+
})}
82+
>
6883
<div className="console-history-gutter">&gt;</div>
6984
<div className="console-history-content">
7085
<Code language={language}>{item.command}</Code>
86+
<ConsoleHistoryItemActions
87+
item={item}
88+
onCommandSubmit={onCommandSubmit}
89+
handleTooltipVisible={(isVisible: boolean) =>
90+
this.setState({ isTooltipVisible: isVisible })
91+
}
92+
/>
7193
</div>
7294
</div>
7395
);

0 commit comments

Comments
 (0)