mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-4285 read full value from text cell (#2288)
* CB-4494 api for reading full value from text cell * CB-4494 apply function fix * CB-4285 graphql getFileFullText for files * CB-4285 textValuePresentation refactor * CB-4285 textValuePresentation refactor - removed unneeded fields * CB-4285 textValuePresentation canSave in data ref * CB-4285 paste full text button * CB-4285 observable map for full text in textValuePresentation * CB-4285 reset cases comment removed (all works cool) * СB-4285 buttons in tools container is now nearby each other * CB-4285 use content action for caching full texts * CB-3842 use only one row in getting full value * CB-3842 use not null at read lob value * CB-4285 schema changes for sqlReadString and sqlReadLobValue * CB-4285 TextValuePresentation refactor * CB-4285 useTextValue refactor * CB-4285 PR comments applied * CB-4285 view full text button for ValuePanel + remove truncated warning if loaded full text --------- Co-authored-by: s.teleshev <s.teleshev@mu.se> Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
s.teleshev
Evgenia Bezborodova
parent
ea0493883a
commit
5db30be61b
@@ -331,7 +331,8 @@ extend type Mutation {
|
||||
addedRows: [ SQLResultRow! ],
|
||||
): String!
|
||||
|
||||
#Return BLOB name
|
||||
# Returns BLOB name
|
||||
@deprecated(reason: "23.3.3") # use sqlReadLobValue
|
||||
readLobValue(
|
||||
projectId: ID,
|
||||
connectionId: ID!,
|
||||
@@ -341,6 +342,27 @@ extend type Mutation {
|
||||
row: [ SQLResultRow! ]!
|
||||
): String!
|
||||
|
||||
@since(version: "23.3.3")
|
||||
sqlReadLobValue(
|
||||
projectId: ID,
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
resultsId: ID!,
|
||||
lobColumnIndex: Int!,
|
||||
row: SQLResultRow!
|
||||
): String!
|
||||
|
||||
# Returns full string value ignoring any limits
|
||||
@since(version: "23.3.3")
|
||||
sqlReadStringValue(
|
||||
projectId: ID,
|
||||
connectionId: ID!,
|
||||
contextId: ID!,
|
||||
resultsId: ID!,
|
||||
columnIndex: Int!,
|
||||
row: SQLResultRow!
|
||||
): String!
|
||||
|
||||
# Returns SQLExecuteInfo
|
||||
asyncSqlExecuteResults(taskId: ID!): SQLExecuteInfo !
|
||||
|
||||
|
||||
+12
-4
@@ -119,10 +119,18 @@ public interface DBWServiceSQL extends DBWService {
|
||||
|
||||
@WebAction
|
||||
String readLobValue(
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull Integer lobColumnIndex,
|
||||
@Nullable List<WebSQLResultsRow> row) throws DBWebException;
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull Integer lobColumnIndex,
|
||||
@NotNull WebSQLResultsRow row) throws DBWebException;
|
||||
|
||||
@NotNull
|
||||
@WebAction
|
||||
String getCellValue(
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull Integer lobColumnIndex,
|
||||
@NotNull WebSQLResultsRow row) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
String updateResultsDataBatchScript(
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2024 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.service.sql;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeBindingMeta;
|
||||
import org.jkiss.dbeaver.model.data.DBDContent;
|
||||
import org.jkiss.dbeaver.model.data.DBDDataReceiver;
|
||||
import org.jkiss.dbeaver.model.data.DBDValue;
|
||||
import org.jkiss.dbeaver.model.exec.*;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
|
||||
import org.jkiss.dbeaver.utils.ContentUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class WebSQLCellValueReceiver implements DBDDataReceiver {
|
||||
protected final DBSDataContainer dataContainer;
|
||||
protected int rowIndex;
|
||||
protected DBDAttributeBindingMeta binding;
|
||||
protected Object value;
|
||||
|
||||
public WebSQLCellValueReceiver(DBSDataContainer dataContainer, int rowIndex) {
|
||||
this.dataContainer = dataContainer;
|
||||
this.rowIndex = rowIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchStart(DBCSession session, DBCResultSet resultSet, long offset, long maxRows) throws DBCException {
|
||||
DBCResultSetMetaData meta = resultSet.getMeta();
|
||||
List<DBCAttributeMetaData> attributes = meta.getAttributes();
|
||||
DBCAttributeMetaData attrMeta = attributes.get(rowIndex);
|
||||
binding = new DBDAttributeBindingMeta(dataContainer, resultSet.getSession(), attrMeta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchRow(DBCSession session, DBCResultSet resultSet) throws DBCException {
|
||||
value = binding.getValueHandler().fetchValueObject(
|
||||
resultSet.getSession(),
|
||||
resultSet,
|
||||
binding.getMetaAttribute(),
|
||||
rowIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchEnd(DBCSession session, DBCResultSet resultSet) throws DBCException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public byte[] getBinaryValue(DBRProgressMonitor monitor) throws DBCException {
|
||||
byte[] binaryValue;
|
||||
if (value instanceof DBDContent dbdContent) {
|
||||
binaryValue = ContentUtils.getContentBinaryValue(monitor, dbdContent);
|
||||
} else if (value instanceof DBDValue dbdValue) {
|
||||
binaryValue = dbdValue.getRawValue().toString().getBytes();
|
||||
} else {
|
||||
binaryValue = value.toString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
if (binaryValue == null) {
|
||||
throw new DBCException("Lob value is null");
|
||||
}
|
||||
return binaryValue;
|
||||
}
|
||||
}
|
||||
+10
-56
@@ -20,39 +20,29 @@ import io.cloudbeaver.server.CBApplication;
|
||||
import io.cloudbeaver.server.CBConstants;
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.data.*;
|
||||
import org.jkiss.dbeaver.model.exec.*;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.sql.DBQuotaException;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
|
||||
import org.jkiss.dbeaver.utils.ContentUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class WebSQLDataLOBReceiver implements DBDDataReceiver {
|
||||
public class WebSQLDataLOBReceiver extends WebSQLCellValueReceiver {
|
||||
private static final Log log = Log.getLog(WebSQLDataLOBReceiver.class);
|
||||
public static final Path DATA_EXPORT_FOLDER = CBPlatform.getInstance().getTempFolder(new VoidProgressMonitor(), "sql-lob-files");
|
||||
|
||||
private final String tableName;
|
||||
private final DBSDataContainer dataContainer;
|
||||
|
||||
private DBDAttributeBinding binding;
|
||||
private Object lobValue;
|
||||
private int rowIndex;
|
||||
|
||||
WebSQLDataLOBReceiver(String tableName, DBSDataContainer dataContainer, int rowIndex) {
|
||||
super(dataContainer, rowIndex);
|
||||
this.tableName = tableName;
|
||||
this.dataContainer = dataContainer;
|
||||
this.rowIndex = rowIndex;
|
||||
if (!Files.exists(DATA_EXPORT_FOLDER)){
|
||||
if (!Files.exists(DATA_EXPORT_FOLDER)) {
|
||||
try {
|
||||
Files.createDirectories(DATA_EXPORT_FOLDER);
|
||||
} catch (IOException e) {
|
||||
@@ -62,64 +52,28 @@ public class WebSQLDataLOBReceiver implements DBDDataReceiver {
|
||||
|
||||
}
|
||||
|
||||
public String createLobFile(DBCSession session) throws DBCException, IOException {
|
||||
public String createLobFile(DBRProgressMonitor monitor) throws DBCException, IOException {
|
||||
String exportFileName = CommonUtils.truncateString(tableName, 32);
|
||||
StringBuilder fileName = new StringBuilder(exportFileName);
|
||||
fileName.append("_")
|
||||
.append(binding.getName())
|
||||
.append("_");
|
||||
.append(binding.getName())
|
||||
.append("_");
|
||||
Timestamp ts = new Timestamp(System.currentTimeMillis());
|
||||
String s = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(ts);
|
||||
fileName.append(s);
|
||||
exportFileName = CommonUtils.escapeFileName(fileName.toString());
|
||||
byte[] binaryValue;
|
||||
byte[] binaryValue = getBinaryValue(monitor);
|
||||
Number fileSizeLimit = CBApplication.getInstance().getAppConfiguration().getResourceQuota(CBConstants.QUOTA_PROP_FILE_LIMIT);
|
||||
if (lobValue instanceof DBDContent) {
|
||||
binaryValue = ContentUtils.getContentBinaryValue(session.getProgressMonitor(), (DBDContent) lobValue);
|
||||
} else if (lobValue instanceof DBDValue) {
|
||||
binaryValue = ((DBDValue) lobValue).getRawValue().toString().getBytes();
|
||||
} else {
|
||||
binaryValue = lobValue.toString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
if (binaryValue == null) {
|
||||
throw new DBCException("Lob value is null");
|
||||
}
|
||||
if (binaryValue.length > fileSizeLimit.longValue()) {
|
||||
throw new DBQuotaException(
|
||||
"Data export quota exceeded \n Please increase the resourceQuotas parameter in configuration",
|
||||
"Data export quota exceeded \n Please increase the resourceQuotas parameter in configuration",
|
||||
CBConstants.QUOTA_PROP_FILE_LIMIT, fileSizeLimit.longValue(), binaryValue.length
|
||||
);
|
||||
}
|
||||
Path file = DATA_EXPORT_FOLDER.resolve(exportFileName);
|
||||
Path file = WebSQLDataLOBReceiver.DATA_EXPORT_FOLDER.resolve(exportFileName);
|
||||
Files.write(file, binaryValue);
|
||||
return exportFileName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void fetchStart(DBCSession session, DBCResultSet resultSet, long offset, long maxRows) throws DBCException {
|
||||
DBCResultSetMetaData meta = resultSet.getMeta();
|
||||
List<DBCAttributeMetaData> attributes = meta.getAttributes();
|
||||
DBCAttributeMetaData attrMeta = attributes.get(rowIndex);
|
||||
binding = new DBDAttributeBindingMeta(dataContainer, resultSet.getSession(), attrMeta);
|
||||
}
|
||||
@Override
|
||||
public void fetchRow(DBCSession session, DBCResultSet resultSet) throws DBCException {
|
||||
lobValue = binding.getValueHandler().fetchValueObject(
|
||||
resultSet.getSession(),
|
||||
resultSet,
|
||||
binding.getMetaAttribute(),
|
||||
rowIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchEnd(DBCSession session, DBCResultSet resultSet) throws DBCException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+33
-8
@@ -54,6 +54,7 @@ import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -797,14 +798,27 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
|
||||
DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier();
|
||||
checkRowIdentifier(resultsInfo, rowIdentifier);
|
||||
String tableName = rowIdentifier.getEntity().getName();
|
||||
WebSQLDataLOBReceiver dataReceiver = new WebSQLDataLOBReceiver(tableName, resultsInfo.getDataContainer(), lobColumnIndex);
|
||||
readCellDataValue(monitor, resultsInfo, row, dataReceiver);
|
||||
try {
|
||||
return dataReceiver.createLobFile(monitor);
|
||||
} catch (Exception e) {
|
||||
throw new DBWebException("Error creating temporary lob file ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void readCellDataValue(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull WebSQLResultsInfo resultsInfo,
|
||||
@Nullable WebSQLResultsRow row,
|
||||
@NotNull WebSQLCellValueReceiver dataReceiver) throws DBException {
|
||||
DBSDataContainer dataContainer = resultsInfo.getDataContainer();
|
||||
DBCExecutionContext executionContext = getExecutionContext(dataContainer);
|
||||
String tableName = rowIdentifier.getEntity().getName();
|
||||
WebSQLDataLOBReceiver dataReceiver = new WebSQLDataLOBReceiver(tableName, dataContainer, lobColumnIndex);
|
||||
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Generate data update batches")) {
|
||||
WebExecutionSource executionSource = new WebExecutionSource(dataContainer, executionContext, this);
|
||||
DBDDataFilter dataFilter = new DBDDataFilter();
|
||||
DBDAttributeBinding[] keyAttributes = rowIdentifier.getAttributes().toArray(new DBDAttributeBinding[0]);
|
||||
DBDAttributeBinding[] keyAttributes = resultsInfo.getDefaultRowIdentifier().getAttributes().toArray(new DBDAttributeBinding[0]);
|
||||
Object[] rowValues = new Object[keyAttributes.length];
|
||||
List<DBDAttributeConstraint> constraints = new ArrayList<>();
|
||||
for (int i = 0; i < keyAttributes.length; i++) {
|
||||
@@ -833,14 +847,25 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
DBCStatistics statistics = dataContainer.readData(
|
||||
executionSource, session, dataReceiver, dataFilter,
|
||||
0, 1, DBSDataContainer.FLAG_NONE, 1);
|
||||
try {
|
||||
return dataReceiver.createLobFile(session);
|
||||
} catch (Exception e) {
|
||||
throw new DBWebException("Error creating temporary lob file ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String readStringValue(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull Integer columnIndex,
|
||||
@Nullable WebSQLResultsRow row
|
||||
) throws DBException {
|
||||
WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId);
|
||||
DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier();
|
||||
checkRowIdentifier(resultsInfo, rowIdentifier);
|
||||
WebSQLCellValueReceiver dataReceiver = new WebSQLCellValueReceiver(resultsInfo.getDataContainer(), columnIndex);
|
||||
readCellDataValue(monitor, resultsInfo, row, dataReceiver);
|
||||
return new String(dataReceiver.getBinaryValue(monitor), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Misc
|
||||
|
||||
|
||||
+18
-6
@@ -131,12 +131,24 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
|
||||
getSQLContext(env),
|
||||
env.getArgument("resultId"));
|
||||
})
|
||||
.dataFetcher("readLobValue", env ->
|
||||
getService(env).readLobValue(
|
||||
getSQLContext(env),
|
||||
env.getArgument("resultsId"),
|
||||
env.getArgument("lobColumnIndex"),
|
||||
getResultsRow(env, "row")))
|
||||
.dataFetcher("readLobValue", env -> // deprecated
|
||||
getService(env).readLobValue(
|
||||
getSQLContext(env),
|
||||
env.getArgument("resultsId"),
|
||||
env.getArgument("lobColumnIndex"),
|
||||
getResultsRow(env, "row").get(0)))
|
||||
.dataFetcher("sqlReadLobValue", env ->
|
||||
getService(env).readLobValue(
|
||||
getSQLContext(env),
|
||||
env.getArgument("resultsId"),
|
||||
env.getArgument("lobColumnIndex"),
|
||||
new WebSQLResultsRow(env.getArgument("row"))))
|
||||
.dataFetcher("sqlReadStringValue", env ->
|
||||
getService(env).getCellValue(
|
||||
getSQLContext(env),
|
||||
env.getArgument("resultsId"),
|
||||
env.getArgument("columnIndex"),
|
||||
new WebSQLResultsRow(env.getArgument("row"))))
|
||||
.dataFetcher("updateResultsDataBatch", env ->
|
||||
getService(env).updateResultsDataBatch(
|
||||
getSQLContext(env),
|
||||
|
||||
+45
-11
@@ -60,6 +60,9 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -319,31 +322,62 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowableFunction<T, R> {
|
||||
R apply(T obj) throws Exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readLobValue(
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull Integer lobColumnIndex,
|
||||
@Nullable List<WebSQLResultsRow> row) throws DBWebException
|
||||
@NotNull WebSQLResultsRow row) throws DBWebException
|
||||
{
|
||||
ThrowableFunction<DBRProgressMonitor, String> function = monitor -> contextInfo.getProcessor().readLobValue(
|
||||
monitor, contextInfo, resultsId, lobColumnIndex, row);
|
||||
return readValue(function, contextInfo.getProcessor());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCellValue(
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
@NotNull String resultsId,
|
||||
@NotNull Integer lobColumnIndex,
|
||||
@NotNull WebSQLResultsRow row
|
||||
) throws DBWebException {
|
||||
if (row == null) {
|
||||
throw new DBWebException("Results row is not found");
|
||||
}
|
||||
WebSQLProcessor processor = contextInfo.getProcessor();
|
||||
ThrowableFunction<DBRProgressMonitor, String> function = monitor -> processor.readStringValue(
|
||||
monitor, contextInfo, resultsId, lobColumnIndex, row);
|
||||
return readValue(function, processor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String readValue(
|
||||
@NotNull ThrowableFunction<DBRProgressMonitor, String> function,
|
||||
@NotNull WebSQLProcessor processor
|
||||
) throws DBWebException {
|
||||
try {
|
||||
var result = new StringBuilder();
|
||||
|
||||
DBExecUtils.tryExecuteRecover(
|
||||
contextInfo.getProcessor().getWebSession().getProgressMonitor(),
|
||||
contextInfo.getProcessor().getConnection().getDataSource(),
|
||||
monitor -> {
|
||||
try {
|
||||
result.append(contextInfo.getProcessor().readLobValue(
|
||||
monitor, contextInfo, resultsId, lobColumnIndex, row.get(0)));
|
||||
} catch (Exception e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
processor.getWebSession().getProgressMonitor(),
|
||||
processor.getConnection().getDataSource(),
|
||||
monitor -> {
|
||||
try {
|
||||
result.append(function.apply(monitor));
|
||||
} catch (Exception e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
return result.toString();
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error reading LOB value ", e);
|
||||
throw new DBWebException("Error reading value ", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
mutation getResultsetDataURL($connectionId: ID!, $contextId: ID!, $resultsId: ID!, $lobColumnIndex: Int!, $row: [ SQLResultRow! ]!) {
|
||||
url: readLobValue(
|
||||
connectionId: $connectionId,
|
||||
contextId: $contextId,
|
||||
resultsId: $resultsId,
|
||||
lobColumnIndex: $lobColumnIndex,
|
||||
row: $row
|
||||
)
|
||||
}
|
||||
mutation getResultsetDataURL($connectionId: ID!, $contextId: ID!, $resultsId: ID!, $lobColumnIndex: Int!, $row: SQLResultRow!) {
|
||||
url: sqlReadLobValue(connectionId: $connectionId, contextId: $contextId, resultsId: $resultsId, lobColumnIndex: $lobColumnIndex, row: $row)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mutation sqlReadStringValue($connectionId: ID!, $contextId: ID!, $resultsId: ID!, $columnIndex: Int!, $row: SQLResultRow!) {
|
||||
text: sqlReadStringValue(connectionId: $connectionId, contextId: $contextId, resultsId: $resultsId, columnIndex: $columnIndex, row: $row)
|
||||
}
|
||||
+68
-6
@@ -26,11 +26,16 @@ import { ResultSetViewAction } from './ResultSetViewAction';
|
||||
|
||||
const RESULT_VALUE_PATH = 'sql-result-value';
|
||||
|
||||
interface ICacheEntry {
|
||||
url?: string;
|
||||
fullText?: string;
|
||||
}
|
||||
|
||||
@databaseDataAction()
|
||||
export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabaseResultSet> implements IResultSetDataContentAction {
|
||||
static dataFormat = [ResultDataFormat.Resultset];
|
||||
|
||||
private readonly cache: Map<string, string>;
|
||||
private readonly cache: Map<string, Partial<ICacheEntry>>;
|
||||
activeElement: IResultSetElementKey | null;
|
||||
|
||||
constructor(
|
||||
@@ -60,6 +65,52 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
|
||||
return !!this.result.data?.hasRowIdentifier && isResultSetContentValue(this.format.get(element));
|
||||
}
|
||||
|
||||
private async loadFileFullText(result: IDatabaseResultSet, columnIndex: number, row: IResultSetValue[]) {
|
||||
if (!result.id) {
|
||||
throw new Error("Result's id must be provided");
|
||||
}
|
||||
|
||||
const response = await this.graphQLService.sdk.sqlReadStringValue({
|
||||
resultsId: result.id,
|
||||
connectionId: result.connectionId,
|
||||
contextId: result.contextId,
|
||||
columnIndex,
|
||||
row: {
|
||||
data: row,
|
||||
},
|
||||
});
|
||||
|
||||
return response.text;
|
||||
}
|
||||
|
||||
async getFileFullText(element: IResultSetElementKey) {
|
||||
const column = this.data.getColumn(element.column);
|
||||
const row = this.data.getRowValue(element.row);
|
||||
|
||||
const cachedFullText = this.retrieveFileFullTextFromCache(element);
|
||||
|
||||
if (cachedFullText) {
|
||||
return cachedFullText;
|
||||
}
|
||||
|
||||
if (!row || !column) {
|
||||
throw new Error('Failed to get value metadata information');
|
||||
}
|
||||
|
||||
const fullText = await this.source.runTask(async () => {
|
||||
try {
|
||||
this.activeElement = element;
|
||||
return await this.loadFileFullText(this.result, column.position, row);
|
||||
} finally {
|
||||
this.activeElement = null;
|
||||
}
|
||||
});
|
||||
|
||||
this.updateCache(element, { fullText });
|
||||
|
||||
return fullText;
|
||||
}
|
||||
|
||||
async getFileDataUrl(element: IResultSetElementKey) {
|
||||
const column = this.data.getColumn(element.column);
|
||||
const row = this.data.getRowValue(element.row);
|
||||
@@ -82,21 +133,32 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
|
||||
}
|
||||
|
||||
async resolveFileDataUrl(element: IResultSetElementKey) {
|
||||
const cache = this.retrieveFileDataUrlFromCache(element);
|
||||
const cachedUrl = this.retrieveFileDataUrlFromCache(element);
|
||||
|
||||
if (cache) {
|
||||
return cache;
|
||||
if (cachedUrl) {
|
||||
return cachedUrl;
|
||||
}
|
||||
|
||||
const url = await this.getFileDataUrl(element);
|
||||
this.cache.set(this.getHash(element), url);
|
||||
this.updateCache(element, { url });
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
private updateCache(element: IResultSetElementKey, partialCache: Partial<ICacheEntry>) {
|
||||
const hash = this.getHash(element);
|
||||
const cachedElement = this.cache.get(hash) ?? {};
|
||||
this.cache.set(hash, { ...cachedElement, ...partialCache });
|
||||
}
|
||||
|
||||
retrieveFileFullTextFromCache(element: IResultSetElementKey) {
|
||||
const hash = this.getHash(element);
|
||||
return this.cache.get(hash)?.fullText;
|
||||
}
|
||||
|
||||
retrieveFileDataUrlFromCache(element: IResultSetElementKey) {
|
||||
const hash = this.getHash(element);
|
||||
return this.cache.get(hash);
|
||||
return this.cache.get(hash)?.url;
|
||||
}
|
||||
|
||||
async downloadFileData(element: IResultSetElementKey) {
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { computed, observable } from 'mobx';
|
||||
|
||||
import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { IDatabaseDataModel } from '../../IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
|
||||
import { ResultSetConstraintAction } from './ResultSetConstraintAction';
|
||||
import { ResultSetDataAction } from './ResultSetDataAction';
|
||||
import { ResultSetDataContentAction } from './ResultSetDataContentAction';
|
||||
import { ResultSetEditAction } from './ResultSetEditAction';
|
||||
import { ResultSetFormatAction } from './ResultSetFormatAction';
|
||||
import { ResultSetSelectAction } from './ResultSetSelectAction';
|
||||
import { ResultSetViewAction } from './ResultSetViewAction';
|
||||
|
||||
interface IResultActionsArgs {
|
||||
resultIndex: number;
|
||||
model: IDatabaseDataModel<any, IDatabaseResultSet>;
|
||||
}
|
||||
|
||||
export function useResultActions({ model, resultIndex }: IResultActionsArgs) {
|
||||
return useObservableRef(
|
||||
() => ({
|
||||
get dataAction(): ResultSetDataAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetDataAction);
|
||||
},
|
||||
get selectAction(): ResultSetSelectAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetSelectAction);
|
||||
},
|
||||
get editAction(): ResultSetEditAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetEditAction);
|
||||
},
|
||||
get contentAction(): ResultSetDataContentAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetDataContentAction);
|
||||
},
|
||||
get formatAction(): ResultSetFormatAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetFormatAction);
|
||||
},
|
||||
get constraintAction(): ResultSetConstraintAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetConstraintAction);
|
||||
},
|
||||
get viewAction(): ResultSetViewAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetViewAction);
|
||||
},
|
||||
}),
|
||||
{
|
||||
dataAction: computed,
|
||||
selectAction: computed,
|
||||
editAction: computed,
|
||||
contentAction: computed,
|
||||
formatAction: computed,
|
||||
constraintAction: computed,
|
||||
viewAction: computed,
|
||||
model: observable.ref,
|
||||
resultIndex: observable.ref,
|
||||
},
|
||||
{ model, resultIndex },
|
||||
);
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
.toolsContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
+68
-96
@@ -10,25 +10,23 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Button, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { Button, s, useS, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { QuotasService } from '@cloudbeaver/core-root';
|
||||
import { BASE_TAB_STYLES, TabContainerPanelComponent, TabList, TabsState, UNDERLINE_TAB_STYLES, useTabLocalState } from '@cloudbeaver/core-ui';
|
||||
import { bytesToSize } from '@cloudbeaver/core-utils';
|
||||
import { bytesToSize, isNotNullDefined } from '@cloudbeaver/core-utils';
|
||||
import { EditorLoader, useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
|
||||
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
|
||||
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
|
||||
import { useResultActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultActions';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';
|
||||
import { QuotaPlaceholder } from '../QuotaPlaceholder';
|
||||
import { VALUE_PANEL_TOOLS_STYLES } from '../ValuePanelTools/VALUE_PANEL_TOOLS_STYLES';
|
||||
import { getTypeExtension } from './getTypeExtension';
|
||||
import moduleStyles from './TextValuePresentation.m.css';
|
||||
import { TextValuePresentationService } from './TextValuePresentationService';
|
||||
import { useTextValue } from './useTextValue';
|
||||
|
||||
@@ -70,6 +68,10 @@ const styles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
const TEXT_PLAIN_TYPE = 'text/plain';
|
||||
const TEXT_JSON_TYPE = 'text/json';
|
||||
const APPLICATION_JSON_TYPE = 'application/json';
|
||||
|
||||
export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelProps<any, IDatabaseResultSet>> = observer(
|
||||
function TextValuePresentation({ model, resultIndex, dataFormat }) {
|
||||
const translate = useTranslate();
|
||||
@@ -77,75 +79,58 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
const quotasService = useService(QuotasService);
|
||||
const textValuePresentationService = useService(TextValuePresentationService);
|
||||
const style = useStyles(styles, UNDERLINE_TAB_STYLES, VALUE_PANEL_TOOLS_STYLES);
|
||||
|
||||
const moduleStyle = useS(moduleStyles);
|
||||
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const activeElements = selection.getActiveElements();
|
||||
const firstSelectedCell = activeElements?.[0];
|
||||
const activeTabs = textValuePresentationService.tabs.getDisplayed({
|
||||
dataFormat: dataFormat,
|
||||
model: model,
|
||||
resultIndex: resultIndex,
|
||||
});
|
||||
const { contentAction, editAction, formatAction } = useResultActions({
|
||||
model,
|
||||
resultIndex,
|
||||
});
|
||||
const contentValue = firstSelectedCell ? formatAction.get(firstSelectedCell) : null;
|
||||
const state = useTabLocalState(() =>
|
||||
observable({
|
||||
currentContentType: 'text/plain',
|
||||
lastContentType: 'text/plain',
|
||||
currentContentType: TEXT_PLAIN_TYPE,
|
||||
|
||||
setContentType(type: string) {
|
||||
this.currentContentType = type;
|
||||
setContentType(contentType: string) {
|
||||
if (contentType === TEXT_JSON_TYPE) {
|
||||
contentType = APPLICATION_JSON_TYPE;
|
||||
}
|
||||
|
||||
this.currentContentType = contentType;
|
||||
},
|
||||
setDefaultContentType(type: string) {
|
||||
this.currentContentType = type;
|
||||
this.lastContentType = type;
|
||||
handleTabOpen(tabId: string) {
|
||||
// currentContentType may be selected automatically we don't want to change state in this case
|
||||
if (tabId !== this.currentContentType) {
|
||||
this.setContentType(tabId);
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const editor = model.source.getAction(resultIndex, ResultSetEditAction);
|
||||
const content = model.source.getAction(resultIndex, ResultSetDataContentAction);
|
||||
|
||||
const activeElements = selection.getActiveElements();
|
||||
const activeTabs = textValuePresentationService.tabs.getDisplayed({ dataFormat, model, resultIndex });
|
||||
|
||||
let contentType = 'text/plain';
|
||||
let firstSelectedCell: IResultSetElementKey | undefined;
|
||||
let readonly = true;
|
||||
let valueTruncated = false;
|
||||
let limit: string | undefined;
|
||||
let valueSize: string | undefined;
|
||||
|
||||
if (activeElements.length > 0) {
|
||||
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
|
||||
firstSelectedCell = activeElements[0];
|
||||
|
||||
const value = format.get(firstSelectedCell);
|
||||
readonly = format.isReadOnly(firstSelectedCell) || format.isBinary(firstSelectedCell);
|
||||
|
||||
if (isResultSetContentValue(value)) {
|
||||
valueTruncated = content.isContentTruncated(value);
|
||||
|
||||
if (valueTruncated) {
|
||||
limit = bytesToSize(quotasService.getQuota('sqlBinaryPreviewMaxLength'));
|
||||
valueSize = bytesToSize(value.contentLength ?? 0);
|
||||
}
|
||||
|
||||
if (value.contentType) {
|
||||
contentType = value.contentType;
|
||||
|
||||
if (contentType === 'text/json') {
|
||||
contentType = 'application/json';
|
||||
}
|
||||
|
||||
if (!activeTabs.some(tab => tab.key === contentType)) {
|
||||
contentType = 'text/plain';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly = model.isReadonly(resultIndex) || model.isDisabled(resultIndex) || readonly;
|
||||
|
||||
if (contentType !== state.lastContentType) {
|
||||
state.setDefaultContentType(contentType);
|
||||
}
|
||||
const { textValue, isTruncated, isTextColumn, pasteFullText } = useTextValue({
|
||||
model,
|
||||
resultIndex,
|
||||
currentContentType: state.currentContentType,
|
||||
});
|
||||
const isSelectedCellReadonly = firstSelectedCell && (formatAction.isReadOnly(firstSelectedCell) || formatAction.isBinary(firstSelectedCell));
|
||||
const isReadonlyByResultIndex = model.isReadonly(resultIndex) || model.isDisabled(resultIndex) || !firstSelectedCell;
|
||||
const isReadonly = isSelectedCellReadonly || isReadonlyByResultIndex;
|
||||
const valueSize =
|
||||
isResultSetContentValue(contentValue) && isNotNullDefined(contentValue.contentLength) ? bytesToSize(contentValue.contentLength) : undefined;
|
||||
const limit = bytesToSize(quotasService.getQuota('sqlBinaryPreviewMaxLength'));
|
||||
const canSave = firstSelectedCell && contentAction.isDownloadable(firstSelectedCell);
|
||||
const shouldShowPasteButton = isTextColumn && isTruncated;
|
||||
const typeExtension = useMemo(() => getTypeExtension(state.currentContentType) ?? [], [state.currentContentType]);
|
||||
const extensions = useCodemirrorExtensions(undefined, typeExtension);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
if (firstSelectedCell && !readonly) {
|
||||
editor.set(firstSelectedCell, newValue);
|
||||
if (firstSelectedCell && !isReadonly) {
|
||||
editAction.set(firstSelectedCell, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,33 +140,15 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
}
|
||||
|
||||
try {
|
||||
await content.downloadFileData(firstSelectedCell);
|
||||
await contentAction.downloadFileData(firstSelectedCell);
|
||||
} catch (exception) {
|
||||
notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error');
|
||||
}
|
||||
}
|
||||
|
||||
let currentContentType = state.currentContentType;
|
||||
|
||||
if (activeTabs.length > 0 && !activeTabs.some(tab => tab.key === currentContentType)) {
|
||||
currentContentType = activeTabs[0].key;
|
||||
}
|
||||
|
||||
const canSave = !!firstSelectedCell && content.isDownloadable(firstSelectedCell);
|
||||
const typeExtension = useMemo(() => getTypeExtension(currentContentType) ?? [], [currentContentType]);
|
||||
const extensions = useCodemirrorExtensions(undefined, typeExtension);
|
||||
|
||||
const value = useTextValue({
|
||||
model,
|
||||
resultIndex,
|
||||
currentContentType,
|
||||
});
|
||||
|
||||
function handleTabOpen(tabId: string) {
|
||||
// currentContentType may be selected automatically we don't want to change state in this case
|
||||
if (tabId !== currentContentType) {
|
||||
state.setContentType(tabId);
|
||||
}
|
||||
if (!activeTabs.some(tab => tab.key === state.currentContentType)) {
|
||||
const contentType = activeTabs.length > 0 && activeTabs[0].key ? activeTabs[0].key : TEXT_PLAIN_TYPE;
|
||||
state.setContentType(contentType);
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
@@ -191,23 +158,28 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
dataFormat={dataFormat}
|
||||
resultIndex={resultIndex}
|
||||
container={textValuePresentationService.tabs}
|
||||
currentTabId={currentContentType}
|
||||
currentTabId={state.currentContentType}
|
||||
model={model}
|
||||
lazy
|
||||
onChange={tab => handleTabOpen(tab.tabId)}
|
||||
onChange={tab => state.handleTabOpen(tab.tabId)}
|
||||
>
|
||||
<TabList style={[BASE_TAB_STYLES, styles, UNDERLINE_TAB_STYLES]} />
|
||||
</TabsState>
|
||||
</actions>
|
||||
<EditorLoader key={readonly ? '1' : '0'} value={value} readonly={readonly} extensions={extensions} onChange={value => handleChange(value)} />
|
||||
{valueTruncated && <QuotaPlaceholder limit={limit} size={valueSize} />}
|
||||
{canSave && (
|
||||
<tools-container>
|
||||
<EditorLoader key={isReadonly ? '1' : '0'} value={textValue} readonly={isReadonly} extensions={extensions} onChange={handleChange} />
|
||||
{isTruncated && <QuotaPlaceholder limit={limit} size={valueSize} />}
|
||||
<div className={s(moduleStyle, { toolsContainer: true })}>
|
||||
{canSave && (
|
||||
<Button disabled={model.isLoading()} onClick={save}>
|
||||
{translate('ui_download')}
|
||||
</Button>
|
||||
</tools-container>
|
||||
)}
|
||||
)}
|
||||
{shouldShowPasteButton && (
|
||||
<Button disabled={model.isLoading()} onClick={pasteFullText}>
|
||||
{translate('data_viewer_presentation_value_content_full_text_button')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</container>,
|
||||
);
|
||||
},
|
||||
|
||||
+53
-11
@@ -5,12 +5,14 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { isNotNullDefined } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { isResultSetBinaryFileValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBinaryFileValue';
|
||||
import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
|
||||
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
|
||||
import { useResultActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultActions';
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import { useAutoFormat } from './useAutoFormat';
|
||||
@@ -21,31 +23,71 @@ interface IUseTextValueArgs {
|
||||
currentContentType: string;
|
||||
}
|
||||
|
||||
export function useTextValue({ model, resultIndex, currentContentType }: IUseTextValueArgs) {
|
||||
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
const editor = model.source.getAction(resultIndex, ResultSetEditAction);
|
||||
interface IUseTextValue {
|
||||
textValue: string;
|
||||
isTruncated: boolean;
|
||||
isTextColumn: boolean;
|
||||
pasteFullText(): Promise<void>;
|
||||
}
|
||||
|
||||
export function useTextValue({ model, resultIndex, currentContentType }: IUseTextValueArgs): IUseTextValue {
|
||||
const { formatAction, editAction, contentAction, dataAction } = useResultActions({ model, resultIndex });
|
||||
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const activeElements = selection.getActiveElements();
|
||||
const firstSelectedCell = activeElements?.[0];
|
||||
const formatter = useAutoFormat();
|
||||
const columnType = firstSelectedCell ? dataAction.getColumn(firstSelectedCell.column)?.dataKind : '';
|
||||
const isTextColumn = columnType?.toLocaleLowerCase() === 'string';
|
||||
const contentValue = firstSelectedCell ? formatAction.get(firstSelectedCell) : null;
|
||||
const cachedFullText = firstSelectedCell ? contentAction.retrieveFileFullTextFromCache(firstSelectedCell) : '';
|
||||
const blob = firstSelectedCell ? formatAction.get(firstSelectedCell) : null;
|
||||
const notificationService = useService(NotificationService);
|
||||
|
||||
const result: IUseTextValue = {
|
||||
textValue: '',
|
||||
isTruncated: false,
|
||||
isTextColumn,
|
||||
async pasteFullText() {
|
||||
if (!firstSelectedCell) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await contentAction.getFileFullText(firstSelectedCell);
|
||||
} catch (exception) {
|
||||
notificationService.logException(exception as any, 'data_viewer_presentation_value_content_paste_error');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (!isNotNullDefined(firstSelectedCell)) {
|
||||
return '';
|
||||
return result;
|
||||
}
|
||||
|
||||
if (editor.isElementEdited(firstSelectedCell)) {
|
||||
return format.getText(firstSelectedCell);
|
||||
if (isResultSetContentValue(contentValue)) {
|
||||
result.isTruncated = contentAction.isContentTruncated(contentValue);
|
||||
}
|
||||
|
||||
const blob = format.get(firstSelectedCell);
|
||||
if (isTextColumn && cachedFullText) {
|
||||
result.textValue = cachedFullText;
|
||||
result.isTruncated = false;
|
||||
}
|
||||
|
||||
if (editAction.isElementEdited(firstSelectedCell)) {
|
||||
result.textValue = formatAction.getText(firstSelectedCell);
|
||||
}
|
||||
|
||||
if (isResultSetBinaryFileValue(blob)) {
|
||||
const value = formatter.formatBlob(currentContentType, blob);
|
||||
|
||||
if (value) {
|
||||
return value;
|
||||
result.textValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
return formatter.format(currentContentType, format.getText(firstSelectedCell));
|
||||
if (!result.textValue) {
|
||||
result.textValue = formatter.format(currentContentType, formatAction.getText(firstSelectedCell));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ export default [
|
||||
['data_viewer_presentation_value_content_was_truncated', 'The value was truncated'],
|
||||
['data_viewer_presentation_value_content_value_size', 'Value size'],
|
||||
['data_viewer_presentation_value_content_download_error', 'Download failed'],
|
||||
['data_viewer_presentation_value_content_paste_error', 'Cannot load full text'],
|
||||
['data_viewer_presentation_value_content_full_text_button', 'View full text'],
|
||||
['data_viewer_script_preview', 'Script'],
|
||||
['data_viewer_script_preview_dialog_title', 'Preview changes'],
|
||||
['data_viewer_script_preview_error_title', "Can't get the script"],
|
||||
|
||||
@@ -34,6 +34,8 @@ export default [
|
||||
['data_viewer_presentation_value_content_was_truncated', 'The value was truncated'],
|
||||
['data_viewer_presentation_value_content_value_size', 'Value size'],
|
||||
['data_viewer_presentation_value_content_download_error', 'Download failed'],
|
||||
['data_viewer_presentation_value_content_paste_error', 'Cannot load full text'],
|
||||
['data_viewer_presentation_value_content_full_text_button', 'View full text'],
|
||||
['data_viewer_refresh_result_set', 'Refresh result set'],
|
||||
['data_viewer_total_count_tooltip', 'Get total count'],
|
||||
['data_viewer_total_count_failed', 'Failed to get total count'],
|
||||
|
||||
@@ -32,6 +32,8 @@ export default [
|
||||
['data_viewer_presentation_value_content_was_truncated', 'Значение было обрезано'],
|
||||
['data_viewer_presentation_value_content_value_size', 'Размер значения'],
|
||||
['data_viewer_presentation_value_content_download_error', 'Не удалось загрузить файл'],
|
||||
['data_viewer_presentation_value_content_paste_error', 'Не удалось загрузить весь текст'],
|
||||
['data_viewer_presentation_value_content_full_text_button', 'Посмотреть весь текст'],
|
||||
['data_viewer_script_preview', 'Скрипт'],
|
||||
['data_viewer_script_preview_dialog_title', 'Предпросмотр изменений'],
|
||||
['data_viewer_script_preview_error_title', 'Не удалось получить скрипт'],
|
||||
|
||||
@@ -38,6 +38,8 @@ export default [
|
||||
['data_viewer_presentation_value_content_was_truncated', 'The value was truncated'],
|
||||
['data_viewer_presentation_value_content_value_size', 'Value size'],
|
||||
['data_viewer_presentation_value_content_download_error', 'Download failed'],
|
||||
['data_viewer_presentation_value_content_paste_error', 'Cannot load full text'],
|
||||
['data_viewer_presentation_value_content_full_text_button', 'View full text'],
|
||||
['data_viewer_script_preview', '脚本'],
|
||||
['data_viewer_script_preview_dialog_title', '预览更改'],
|
||||
['data_viewer_script_preview_error_title', '无法获取脚本'],
|
||||
|
||||
Reference in New Issue
Block a user