Skip to content

Commit

Permalink
Display active runtime metadata in console pane (#6156)
Browse files Browse the repository at this point in the history
Addresses #5999 

Adds a new feature flag called `positron.multipleConsoleSessions`. When
the feature flag is on users can now view details for an active session
by clicking on the new info icon button that lives in the console pane
action bar. A modal popup will render, containing the details of the
session, such as name, state, id, interpreter path, and environment
management tool.

The popup provides a link to the session logs in the output pane. When
clicked, the user is taken to the output pane and shown the kernel
channel for their interpreter.

This is a first pass for the info panel - additional information may be
added based off feedback! Follow up work to add additional links to
other channels is covered in #6149.

### Release Notes

#### New Features

- N/A

#### Bug Fixes

- N/A


### QA Notes
- [ ] Verify the info button is only shown when the feature flag is on
and there is an active session for the console instance
- Note: An issue has been noted where the popup is not rendered if the
console pane height is smaller than the height of the popup (#3061)
- [ ] Verify the session status shown in the popup updates in real time

### Screenshots

**Feature Flag**
<img width="1272" alt="Screenshot 2025-01-29 at 2 19 30 PM"
src="https://github.com/user-attachments/assets/84154034-f7a7-4696-b016-61c4aa3c9370"
/>

**Popup**
<img width="1330" alt="Screenshot 2025-01-29 at 4 38 16 PM"
src="https://github.com/user-attachments/assets/fc819ef6-ad75-4e59-9217-47b97c965b41"
/>

**Real-Time Session State Updates**

https://github.com/user-attachments/assets/16b0b81f-7b48-43d0-9f54-047e6e2af4be
  • Loading branch information
dhruvisompura authored Jan 31, 2025
1 parent 21a4d6b commit 97ebe00
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { PositronConsoleState } from '../../../../services/positronConsole/brows
import { RuntimeExitReason, RuntimeState } from '../../../../services/languageRuntime/common/languageRuntimeService.js';
import { ILanguageRuntimeSession, RuntimeStartMode } from '../../../../services/runtimeSession/common/runtimeSessionService.js';
import { ConsoleInstanceMenuButton } from './consoleInstanceMenuButton.js';
import { multipleConsoleSessionsFeatureEnabled } from '../../../../services/runtimeSession/common/positronMultipleConsoleSessionsFeatureFlag.js';
import { ConsoleInstanceInfoButton } from './consoleInstanceInfoButton.js';

/**
* Constants.
Expand Down Expand Up @@ -101,6 +103,7 @@ export const ActionBar = (props: ActionBarProps) => {

// Constants.
const showDeveloperUI = IsDevelopmentContext.getValue(positronConsoleContext.contextKeyService);
const multiSessionsEnabled = multipleConsoleSessionsFeatureEnabled(positronConsoleContext.configurationService);

// State hooks.
const [activePositronConsoleInstance, setActivePositronConsoleInstance] =
Expand Down Expand Up @@ -382,6 +385,7 @@ export const ActionBar = (props: ActionBarProps) => {
ariaLabel={positronRestartConsole}
onPressed={restartConsoleHandler}
/>
{multiSessionsEnabled && <ConsoleInstanceInfoButton />}
<ActionBarSeparator />
{showDeveloperUI &&
<ActionBarButton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

.positron-modal-popup-children:has(.console-instance-info) {
background: var(--vscode-editorHoverWidget-background);
}

.console-instance-info .top-separator {
border-top: 1px solid var(--vscode-editorHoverWidget-border);
}

.console-instance-info .actions {
background-color: var(--vscode-editorHoverWidget-statusBarBackground);
padding-bottom: 4px;
}

.console-instance-info .content .line {
margin: 8px 0;
overflow-wrap: break-word;
padding: 0 8px;
}

.console-instance-info .actions .link {
color: var(--vscode-textLink-foreground);
cursor: pointer;
font-size: 12px;
line-height: 22px;
padding: 0 8px;
text-decoration: underline;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

// CSS.
import './consoleInstanceInfoButton.css';

// React.
import React, { useEffect, useRef, useState } from 'react';

// Other dependencies.
import { localize } from '../../../../../nls.js';
import * as DOM from '../../../../../base/browser/dom.js';
import { PositronModalPopup } from '../../../../browser/positronComponents/positronModalPopup/positronModalPopup.js'
import { ActionBarButton } from '../../../../../platform/positronActionBar/browser/components/actionBarButton.js';
import { PositronModalReactRenderer } from '../../../../browser/positronModalReactRenderer/positronModalReactRenderer.js';
import { usePositronConsoleContext } from '../positronConsoleContext.js';
import { PositronButton } from '../../../../../base/browser/ui/positronComponents/button/positronButton.js';
import { ILanguageRuntimeSession } from '../../../../services/runtimeSession/common/runtimeSessionService.js';
import { DisposableStore } from '../../../../../base/common/lifecycle.js';

const positronConsoleInfo = localize('positron.console.info.label', "Console information");
const showKernelOutputChannel = localize('positron.console.info.showKernelOutputChannel', "Show Kernel Output Channel");

export const ConsoleInstanceInfoButton = () => {
// Hooks.
const positronConsoleContext = usePositronConsoleContext();

// Reference hooks.
const ref = useRef<HTMLButtonElement>(undefined!);

const handlePressed = () => {
if (!positronConsoleContext.activePositronConsoleInstance) {
return;
}

// Create the renderer.
const renderer = new PositronModalReactRenderer({
keybindingService: positronConsoleContext.keybindingService,
layoutService: positronConsoleContext.workbenchLayoutService,
container: positronConsoleContext.workbenchLayoutService.getContainer(DOM.getWindow(ref.current)),
parent: ref.current
});

renderer.render(
<ConsoleInstanceInfoModalPopup
anchorElement={ref.current}
renderer={renderer}
session={positronConsoleContext.activePositronConsoleInstance.session}
/>
);
}

// Render.
return (
<ActionBarButton
iconId='info'
align='right'
tooltip={positronConsoleInfo}
ariaLabel={positronConsoleInfo}
onPressed={handlePressed}
ref={ref}
/>
)
};

interface ConsoleInstanceInfoModalPopupProps {
anchorElement: HTMLElement;
renderer: PositronModalReactRenderer;
session: ILanguageRuntimeSession;
}

const ConsoleInstanceInfoModalPopup = (props: ConsoleInstanceInfoModalPopupProps) => {
const [sessionState, setSessionState] = useState(() => props.session.getRuntimeState());

// Main useEffect hook.
useEffect(() => {
const disposableStore = new DisposableStore();
disposableStore.add(props.session.onDidChangeRuntimeState(state => {
setSessionState(state);
}));
return () => disposableStore.dispose();
}, []);

const showKernelOutputChannelClickHandler = () => {
props.session.showOutput();
}

// Render.
return (
<PositronModalPopup
anchorElement={props.anchorElement}
height='min-content'
keyboardNavigationStyle='menu'
renderer={props.renderer}
popupAlignment='auto'
popupPosition='auto'
width={400}
>
<div className='console-instance-info'>
<div className='content'>
<p className='line'>{props.session.metadata.sessionName}</p>
<div className='top-separator'>
<p className='line'>
{(() => localize(
'positron.console.info.sessionId', 'Session ID: {0}',
props.session.sessionId
))()}
</p>
<p className='line'>{(() => localize(
'positron.console.info.state', 'State: {0}',
sessionState))()}
</p>
</div>
<div className='top-separator'>
<p className='line'>{(() => localize(
'positron.console.info.runtimePath', 'Path: {0}',
props.session.runtimeMetadata.runtimePath))()}
</p>
<p className='line'>{(() => localize(
'positron.console.info.runtimeSource', 'Source: {0}',
props.session.runtimeMetadata.runtimeSource))()}
</p>
</div>
</div>
<div className='top-separator actions'>
<PositronButton className='link' onPressed={showKernelOutputChannelClickHandler}>
{showKernelOutputChannel}
</PositronButton>
</div>
</div>
</PositronModalPopup>
)
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface RuntimeSessionProps {
*/
export const RuntimeSession = (props: RuntimeSessionProps) => {

const [sessionState, setSessionState] = useState(props.session.getRuntimeState());
const [sessionState, setSessionState] = useState(() => props.session.getRuntimeState());
const [expanded, setExpanded] = useState(false);

// Main useEffect hook.
Expand All @@ -44,7 +44,7 @@ export const RuntimeSession = (props: RuntimeSessionProps) => {
setSessionState(state);
}));
return () => disposableStore.dispose();
});
}, []);

