Skip to content

Commit

Permalink
Restore last connection date times
Browse files Browse the repository at this point in the history
  • Loading branch information
DonJayamanne committed Aug 16, 2023
1 parent 341812a commit afcb622
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 67 deletions.
8 changes: 8 additions & 0 deletions src/api.internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,21 @@ declare module './api' {
* If this is the only quick pick item in the list and this is true, then this item will be selected by default.
*/
default?: boolean;
/**
* The Jupyter Server command associated with this quick pick item.
*/
command?: JupyterServerCommand;
})[]
>
| (QuickPickItem & {
/**
* If this is the only quick pick item in the list and this is true, then this item will be selected by default.
*/
default?: boolean;
/**
* The Jupyter Server command associated with this quick pick item.
*/
command?: JupyterServerCommand;
})[];
}
}
3 changes: 3 additions & 0 deletions src/api.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ declare module './api' {
*/
default?: boolean;
})[];
/**
* @param item The original quick Pick returned by getQuickPickEntryItems will be passed into this method.
*/
handleQuickPick?(item: QuickPickItem, backEnabled: boolean): Promise<string | 'back' | undefined>;
/**
* Given the handle, returns the Jupyter Server information.
Expand Down
21 changes: 16 additions & 5 deletions src/kernels/jupyter/connection/jupyterServerProviderRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ class JupyterUriProviderAdaptor extends Disposables implements IJupyterUriProvid
);
}
}
async getQuickPickEntryItems(value?: string): Promise<(QuickPickItem & { default?: boolean | undefined })[]> {
async getQuickPickEntryItems(
value?: string
): Promise<(QuickPickItem & { default?: boolean | undefined; command?: JupyterServerCommand })[]> {
if (!this.provider.commandProvider) {
throw new Error(`No Jupyter Server Command Provider for ${this.provider.extensionId}#${this.provider.id}`);
}
Expand All @@ -116,8 +118,10 @@ class JupyterUriProviderAdaptor extends Disposables implements IJupyterUriProvid
return items.map((c) => {
return {
label: c.title,
detail: c.detail,
tooltip: c.tooltip,
default: c.picked === true
default: c.picked === true,
command: c
};
});
} catch (ex) {
Expand All @@ -130,14 +134,21 @@ class JupyterUriProviderAdaptor extends Disposables implements IJupyterUriProvid
token.dispose();
}
}
async handleQuickPick(item: QuickPickItem, _backEnabled: boolean): Promise<string | undefined> {
async handleQuickPick(
item: QuickPickItem & { command?: JupyterServerCommand },
_backEnabled: boolean
): Promise<string | undefined> {
if (!this.provider.commandProvider) {
throw new Error(`No Jupyter Server Command Provider for ${this.provider.extensionId}#${this.provider.id}`);
}
const token = new CancellationTokenSource();
try {
const items = await this.provider.commandProvider.getCommands('', token.token);
const command = items.find((c) => c.title === item.label) || this.commands.get(item.label);
let command: JupyterServerCommand | undefined =
'command' in item ? (item.command as JupyterServerCommand) : undefined;
if (!command) {
const items = await this.provider.commandProvider.getCommands('', token.token);
command = items.find((c) => c.title === item.label) || this.commands.get(item.label);
}
if (!command) {
throw new Error(
`Jupyter Server Command ${item.label} not found in Command Provider ${this.provider.extensionId}#${this.provider.id}`
Expand Down
13 changes: 7 additions & 6 deletions src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from '../types';
import { sendTelemetryEvent } from '../../../telemetry';
import { traceError, traceVerbose } from '../../../platform/logging';
import { IJupyterServerUri, IJupyterUriProvider } from '../../../api';
import { IJupyterServerUri, IJupyterUriProvider, JupyterServerCommand } from '../../../api';
import { Disposables } from '../../../platform/common/utils';
import { IServiceContainer } from '../../../platform/ioc/types';
import { IExtensionSyncActivationService } from '../../../platform/activation/types';
Expand Down Expand Up @@ -252,14 +252,15 @@ class JupyterUriProviderWrapper extends Disposables implements IInternalJupyterU
};
});
}
public async handleQuickPick(item: QuickPickItem, back: boolean): Promise<string | 'back' | undefined> {
public async handleQuickPick(
item: QuickPickItem & { original: QuickPickItem & { command?: JupyterServerCommand } },
back: boolean
): Promise<string | 'back' | undefined> {
if (!this.provider.handleQuickPick) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((item as any).original) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return this.provider.handleQuickPick((item as any).original, back);
if ('original' in item && item.original) {
return this.provider.handleQuickPick(item.original, back);
}
return this.provider.handleQuickPick(item, back);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { IKernelFinder, KernelConnectionMetadata, RemoteKernelConnectionMetadata
import { IApplicationShell } from '../../../platform/common/application/types';
import { InteractiveWindowView, JVSC_EXTENSION_ID, JupyterNotebookView } from '../../../platform/common/constants';
import { disposeAllDisposables } from '../../../platform/common/helpers';
import { IDisposable, IFeaturesManager } from '../../../platform/common/types';
import { IDisposable } from '../../../platform/common/types';
import { Common, DataScience } from '../../../platform/common/utils/localize';
import {
IMultiStepInput,
Expand Down Expand Up @@ -80,8 +80,6 @@ export class RemoteNotebookKernelSourceSelector implements IRemoteNotebookKernel
@inject(JupyterServerSelector) private readonly serverSelector: JupyterServerSelector,
@inject(JupyterConnection) private readonly jupyterConnection: JupyterConnection,
@inject(IConnectionDisplayDataProvider) private readonly displayDataProvider: IConnectionDisplayDataProvider,
@inject(IFeaturesManager)
private readonly features: IFeaturesManager,
@inject(IRemoteKernelFinderController)
private readonly kernelFinderController: IRemoteKernelFinderController,
@inject(IJupyterUriProviderRegistration)
Expand Down Expand Up @@ -144,42 +142,52 @@ export class RemoteNotebookKernelSourceSelector implements IRemoteNotebookKernel
| QuickPickItem
)[] = [];

const displayLastUsedTime = !this.features.features.enableProposedJupyterServerProviderApi;
const serversAndTimes: { server: IRemoteKernelFinder; time?: number }[] = [];
await Promise.all(
servers
.filter((s) => s.serverProviderHandle.id === provider.id)
.map(async (server) => {
// remote server
const lastUsedTime = displayLastUsedTime
? (await this.serverUriStorage.getAll()).find(
(item) =>
generateIdFromRemoteProvider(item.provider) ===
generateIdFromRemoteProvider(server.serverProviderHandle)
)
: undefined;
if ((token.isCancellationRequested || !lastUsedTime) && displayLastUsedTime) {
const lastUsedTime = (await this.serverUriStorage.getAll()).find(
(item) =>
generateIdFromRemoteProvider(item.provider) ===
generateIdFromRemoteProvider(server.serverProviderHandle)
);
if (token.isCancellationRequested) {
return;
}
quickPickServerItems.push({
type: KernelFinderEntityQuickPickType.KernelFinder,
kernelFinderInfo: server,
idAndHandle: server.serverProviderHandle,
label: server.displayName,
detail:
displayLastUsedTime && lastUsedTime?.time
? DataScience.jupyterSelectURIMRUDetail(new Date(lastUsedTime.time))
: undefined,
buttons: provider.removeHandle
? [
{
iconPath: new ThemeIcon('trash'),
tooltip: DataScience.removeRemoteJupyterServerEntryInQuickPick
}
]
: []
});
serversAndTimes.push({ server, time: lastUsedTime?.time });
})
);
serversAndTimes.sort((a, b) => {
if (!a.time && !b.time) {
return a.server.displayName.localeCompare(b.server.displayName);
}
if (!a.time && b.time) {
return 1;
}
if (a.time && !b.time) {
return -1;
}
return (a.time || 0) > (b.time || 0) ? -1 : 1;
});
serversAndTimes.forEach(({ server, time }) => {
quickPickServerItems.push({
type: KernelFinderEntityQuickPickType.KernelFinder,
kernelFinderInfo: server,
idAndHandle: server.serverProviderHandle,
label: server.displayName,
detail: time ? DataScience.jupyterSelectURIMRUDetail(new Date(time)) : undefined,
buttons: provider.removeHandle
? [
{
iconPath: new ThemeIcon('close'),
tooltip: DataScience.removeRemoteJupyterServerEntryInQuickPick
}
]
: []
});
});

if (provider.getQuickPickEntryItems && provider.handleQuickPick) {
if (quickPickServerItems.length > 0) {
Expand Down
11 changes: 2 additions & 9 deletions src/platform/common/featureManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ const deprecatedFeatures: DeprecatedFeatureInfo[] = [
export class FeatureManager implements IFeaturesManager {
private _onDidChangeFeatures = new Emitter<void>();
readonly onDidChangeFeatures = this._onDidChangeFeatures.event;
private _features: IFeatureSet = {
enableProposedJupyterServerProviderApi: false
};
private _features: IFeatureSet = {};
get features(): IFeatureSet {
return this._features;
}
Expand All @@ -74,12 +72,7 @@ export class FeatureManager implements IFeaturesManager {
}

private _updateFeatures() {
const enabled = this.workspace
.getConfiguration('jupyter')
.get<boolean>('enableProposedJupyterServerProviderApi', false);
this.features = {
enableProposedJupyterServerProviderApi: enabled === true
};
this.features = {};
}

public dispose() {
Expand Down
5 changes: 1 addition & 4 deletions src/platform/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export interface IJupyterSettings {
readonly experiments: IExperiments;
readonly logging: ILoggingSettings;
readonly allowUnauthorizedRemoteConnection: boolean;
readonly enableProposedJupyterServerProviderApi?: boolean;
readonly jupyterInterruptTimeout: number;
readonly jupyterLaunchTimeout: number;
readonly jupyterLaunchRetries: number;
Expand Down Expand Up @@ -258,9 +257,7 @@ export type DeprecatedFeatureInfo = {
setting?: DeprecatedSettingAndValue;
};

export interface IFeatureSet {
enableProposedJupyterServerProviderApi: boolean;
}
export interface IFeatureSet {}

export const IFeaturesManager = Symbol('IFeaturesManager');

Expand Down
40 changes: 27 additions & 13 deletions src/standalone/userJupyterServer/userServerUrlProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export class UserJupyterServerUrlProvider
private secureConnectionValidator: SecureConnectionValidator;
private jupyterServerUriInput: UserJupyterServerUriInput;
private jupyterServerUriDisplayName: UserJupyterServerDisplayName;
private lastEnteredUrl?: string;
constructor(
@inject(IClipboard) clipboard: IClipboard,
@inject(IApplicationShell) applicationShell: IApplicationShell,
Expand Down Expand Up @@ -170,26 +169,27 @@ export class UserJupyterServerUrlProvider
return {
baseUrl: Uri.parse(serverInfo.baseUrl),
token: serverInfo.token,
authorizationHeader: serverInfo.authorizationHeader,
headers: serverInfo.authorizationHeader,
mappedRemoteNotebookDir: serverInfo.mappedRemoteNotebookDir
? Uri.file(serverInfo.mappedRemoteNotebookDir)
: undefined,
webSocketProtocols: serverInfo.webSocketProtocols
};
}
public async handleCommand(
_command: JupyterServerCommand,
command: JupyterServerCommand & { url?: string },
_token: CancellationToken
): Promise<void | JupyterServer | 'back' | undefined> {
const token = new CancellationTokenSource();
this.disposables.push(token);
this.disposables.push(new Disposable(() => token.cancel()));
try {
const handleOrBack = await this.captureRemoteJupyterUrl(token.token, this.lastEnteredUrl);
if (!handleOrBack) {
const url = 'url' in command ? command.url : undefined;
const handleOrBack = await this.captureRemoteJupyterUrl(token.token, url);
if (!handleOrBack || handleOrBack === InputFlowAction.cancel) {
return;
}
if (handleOrBack === 'back') {
if (handleOrBack && handleOrBack instanceof InputFlowAction) {
return 'back';
}
const servers = await this.getJupyterServers(token.token);
Expand All @@ -210,22 +210,23 @@ export class UserJupyterServerUrlProvider
* @param value Value entered by the user in the quick pick
*/
async getCommands(value: string, _token: CancellationToken): Promise<JupyterServerCommand[]> {
this.lastEnteredUrl = undefined;
let url = '';
try {
value = (value || '').trim();
if (['http:', 'https:'].includes(new URL(value.trim()).protocol.toLowerCase())) {
this.lastEnteredUrl = value;
url = value;
}
} catch {
//
}
if (this.lastEnteredUrl) {
return [{ title: DataScience.connectToToTheJupyterServer(this.lastEnteredUrl) }];
if (url) {
const title = DataScience.connectToToTheJupyterServer(url);
return [{ title, url } as JupyterServerCommand];
}
return [
{
title: DataScience.jupyterSelectURIPrompt,
tooltip: DataScience.jupyterSelectURINewDetail,
detail: DataScience.jupyterSelectURINewDetail,
picked: true
}
];
Expand Down Expand Up @@ -368,6 +369,7 @@ export class UserJupyterServerUrlProvider
let nextStep: Steps = 'Get Url';
let previousStep: Steps | undefined = 'Get Url';
let url = initialUrl;
let initialUrlWasValid = false;
if (initialUrl) {
// Validate the URI first, which would otherwise be validated when user enters the Uri into the input box.
const initialVerification = this.jupyterServerUriInput.parseUserUriAndGetValidationError(initialUrl);
Expand All @@ -376,6 +378,7 @@ export class UserJupyterServerUrlProvider
validationErrorMessage = initialVerification.validationError;
nextStep = 'Get Url';
} else {
initialUrlWasValid = true;
jupyterServerUri = initialVerification.jupyterServerUri;
nextStep = 'Check Passwords';
}
Expand All @@ -385,12 +388,13 @@ export class UserJupyterServerUrlProvider
try {
handle = uuid();
if (nextStep === 'Get Url') {
initialUrlWasValid = false;
nextStep = 'Check Passwords';
previousStep = undefined;
const errorMessage = validationErrorMessage;
validationErrorMessage = ''; // Never display this validation message again.
const result = await this.jupyterServerUriInput.getUrlFromUser(
initialUrl,
url || initialUrl,
errorMessage,
disposables
);
Expand All @@ -409,7 +413,8 @@ export class UserJupyterServerUrlProvider
const passwordDisposables: Disposable[] = [];
if (nextStep === 'Check Passwords') {
nextStep = 'Check Insecure Connections';
previousStep = 'Get Url';
// If we were given a Url, then back should get out of this flow.
previousStep = initialUrlWasValid && initialUrl ? undefined : 'Get Url';

try {
const errorMessage = validationErrorMessage;
Expand Down Expand Up @@ -468,6 +473,10 @@ export class UserJupyterServerUrlProvider
nextStep = 'Verify Connection';
previousStep =
requiresPassword && jupyterServerUri.token.length === 0 ? 'Check Passwords' : 'Get Url';
if (previousStep === 'Get Url') {
// If we were given a Url, then back should get out of this flow.
previousStep = initialUrlWasValid && initialUrl ? undefined : 'Get Url';
}
if (
!requiresPassword &&
jupyterServerUri.token.length === 0 &&
Expand Down Expand Up @@ -545,6 +554,11 @@ export class UserJupyterServerUrlProvider
: requiresPassword && jupyterServerUri.token.length === 0
? 'Check Passwords'
: 'Get Url';
if (previousStep === 'Get Url') {
// If we were given a Url, then back should get out of this flow.
previousStep = initialUrlWasValid && initialUrl ? undefined : 'Get Url';
}

jupyterServerUri.displayName = await this.jupyterServerUriDisplayName.getDisplayName(
handle,
jupyterServerUri.displayName || new URL(jupyterServerUri.baseUrl).hostname
Expand Down

0 comments on commit afcb622

Please sign in to comment.