in gui/frontend/src/script-execution/PresentationInterface.tsx [438:704]
public async addResultData(data: IExecutionResult, dataOptions: IResponseDataOptions,
presentationOptions?: IPresentationOptions, queryType?: QueryType): Promise<void> {
// If this is the first result we receive, switch to the loading state.
const isBusy = this.loadingState === LoadingState.Loading || this.loadingState === LoadingState.Waiting;
if (this.waitTimer || isBusy) {
if (this.waitTimer) {
clearTimeout(this.waitTimer);
this.waitTimer = null;
}
// Execution is finished if either we have execution info (and no result data so far) or we got
// a chat result with an answer.
if (!this.resultData && "executionInfo" in data) {
this.changeLoadingState(LoadingState.Idle);
} else if (data.type === "chat" && "answer" in data && data.answer !== "") {
this.changeLoadingState(LoadingState.Idle);
} else if (data.type !== "chat") {
this.changeLoadingState(LoadingState.Loading);
}
}
switch (data.type) {
case "text": {
if (!this.resultData) {
// No data yet. Take the given one unchanged.
this.resultData = data;
} else if (this.resultData.type === "resultSets") {
// There's result set data shown currently. In this case add the text to the output tab.
// If no output tab exists already, create it.
if (!this.resultData.output) {
this.resultData.output = [];
}
if (data.text) {
this.resultData.output.push(...data.text);
}
} else if (this.resultData.type === "graphData") {
// TODO: If graph data is visible, add the text as another entry to the graph page.
} else if (this.resultData.type === "chat") {
// TODO: If a chat result is visible, add the text as another entry to the page.
} else if (this.resultData.type === "about") {
// TODO: If a about result is visible, add the text as another entry to the page.
} else {
// Text data to render.
if (!this.resultData.text) {
this.resultData.text = data.text;
} else if (data.text) {
// If the last entry has the same language as the new data, then merge them.
const lastEntry = this.resultData.text[this.resultData.text.length - 1];
while (data.text.length > 0 && data.text[0].index === lastEntry.index
&& data.text[0].language === lastEntry?.language) {
lastEntry.content += data.text[0].content;
data.text.shift();
}
// Add the remaining text data.
this.resultData.text.push(...data.text);
if (data.executionInfo) {
this.resultData.executionInfo = data.executionInfo;
// We got execution info, so we can return into idle state.
this.changeLoadingState(LoadingState.Idle);
}
}
}
break;
}
case "resultSetRows": {
if (!this.resultData) {
if (data.rows.length === 0 && queryType !== QueryType.Select) {
this.addEmptyResultSetAsText(data, dataOptions);
} else {
this.resultData = {
type: "resultSets",
sets: [{
type: "resultSet",
index: dataOptions.index,
subIndex: dataOptions.subIndex,
resultId: dataOptions?.resultId,
sql: dataOptions.sql ?? "",
columns: data.columns ?? [],
data,
updatable: dataOptions.updatable ?? false,
fullTableName: dataOptions.fullTableName ?? "",
}],
};
}
} else {
let needUpdate = true;
switch (this.resultData.type) {
case "resultSets": {
// Find the target set (tab) to add the data to or create a new one.
const resultSets = this.resultData.sets;
// Add the data to our internal storage, to support switching tabs
// for multiple result sets.
let index = -1;
const resultSet = resultSets.find((candidate, candidateIndex) => {
if (candidate.resultId === dataOptions.resultId) {
index = candidateIndex;
return true;
}
return false;
});
if (resultSet) {
// No need for re-rendering if the result set already exists and
// we only add new rows.
if (data.executionInfo) {
resultSet.data.executionInfo = data.executionInfo;
} else {
needUpdate = false;
}
// Internal storage to support result tab switching.
if (dataOptions.replaceData) {
resultSet.data.rows = data.rows;
} else {
resultSet.data.rows.push(...data.rows);
}
// Also update columns if they were given.
if (data.columns && data.columns.length > 0) {
resultSet.columns = data.columns;
}
resultSet.data.currentPage = data.currentPage;
resultSet.data.hasMoreRows = data.hasMoreRows;
if (this.currentSet === index + 1) {
// Tab is visible, so send it the new data.
if (data.columns && data.columns.length > 0) {
await this.resultRef.current?.updateColumns(dataOptions.resultId,
data.columns);
}
await this.resultRef.current?.addData(data, dataOptions.resultId,
dataOptions.replaceData);
}
} else {
if (data.rows.length === 0 && queryType !== QueryType.Select) {
this.addEmptyResultSetAsText(data, dataOptions);
} else {
// No existing result set tab found - create it.
this.resultData.sets.push({
type: "resultSet",
index: dataOptions.index,
subIndex: dataOptions.subIndex,
resultId: dataOptions?.resultId,
sql: dataOptions.sql ?? "",
columns: data.columns ?? [],
data,
updatable: dataOptions.updatable ?? false,
fullTableName: dataOptions.fullTableName ?? "",
});
}
}
break;
}
case "text": {
if (data.rows.length === 0) {
this.addEmptyResultSetAsText(data, dataOptions);
} else {
// Move the text over to the output tab of the new result set data.
this.resultData = {
type: "resultSets",
sets: [{
type: "resultSet",
index: dataOptions.index,
resultId: dataOptions?.resultId,
sql: dataOptions.sql ?? "",
columns: data.columns ?? [],
data,
updatable: dataOptions.updatable ?? false,
fullTableName: dataOptions.fullTableName ?? "",
}],
output: this.resultData.text,
};
}
break;
}
default: {
// Replace the existing data entirely.
this.resultData = {
type: "resultSets",
sets: [{
type: "resultSet",
index: dataOptions.index,
resultId: dataOptions?.resultId,
sql: dataOptions.sql ?? "",
columns: data.columns ?? [],
data,
updatable: dataOptions.updatable ?? false,
fullTableName: dataOptions.fullTableName ?? "",
}],
};
break;
}
}
// Everything but result sets is finished implicitly when the result comes in.
let allFinished = true;
if (this.resultData.type === "resultSets") {
// Result sets may still be waiting for more row data.
allFinished = this.resultData.sets.every((value) => {
return value.data.executionInfo != null;
});
}
if (allFinished && this.loadingState !== LoadingState.Idle) {
this.changeLoadingState(LoadingState.Idle);
}
if (!needUpdate) {
return;
}
}
break;
}
case "graphData": {
// New graph always replaces what was there before.
this.resultData = data;
this.minHeight = 200;
break;
}
case "chat": {
// Chat results display
this.resultData = data;
this.minHeight = 200;
if (data.answer !== "") {
// We got the whole answer, so we can stop waiting.
this.changeLoadingState(LoadingState.Idle);
}
break;
}
case "about": {
// About results display
this.resultData = data;
this.minHeight = 220;
break;
}
default:
}
// The backend is only set when the presentation is active.
if (this.#backend) {
this.renderResults(presentationOptions);
}
}