// Render.
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface runtimeSessionCardProps {
*/
export const RuntimeSessionCard = (props: runtimeSessionCardProps) => {

const [sessionState, setSessionState] = useState(props.session.getRuntimeState());
const [sessionState, setSessionState] = useState(() => props.session.getRuntimeState());

const shutdownSession = () => {
props.session.shutdown(RuntimeExitReason.Shutdown);
Expand Down Expand Up @@ -59,7 +59,7 @@ export const RuntimeSessionCard = (props: runtimeSessionCardProps) => {
setSessionState(state);
}));
return () => disposableStore.dispose();
});
}, []);

return (
<tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import { localize } from '../../../../nls.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { ConfigurationScope, Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { positronConfigurationNodeBase } from '../../languageRuntime/common/languageRuntime.js';

// Key for the multiple sessions setting
export const USE_POSITRON_MULTIPLE_CONSOLE_SESSIONS_CONFIG_KEY =
'positron.multipleConsoleSessions';

/**
* Retrieves the value of the configuration setting that determines whether to enable
* multiple sessions feature.
* @param configurationService The configuration service
* @returns Whether to enablet the multiple sessions feature
*/
export function multipleConsoleSessionsFeatureEnabled(
configurationService: IConfigurationService
) {
return Boolean(
configurationService.getValue(USE_POSITRON_MULTIPLE_CONSOLE_SESSIONS_CONFIG_KEY)
);
}

// Register the configuration setting
const configurationRegistry = Registry.as<IConfigurationRegistry>(
Extensions.Configuration
);
configurationRegistry.registerConfiguration({
...positronConfigurationNodeBase,
scope: ConfigurationScope.MACHINE_OVERRIDABLE,
properties: {
[USE_POSITRON_MULTIPLE_CONSOLE_SESSIONS_CONFIG_KEY]: {
type: 'boolean',
default: false,
markdownDescription: localize(
'positron.enableMultipleConsoleSessionsFeature',
'**CAUTION**: Enable experimental Positron multiple console sessions features which may result in unexpected behaviour. Please restart Positron if you change this option.'
),
},
},
});

0 comments on commit 97ebe00

Please sign in to comment.