Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: JS API reconnect #1149

Merged
merged 7 commits into from
Mar 29, 2023
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
134 changes: 127 additions & 7 deletions __mocks__/dh-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,20 @@ class DeephavenObject {
callbacks[i](event);
}
}

nextEvent(name, timeout) {
let cleanup;
return new Promise((resolve, reject) => {
cleanup = this.addEventListener(name, detail => {
resolve(detail);
cleanup();
});
});
}

hasListeners(name) {
return (this._listeners[name]?.length ?? 0) > 0;
}
}

class Sort {
Expand Down Expand Up @@ -615,6 +629,11 @@ class Table extends DeephavenObject {
size = ROW_COUNT,
suppressFilter = false,
customColumns = [],
description = 'Mock Table',
layoutHints = null,
hasInputTable = false,
pluginName = null,
totalsTableConfig = {},
} = {}) {
super({ sort, filter, columns, size });

Expand All @@ -623,6 +642,7 @@ class Table extends DeephavenObject {
this.columns = columns;
this.customColumns = customColumns;
this.size = size;
this.totalSize = size;
this.suppressFilter = suppressFilter;

this.startRow = 0;
Expand All @@ -631,6 +651,11 @@ class Table extends DeephavenObject {
this.pendingViewportUpdate = false;
this.isClosed = false;
this.isUncoalesced = false;
this.description = description;
this.layoutHints = layoutHints;
this.hasInputTable = hasInputTable;
this.pluginName = pluginName;
this.totalsTableConfig = totalsTableConfig;
}

close() {}
Expand Down Expand Up @@ -665,7 +690,7 @@ class Table extends DeephavenObject {
const { startRow, endRow, viewportColumns, size, filter } = this;

if (startRow == null || endRow == null || viewportColumns == null) {
return null;
throw new Error('Viewport not set');
}

let rows = [];
Expand All @@ -686,13 +711,17 @@ class Table extends DeephavenObject {
return viewportData;
}

findColumn(name) {
const column = this.columns.find(col => col.name === name);
if (column === undefined) {
throw new Error(`Column ${name} not found`);
}
return column;
}

findColumns(names) {
return names.map(name => {
const column = this.columns.find(col => col.name === name);
if (column === undefined) {
throw new Error(`Column ${name} not found`);
}
return column;
return this.findColumn(name);
});
}

Expand Down Expand Up @@ -744,6 +773,10 @@ class Table extends DeephavenObject {
});
}

getGrandTotalsTable() {
return this.getTotalsTable();
}

selectDistinct() {
return new Promise((resolve, reject) => {
const table = makeDummyTable();
Expand All @@ -758,10 +791,46 @@ class Table extends DeephavenObject {
});
}

reverse() {
return this.copy();
}

rollup() {
return this.copy();
}

treeTable() {
return this.copy();
}

inputTable() {
return Promise.resolve(new InputTable());
}

freeze() {
return this.copy();
}

snapshot() {
return this.copy();
}

join() {
return this.copy();
}

byExternal() {
return this.copy();
}

seekRow() {
return Promise.resolve(-1);
}

getColumnStatistics() {
return Promise.reject(new Error('Column statistics not implemented'));
}

subscribe(columns, updateInterval = UPDATE_INTERVAL) {
return new TableSubscription({
table: this,
Expand All @@ -783,6 +852,8 @@ Table.EVENT_UPDATED = 'updated';
Table.EVENT_CONNECT = 'connect';
Table.EVENT_DISCONNECT = 'disconnect';
Table.EVENT_RECONNECT = 'reconnect';
Table.EVENT_RECONNECTFAILED = 'reconnectfailed';
Table.SIZE_UNCOALESCED = 'sizeuncoalesced';

class TableViewportSubscription extends DeephavenObject {
constructor({ table = makeDummyTable() } = {}) {
Expand Down Expand Up @@ -1267,16 +1338,44 @@ class IdeConnection extends DeephavenObject {
}
}

IdeConnection.EVENT_DISCONNECT = 'disconnect';
IdeConnection.EVENT_RECONNECT = 'reconnect';

class IdeSession extends DeephavenObject {
constructor(language) {
super();

this.language = language;
this.tables = [];
this.widgets = [];
this.logMessageCallbacks = [];
this.subscribeCallbacks = [];
}

onLogMessage(callback) {
this.logMessageCallbacks.push(callback);
return () => {
this.logMessageCallbacks = this.logMessageCallbacks.filter(
cb => cb !== callback
);
};
}

onLogMessage(callback) {}
subscribeToFieldUpdates(callback) {
this.subscribeCallbacks.push(callback);
return () => {
this.subscribeCallbacks = this.subscribeCallbacks.filter(
cb => cb !== callback
);
};
}

notifySubscribeCallbacks(changes) {
const callbacks = [...this.subscribeCallbacks];
callbacks.forEach(cb => {
cb(changes);
});
}

close() {}
/**
Expand Down Expand Up @@ -1374,6 +1473,8 @@ class IdeSession extends DeephavenObject {
}

timer = setTimeout(() => {
this.notifySubscribeCallbacks(tableChanges);
this.notifySubscribeCallbacks(widgetChanges);
resolve(result);
}, delay);
});
Expand Down Expand Up @@ -1413,6 +1514,23 @@ class IdeSession extends DeephavenObject {
});
}

getTreeTable(name) {
// We're just going to use a regular table for TreeTable
// Actual impl is different, but should be fine for mock
this.getTable(name);
}

getObject(variableDefinition) {
switch (variableDefinition.type) {
case dh.VariableType.FIGURE:
return this.getFigure(variableDefinition.title);
case dh.VariableType.TreeTable:
return this.getTreeTable(variableDefinition.title);
default:
return this.getTable(variableDefinition.title);
}
}

openDocument() {}
changeDocument() {}
getCompletionItems() {
Expand Down Expand Up @@ -1864,6 +1982,8 @@ const dh = {
TotalsTableConfig: TotalsTableConfig,
TableViewportSubscription,
TableSubscription,
// TreeTable and Table are different in actual implementation, but should be okay for the mock
TreeTable: Table,
Column: Column,
RangeSet,
Row: Row,
Expand Down
22 changes: 13 additions & 9 deletions packages/code-studio/src/main/AppInit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,10 @@ function AppInit(props: AppInitProps) {
setServerConfigValues,
} = props;

// General error means the app is dead and is unlikely to recover
const [error, setError] = useState<unknown>();
// Disconnect error may be temporary, so just show an error overlaid on the app
const [disconnectError, setDisconnectError] = useState<unknown>();
const [isFontLoading, setIsFontLoading] = useState(true);

const initClient = useCallback(async () => {
Expand All @@ -175,14 +178,12 @@ function AppInit(props: AppInitProps) {
? // Fall back to the old API for anonymous auth if the new API is not supported
createConnection()
: coreClient.getAsIdeConnection());
connection.addEventListener(
dh.IdeConnection.HACK_CONNECTION_FAILURE,
event => {
const { detail } = event;
log.error('Connection failure', `${JSON.stringify(detail)}`);
setError(`Unable to connect: ${detail.details ?? 'Unknown Error'}`);
}
);
connection.addEventListener(dh.IdeConnection.EVENT_SHUTDOWN, event => {
const { detail } = event;
log.info('Shutdown', `${JSON.stringify(detail)}`);
setError(`Server shutdown: ${detail ?? 'Unknown reason'}`);
setDisconnectError(null);
});

const sessionWrapper = await loadSessionWrapper(connection);
const name = 'user';
Expand Down Expand Up @@ -318,7 +319,10 @@ function AppInit(props: AppInitProps) {

const isLoading = (workspace == null && error == null) || isFontLoading;
const isLoaded = !isLoading && error == null;
const errorMessage = error != null ? `${error}` : null;
const errorMessage =
error != null || disconnectError != null
? `${error ?? disconnectError}`
: null;

return (
<>
Expand Down
Loading