Skip to content
Open
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
18 changes: 18 additions & 0 deletions packages/sqle/src/locale/en-US/dataSourceComparison.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Only the keys that participate in cross-locale switching are translated
// here; other dataSourceComparison keys fall back to zh-CN via i18next's
// per-key fallback (fallbackLng = zh-CN). The drop-create warning banner
// MUST display in the user's selected language because it surfaces data
// loss risk; relying on zh-CN fallback would defeat the i18n contract.
// eslint-disable-next-line import/no-anonymous-default-export
export default {
entry: {
modifiedSqlDrawer: {
dropCreateWarningBanner:
'Modify SQL contains destructive operations. Please review before execution.',
dropCreateWarningTable:
'Data loss risk; table will be dropped and recreated.',
dropCreateWarningView:
'View will be recreated; downstream queries depending on this view may be affected.'
}
}
};
4 changes: 3 additions & 1 deletion packages/sqle/src/locale/en-US/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import pipelineConfiguration from './pipelineConfiguration';
import versionManagement from './versionManagement';
import sqlInsights from './sqlInsights';
import globalDashboard from './globalDashboard';
import dataSourceComparison from './dataSourceComparison';

// eslint-disable-next-line import/no-anonymous-default-export
export default {
Expand Down Expand Up @@ -61,6 +62,7 @@ export default {
pipelineConfiguration,
versionManagement,
sqlInsights,
globalDashboard
globalDashboard,
dataSourceComparison
}
};
6 changes: 5 additions & 1 deletion packages/sqle/src/locale/zh-CN/dataSourceComparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ export default {
}
},
modifiedSqlDrawer: {
title: '变更SQL语句信息'
title: '变更SQL语句信息',
dropCreateWarningBanner:
'变更SQL中含数据破坏性操作,请人工确认后再执行。',
dropCreateWarningTable: '数据将丢失;表将被删除并重建。',
dropCreateWarningView: '视图将被重建;依赖该视图的下游查询可能受影响。'
},
modifiedSqlAuditResult: {
cardTitle: '变更语句'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { mockUsePermission } from '@actiontech/shared/lib/testUtil/mockHook/mock
import { getBySelector } from '@actiontech/shared/lib/testUtil/customQuery';
import { mockProjectInfo } from '@actiontech/shared/lib/testUtil/mockHook/data';
import { compressToEncodedURIComponent } from 'lz-string';
import { IDatabaseDiffModifySQL } from '@actiontech/shared/lib/api/sqle/service/common';
import i18n from 'i18next';
import enUSDataSourceComparison from '../../../../locale/en-US/dataSourceComparison';
import zhCNDataSourceComparison from '../../../../locale/zh-CN/dataSourceComparison';

describe('ModifiedSqlDrawer', () => {
beforeEach(() => {
Expand Down Expand Up @@ -119,4 +123,152 @@ describe('ModifiedSqlDrawer', () => {
customRender(true);
expect(screen.queryByText('生成变更工单')).not.toBeInTheDocument();
});

// ---------------------------------------------------------------------------
// UI-3 (compat-RISK-6): WARNING banner + per-line highlight coverage.
//
// map-case fixtures: every scenario stuffs `databaseDiffModifiedSqlInfos`
// with a hand-crafted `modify_sqls` list so the production code's WARNING
// detection (`anySqlHasWarning` + `sqlHasWarningLine`) walks the same input
// path it would in real life. We never assert against translated strings to
// avoid coupling to the locale wording — the data-testid contract is the
// stable surface for both banner and highlight.
// ---------------------------------------------------------------------------
const buildDiffInfos = (
sqls: string[],
schemaName = 'dms'
): IDatabaseDiffModifySQL[] => [
{
schema_name: schemaName,
modify_sqls: sqls.map((sql) => ({ sql_statement: sql }))
}
];

const renderWithSqls = (sqls: string[]) =>
sqleSuperRender(
<ModifiedSqlDrawer
open={true}
onClose={onCloseSpy}
instanceType="MySQL"
generateModifySqlPending={false}
databaseDiffModifiedSqlInfos={buildDiffInfos(sqls)}
comparisonInstanceInfo={{
instanceId: 'instanceId',
instanceName: comparisonInstanceName,
instanceType: 'MySQL'
}}
genDatabaseDiffModifiedSQLsParams={apiParams}
dbExistingSchemas={dbExistingSchemas}
/>
);

it('should render the drop-create warning banner when a SQL statement contains -- WARNING comment', () => {
renderWithSqls([
`-- WARNING: data loss risk; table will be dropped and recreated.\nDROP TABLE IF EXISTS audit_files;\nCREATE TABLE audit_files (id BIGINT);`
]);

expect(
screen.getByTestId('modified-sql-drop-create-warning-banner')
).toBeInTheDocument();
});

it('should add a warning-highlight-line class to every -- WARNING line inside the SQL preview', () => {
renderWithSqls([
`-- WARNING: data loss risk; table will be dropped and recreated.\nDROP TABLE IF EXISTS audit_files;\nCREATE TABLE audit_files (id BIGINT);`
]);

const highlightContainer = screen.getByTestId('warning-highlighted-sql');
expect(highlightContainer).toBeInTheDocument();
const warningLineNodes = highlightContainer.querySelectorAll(
'.warning-highlight-line'
);
expect(warningLineNodes.length).toBe(1);
expect(warningLineNodes[0].textContent).toMatch(/-- WARNING:/);
});

it('should not render the banner nor the warning highlight container when no SQL contains a -- WARNING comment', () => {
renderWithSqls([
`ALTER TABLE audit_files ADD COLUMN status VARCHAR(32);`,
`ALTER TABLE audit_files ADD COLUMN created_at DATETIME;`
]);

expect(
screen.queryByTestId('modified-sql-drop-create-warning-banner')
).not.toBeInTheDocument();
expect(
screen.queryByTestId('warning-highlighted-sql')
).not.toBeInTheDocument();
// Sanity: SQL lines still render but never carry the highlight class.
expect(document.querySelectorAll('.warning-highlight-line').length).toBe(0);
});

it('should render the drop-create warning banner only once even when multiple modify SQLs carry -- WARNING comments', () => {
renderWithSqls([
`-- WARNING: view will be recreated; downstream queries may be affected.\nDROP VIEW IF EXISTS v_audit_summary;\nCREATE VIEW v_audit_summary AS SELECT id FROM audit_files;`,
`-- WARNING: data loss risk; table will be dropped and recreated.\nDROP TABLE IF EXISTS audit_files;\nCREATE TABLE audit_files (id BIGINT, status VARCHAR(32));`
]);

expect(
screen.queryAllByTestId('modified-sql-drop-create-warning-banner').length
).toBe(1);
// Both SQL fragments must keep their own highlight container — banner
// dedup happens at the drawer level, not by collapsing per-fragment
// visual cues.
expect(screen.queryAllByTestId('warning-highlighted-sql').length).toBe(2);
expect(document.querySelectorAll('.warning-highlight-line').length).toBe(2);
});

// Case 4 — i18n switching: banner copy MUST follow the active language.
// jest-setup only seeds zh-CN; we add the en-US bundle on the fly so the
// test can flip to English and assert the literal differs from Chinese.
describe('locale switching for the WARNING banner copy', () => {
beforeAll(() => {
i18n.addResourceBundle(
'en-US',
'translation',
{ dataSourceComparison: enUSDataSourceComparison },
true,
true
);
});

afterEach(() => {
// restore default zh-CN so the rest of the test file keeps using the
// language jest-setup expects
i18n.changeLanguage('zh-CN');
});

it('should render the WARNING banner copy according to the active language (zh-CN vs en-US)', async () => {
const sqls = [
`-- WARNING: data loss risk; table will be dropped and recreated.\nDROP TABLE IF EXISTS audit_files;\nCREATE TABLE audit_files (id BIGINT);`
];

// ----- zh-CN -----
await i18n.changeLanguage('zh-CN');
const zhView = renderWithSqls(sqls);
const zhBanner = zhView.getByTestId(
'modified-sql-drop-create-warning-banner'
);
const zhCopy =
zhCNDataSourceComparison.entry.modifiedSqlDrawer
.dropCreateWarningBanner;
expect(zhBanner).toHaveTextContent(zhCopy);
zhView.unmount();

// ----- en-US -----
await i18n.changeLanguage('en-US');
const enView = renderWithSqls(sqls);
const enBanner = enView.getByTestId(
'modified-sql-drop-create-warning-banner'
);
const enCopy =
enUSDataSourceComparison.entry.modifiedSqlDrawer
.dropCreateWarningBanner;
expect(enBanner).toHaveTextContent(enCopy);

// sanity — the two locales must actually produce different copy or the
// test would always pass on either branch.
expect(zhCopy).not.toEqual(enCopy);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
import AuditResult from '../SqlAuditResult';
import {
ModifiedSqlAuditResultInfoStyleWrapper,
ModifiedSqlAuditResultTitleStyleWrapper
ModifiedSqlAuditResultTitleStyleWrapper,
WarningHighlightedSqlStyleWrapper
} from './style';
import { useTranslation } from 'react-i18next';
import {
isWarningLine,
sqlHasWarningLine
} from '../ModifiedSqlDrawer/warningSql';

type Props = {
dataSource?: ISQLStatementWithAuditResult[];
Expand All @@ -17,6 +22,35 @@
auditError?: string;
};

// Renders a SQL string verbatim, line by line, while attaching a
// `warning-highlight-line` className to every `-- WARNING:` line. Used in
// place of SQLRenderer when WARNING annotations are present so the warning
// rows visibly stand out without depending on SQLRenderer's internal HTML.
const SqlWithWarningHighlight: React.FC<{ sql: string }> = ({ sql }) => {
const lines = sql.split(/\r?\n/);
return (
<WarningHighlightedSqlStyleWrapper data-testid="warning-highlighted-sql">
{lines.map((line, lineIndex) => (
<span
key={lineIndex}
data-testid={
isWarningLine(line)
? `warning-line-${lineIndex}`
: `sql-line-${lineIndex}`
}
className={
isWarningLine(line)
? 'code-line warning-highlight-line'
: 'code-line'
}
>
{line === '' ? ' ' : line}

Check warning on line 47 in packages/sqle/src/page/DataSourceComparison/ComparisonEntry/component/ModifiedSqlAuditResult/List.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
</span>
))}
</WarningHighlightedSqlStyleWrapper>
);
};

const ModifiedSqlAuditResultList: React.FC<Props> = ({
dataSource,
instanceType,
Expand All @@ -34,6 +68,8 @@
split={false}
dataSource={dataSource}
renderItem={(item, index) => {
const sqlText = item.sql_statement ?? '';

Check warning on line 71 in packages/sqle/src/page/DataSourceComparison/ComparisonEntry/component/ModifiedSqlAuditResult/List.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
const containsWarning = sqlHasWarningLine(sqlText);
return (
<List.Item key={index}>
<ModifiedSqlAuditResultInfoStyleWrapper>
Expand All @@ -45,7 +81,11 @@
</div>
</ModifiedSqlAuditResultTitleStyleWrapper>

<SQLRenderer sql={item.sql_statement ?? ''} showLineNumbers />
{containsWarning ? (
<SqlWithWarningHighlight sql={sqlText} />
) : (
<SQLRenderer sql={sqlText} showLineNumbers />
)}
<SqlAuditResultCollapseStyleWrapper
activeKey={auditResultCollapseActiveKeys}
onChange={(keys) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,30 @@ export const ModifiedSqlAuditResultTitleStyleWrapper = styled('div')`
color: ${({ theme }) => theme.sharedTheme.uiToken.colorPrimary};
}
`;

export const WarningHighlightedSqlStyleWrapper = styled('pre')`
margin: 0;
padding: 8px 12px;
background: ${({ theme }) => theme.sharedTheme.uiToken.colorBgBase};
border: 1px solid ${({ theme }) => theme.sharedTheme.uiToken.colorBorder};
border-radius: 4px;
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
font-size: 12px;
line-height: 20px;
white-space: pre;
overflow-x: auto;

.code-line {
display: block;
min-height: 20px;
}

.warning-highlight-line {
background: ${({ theme }) => theme.sharedTheme.uiToken.colorWarningBgHover};
color: ${({ theme }) => theme.sharedTheme.uiToken.colorWarning};
border-left: 3px solid
${({ theme }) => theme.sharedTheme.uiToken.colorWarning};
padding-left: 6px;
font-weight: 500;
}
`;
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { BasicDrawer } from '@actiontech/dms-kit';
import { IDatabaseDiffModifySQL } from '@actiontech/shared/lib/api/sqle/service/common';
import { Spin } from 'antd';
import { Alert, Spin } from 'antd';
import { useTranslation } from 'react-i18next';
import { SelectedInstanceInfo } from '../../index.type';
import { useCurrentProject } from '@actiontech/shared/lib/features';
import { CreateWorkflowForModifiedSqlAction } from '../../actions';
import { IGenDatabaseDiffModifySQLsV1Params } from '@actiontech/shared/lib/api/sqle/service/database_comparison/index.d';
import ModifiedSqlAuditResult from '../ModifiedSqlAuditResult';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { anySqlHasWarning } from './warningSql';
type Props = {
open: boolean;
onClose: () => void;
Expand Down Expand Up @@ -36,6 +37,18 @@ const ModifiedSqlDrawer: React.FC<Props> = ({
onClose();
setAuditResultCollapseActiveKeys([]);
};
// Surface the drawer-level warning banner exactly once when ANY modify SQL
// across ANY schema contains a `-- WARNING:` line. The banner is independent
// of dbType: drivers emit WARNING lines whenever a non-destructive ALTER is
// not possible (e.g. Hive DROP+CREATE fallback, VIEW recreation).
const hasAnyWarningSql = useMemo(() => {
if (!databaseDiffModifiedSqlInfos) {
return false;
}
return databaseDiffModifiedSqlInfos.some((schemaInfo) =>
anySqlHasWarning(schemaInfo.modify_sqls)
);
}, [databaseDiffModifiedSqlInfos]);
return (
<BasicDrawer
open={open}
Expand All @@ -51,6 +64,17 @@ const ModifiedSqlDrawer: React.FC<Props> = ({
})}
>
<Spin spinning={generateModifySqlPending}>
{hasAnyWarningSql && (
<Alert
data-testid="modified-sql-drop-create-warning-banner"
type="warning"
showIcon
message={t(
'dataSourceComparison.entry.modifiedSqlDrawer.dropCreateWarningBanner'
)}
style={{ marginBottom: 12 }}
/>
)}
<ModifiedSqlAuditResult
auditResultCollapseActiveKeys={auditResultCollapseActiveKeys}
auditResultCollapseActiveKeysOnChange={
Expand Down
Loading
Loading