-
- {renderTitle()}
- {hasIssues && (
-
- )}
+
+
+
+ {roots.map((root, idx) => {
+ const hasIssues = hasRootIssues(root.issues);
+
+ return (
+ openDialog(root.issues)}
+ />
+ );
+ })}
+
- {hasIssues && showIssues && }
+
+
+
+ );
+}
+
+interface ErrorPreviewItemProps {
+ severity: SEVERITY;
+ message?: string;
+ hideSeverity?: boolean;
+ hasIssues?: boolean;
+ expanded?: boolean;
+ onClick: () => void;
+}
+
+export function ErrorPreviewItem({
+ severity,
+ message,
+ hideSeverity,
+ hasIssues,
+ expanded,
+ onClick,
+}: ErrorPreviewItemProps) {
+ const buttonLabel = expanded ? i18n('action_hide-details') : i18n('action_show-details');
+
+ return (
+
+ {hideSeverity ? null : }
+ {message}
+
+ {hasIssues && (
+
+ )}
);
}
@@ -77,10 +159,7 @@ interface IssuesProps {
hideSeverity?: boolean;
}
export function Issues({issues, hideSeverity}: IssuesProps) {
- const mostSevereIssue = issues?.reduce((result, issue) => {
- const severity = issue.severity ?? 10;
- return Math.min(result, severity);
- }, 10);
+ const mostSevereIssue = getMostSevere(issues);
return (
{issues?.map((issue, index) => (
@@ -88,7 +167,7 @@ export function Issues({issues, hideSeverity}: IssuesProps) {
key={index}
hideSeverity={hideSeverity}
issue={issue}
- expanded={issue === mostSevereIssue}
+ expanded={issue.severity === mostSevereIssue}
/>
))}
@@ -109,7 +188,7 @@ function Issue({
const severity = getSeverity(issue.severity);
const issues = issue.issues;
- const hasIssues = Array.isArray(issues) && issues.length > 0;
+ const hasIssues = hasRootIssues(issues);
const arrowDirection = isExpand ? 'bottom' : 'right';
@@ -133,7 +212,9 @@ function Issue({
{hideSeverity ? null :
}
{issue.issue_code ? (
-
Code: {issue.issue_code}
+
+ {i18n('field_code')}: {issue.issue_code}
+
) : null}
{hasIssues && isExpand && (
@@ -158,12 +239,15 @@ function IssueText({issue}: IssueTextProps) {
return (
{position && (
-
+
{position}
)}
-
+
);
@@ -217,14 +301,3 @@ function IssueSeverity({severity}: {severity: SEVERITY}) {
);
}
-
-function getIssuePosition(issue: IssueMessage): string {
- const {position} = issue;
- if (typeof position !== 'object' || position === null || !isNumeric(position.row)) {
- return '';
- }
-
- const {row, column} = position;
-
- return isNumeric(column) ? `${row}:${column}` : `line ${row}`;
-}
diff --git a/src/containers/Tenant/Query/Issues/IssuesDialog.tsx b/src/containers/Tenant/Query/Issues/IssuesDialog.tsx
new file mode 100644
index 0000000000..1c94b9affe
--- /dev/null
+++ b/src/containers/Tenant/Query/Issues/IssuesDialog.tsx
@@ -0,0 +1,35 @@
+import {Dialog} from '@gravity-ui/uikit';
+
+import type {IssueMessage} from '../../../../types/api/query';
+
+import {Issues} from './Issues';
+
+interface IssuesDialogProps {
+ open: boolean;
+ issues: IssueMessage[];
+ hideSeverity?: boolean;
+ onClose: () => void;
+ textButtonCancel?: string;
+ size?: 's' | 'm' | 'l';
+ caption?: string;
+}
+
+export function IssuesDialog({
+ open,
+ issues,
+ hideSeverity,
+ onClose,
+ textButtonCancel = 'Close',
+ size = 'm',
+ caption,
+}: IssuesDialogProps) {
+ return (
+
+ );
+}
diff --git a/src/containers/Tenant/Query/Issues/helpers.ts b/src/containers/Tenant/Query/Issues/helpers.ts
new file mode 100644
index 0000000000..c225a9ad78
--- /dev/null
+++ b/src/containers/Tenant/Query/Issues/helpers.ts
@@ -0,0 +1,45 @@
+import type {ErrorResponse, IssueMessage} from '../../../../types/api/query';
+import {isNumeric} from '../../../../utils/utils';
+
+export function hasRootIssues(issues?: IssueMessage[]): issues is IssueMessage[] {
+ return Array.isArray(issues) && issues.length > 0;
+}
+
+export function normalizeRoots(data: ErrorResponse | string): IssueMessage[] {
+ if (typeof data === 'string') {
+ return [];
+ }
+
+ if (data?.error?.message) {
+ return [
+ {
+ message: data.error.message,
+ severity: data.error.severity,
+ position: data.error.position,
+ end_position: data.error.end_position,
+ issue_code: data.error.issue_code,
+ issues: Array.isArray(data.issues) ? data.issues : [],
+ },
+ ];
+ }
+
+ return Array.isArray(data.issues) ? data.issues : [];
+}
+
+export function getIssuePosition(issue: IssueMessage): string {
+ const {position} = issue;
+ if (typeof position !== 'object' || position === null || !isNumeric(position.row)) {
+ return '';
+ }
+
+ const {row, column} = position;
+
+ return isNumeric(column) ? `${row}:${column}` : `line ${row}`;
+}
+
+export function getMostSevere(issues?: IssueMessage[] | null) {
+ return issues?.reduce((result, issue) => {
+ const severity = issue.severity ?? 10;
+ return Math.min(result, severity);
+ }, 10);
+}
diff --git a/src/containers/Tenant/Query/Issues/i18n/en.json b/src/containers/Tenant/Query/Issues/i18n/en.json
new file mode 100644
index 0000000000..2c1a620d78
--- /dev/null
+++ b/src/containers/Tenant/Query/Issues/i18n/en.json
@@ -0,0 +1,9 @@
+{
+ "action_show-details": "Show details",
+ "action_hide-details": "Hide details",
+ "action_close": "Close",
+ "action_show-full-message": "Show full message",
+
+ "field_code": "Code",
+ "field_position": "Position"
+}
diff --git a/src/containers/Tenant/Query/Issues/i18n/index.ts b/src/containers/Tenant/Query/Issues/i18n/index.ts
new file mode 100644
index 0000000000..14b87ff223
--- /dev/null
+++ b/src/containers/Tenant/Query/Issues/i18n/index.ts
@@ -0,0 +1,7 @@
+import {registerKeysets} from '../../../../../utils/i18n';
+
+import en from './en.json';
+
+const COMPONENT = 'issues';
+
+export default registerKeysets(COMPONENT, {en});
diff --git a/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.scss b/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.scss
index 86c532db7c..e905320ac5 100644
--- a/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.scss
+++ b/src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.scss
@@ -1,6 +1,5 @@
.ydb-query-result-error {
&__message {
- padding-top: var(--g-spacing-4);
- padding-left: var(--g-spacing-4);
+ padding: var(--g-spacing-4) var(--g-spacing-4) 0 var(--g-spacing-4);
}
}
diff --git a/src/containers/Tenant/utils/schemaQueryTemplates.ts b/src/containers/Tenant/utils/schemaQueryTemplates.ts
index c28a664ec8..682d5b3e39 100644
--- a/src/containers/Tenant/utils/schemaQueryTemplates.ts
+++ b/src/containers/Tenant/utils/schemaQueryTemplates.ts
@@ -1,5 +1,9 @@
import type {IQueryResult} from '../../../types/store/query';
-import {getStringifiedData} from '../../../utils/dataFormatters/dataFormatters';
+import {
+ getStringifiedData,
+ stripIndentByFirstLine,
+ trimOuterEmptyLines,
+} from '../../../utils/dataFormatters/dataFormatters';
import type {SchemaData} from '../Schema/SchemaViewer/types';
export interface SchemaQueryParams {
@@ -19,13 +23,6 @@ function toLF(str: string) {
return str.replace(/\r\n?/g, '\n');
}
-function stripAllIndent(str: string) {
- return str
- .split('\n')
- .map((line) => line.trim())
- .join('\n');
-}
-
function indentBlock(str: string, pad = ' ') {
return str.replace(/^/gm, pad);
}
@@ -347,8 +344,8 @@ export const alterStreamingQueryText = (params?: SchemaQueryParams) => {
const sysData = params?.streamingQueryData;
const rawQueryText = getStringifiedData(sysData?.resultSets?.[0]?.result?.[0]?.Text);
let queryText = toLF(rawQueryText);
- queryText = queryText.trim();
- queryText = stripAllIndent(queryText);
+ queryText = trimOuterEmptyLines(queryText);
+ queryText = stripIndentByFirstLine(queryText);
queryText = normalizeParameter(queryText);
const bodyQueryText = queryText
diff --git a/src/utils/dataFormatters/dataFormatters.ts b/src/utils/dataFormatters/dataFormatters.ts
index 616b085086..2ee9adfd3f 100644
--- a/src/utils/dataFormatters/dataFormatters.ts
+++ b/src/utils/dataFormatters/dataFormatters.ts
@@ -241,3 +241,45 @@ export function getStringifiedData(value: unknown) {
return value.toString();
}
}
+
+// Delete outer empty lines, saving first line spaces
+export function trimOuterEmptyLines(str: string) {
+ const lines = str.split('\n');
+
+ let start = 0;
+ while (start < lines.length && lines[start].trim() === '') {
+ start++;
+ }
+
+ let end = lines.length - 1;
+ while (end >= start && lines[end].trim() === '') {
+ end--;
+ }
+
+ return lines.slice(start, end + 1).join('\n');
+}
+
+// Remove from each line exactly as many leading spaces
+// as from the first non-empty line
+export function stripIndentByFirstLine(str: string) {
+ const lines = str.split('\n');
+ if (lines.length === 0) {
+ return str;
+ }
+
+ const firstIdx = lines.findIndex((l) => l.trim() !== '');
+ if (firstIdx === -1) {
+ return str;
+ }
+
+ const firstLine = lines[firstIdx];
+ const match = firstLine.match(/^ +/);
+ const leadSpaces = match ? match[0].length : 0;
+ if (leadSpaces === 0) {
+ return str;
+ }
+
+ const re = new RegExp(`^ {0,${leadSpaces}}`);
+ const res = lines.map((line) => line.replace(re, ''));
+ return res.join('\n');
+}
diff --git a/src/utils/query.ts b/src/utils/query.ts
index 82ed602203..24173add71 100644
--- a/src/utils/query.ts
+++ b/src/utils/query.ts
@@ -231,6 +231,10 @@ export function isQueryErrorResponse(data: unknown): data is ErrorResponse {
return Boolean(data && typeof data === 'object' && 'error' in data && 'issues' in data);
}
+export function isErrorResponse(data: unknown): data is ErrorResponse {
+ return Boolean(data && typeof data === 'object' && 'issues' in data);
+}
+
// Although schema is set in request, if schema is not supported default schema for the version will be used
// So we should additionally parse response
export function parseQueryAPIResponse(