mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-10 16:53:35 +08:00
🐛 fix(nacos-config): 修复配置发布监听误报
- 发布前停止当前监听,避免自发布事件触发远端变更提示 - 发布失败时恢复监听,成功后由详情刷新重建监听 - 移除瞬态 dirty 标签并补充发布时序回归测试
This commit is contained in:
@@ -48,6 +48,7 @@ const nacosBackend = vi.hoisted(() => ({
|
||||
NacosImportConfigs: vi.fn(),
|
||||
NacosDeleteConfig: vi.fn(),
|
||||
NacosGetConfig: vi.fn(),
|
||||
NacosPublishConfig: vi.fn(),
|
||||
NacosGetBetaConfig: vi.fn(),
|
||||
NacosStartConfigListen: vi.fn(),
|
||||
NacosStopConfigListen: vi.fn(),
|
||||
@@ -245,6 +246,7 @@ describe('NacosViewer config selection actions', () => {
|
||||
md5: 'test-md5',
|
||||
},
|
||||
});
|
||||
nacosBackend.NacosPublishConfig.mockResolvedValue({ success: true });
|
||||
nacosBackend.NacosGetBetaConfig.mockResolvedValue({
|
||||
success: true,
|
||||
data: { exists: false },
|
||||
@@ -775,6 +777,18 @@ describe('NacosViewer config selection actions', () => {
|
||||
expect(antdState.message.info).toHaveBeenCalledTimes(1);
|
||||
expect(renderedText(renderer!.toJSON())).not.toContain('Listening');
|
||||
|
||||
const compactBanner = renderer!.root.find(
|
||||
(node) => String(node.type) === 'alert',
|
||||
);
|
||||
expect(compactBanner.props.className).toBe('gn-v2-nacos-banner');
|
||||
expect(renderedText(compactBanner)).toContain('Remote config update detected');
|
||||
expect(renderedText(compactBanner)).toContain('Reload remote');
|
||||
expect(renderedText(compactBanner)).toContain('Dismiss');
|
||||
expect(renderedText(compactBanner)).not.toContain('Local draft is clean');
|
||||
expect(
|
||||
compactBanner.findByProps({ className: 'gn-v2-nacos-banner__copy' }),
|
||||
).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
findButtonByExactText(renderer!, 'Reload remote')!.props.onClick();
|
||||
});
|
||||
@@ -858,6 +872,135 @@ describe('NacosViewer config selection actions', () => {
|
||||
renderer!.unmount();
|
||||
});
|
||||
|
||||
it('does not flash an internal dirty badge while publishing edited content', async () => {
|
||||
const pendingPublish = deferred<any>();
|
||||
nacosBackend.NacosPublishConfig.mockReturnValue(pendingPublish.promise);
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<NacosViewer connectionId="nacos-1" namespaceId="dev" namespaceName="dev" />,
|
||||
);
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
await act(async () => {
|
||||
latestConfigTableProps().onRow(rows[0]).onClick();
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
await act(async () => {
|
||||
renderer!.root.find(
|
||||
(node) => (node.type as any) === 'nacos-editor',
|
||||
).props.onChange('server: changed');
|
||||
});
|
||||
|
||||
const publish = findButtonByExactText(renderer!, 'Publish');
|
||||
expect(publish?.props.disabled).toBe(false);
|
||||
expect(renderedText(renderer!.toJSON())).not.toContain('dirty');
|
||||
|
||||
act(() => {
|
||||
publish!.props.onClick();
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
expect(renderedText(renderer!.toJSON())).not.toContain('dirty');
|
||||
|
||||
pendingPublish.resolve({ success: true });
|
||||
await flushEffects();
|
||||
|
||||
expect(antdState.message.success).toHaveBeenCalledWith('Published successfully');
|
||||
expect(renderedText(renderer!.toJSON())).not.toContain('dirty');
|
||||
|
||||
await act(async () => {
|
||||
renderer!.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not report its own publish event as a remote config change', async () => {
|
||||
const pendingListReload = deferred<any>();
|
||||
let pageListCalls = 0;
|
||||
nacosBackend.NacosSearchConfigs.mockImplementation(
|
||||
async (_config: unknown, query: any) => {
|
||||
if (query?.pageSize === 50) {
|
||||
pageListCalls += 1;
|
||||
if (pageListCalls === 2) return pendingListReload.promise;
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
totalCount: rows.length,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: rows,
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
nacosBackend.NacosStartConfigListen.mockResolvedValue({
|
||||
success: true,
|
||||
data: { watchId: 'watch-publish' },
|
||||
});
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<NacosViewer connectionId="nacos-1" namespaceId="dev" namespaceName="dev" />,
|
||||
);
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
await act(async () => {
|
||||
latestConfigTableProps().onRow(rows[0]).onClick();
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
await act(async () => {
|
||||
renderer!.root.find(
|
||||
(node) => (node.type as any) === 'nacos-editor',
|
||||
).props.onChange('server: published locally');
|
||||
});
|
||||
act(() => {
|
||||
findButtonByExactText(renderer!, 'Publish')!.props.onClick();
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
expect(antdState.message.success).toHaveBeenCalledWith('Published successfully');
|
||||
|
||||
await act(async () => {
|
||||
runtimeState.configChangedHandler!({
|
||||
watchId: 'watch-publish',
|
||||
connectionId: 'nacos-1',
|
||||
namespaceId: 'dev',
|
||||
group: rows[0].group,
|
||||
dataId: rows[0].dataId,
|
||||
});
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
const infoCallsDuringPublish = antdState.message.info.mock.calls.length;
|
||||
const remoteBannerDuringPublish = renderedText(renderer!.toJSON()).includes(
|
||||
'Remote config update detected',
|
||||
);
|
||||
|
||||
pendingListReload.resolve({
|
||||
success: true,
|
||||
data: {
|
||||
totalCount: rows.length,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: rows,
|
||||
},
|
||||
});
|
||||
await flushEffects();
|
||||
await act(async () => {
|
||||
renderer!.unmount();
|
||||
});
|
||||
|
||||
expect(infoCallsDuringPublish).toBe(0);
|
||||
expect(remoteBannerDuringPublish).toBe(false);
|
||||
});
|
||||
|
||||
it('disables config import for restrictDataImport protection only', async () => {
|
||||
storeState.connections[0].config.protection.restrictDataImport = true;
|
||||
|
||||
|
||||
@@ -762,8 +762,12 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
message.warning(tr('nacos_viewer.message.beta_ips_required'));
|
||||
return;
|
||||
}
|
||||
const publishTarget = detail;
|
||||
const publishSelectionGeneration = selectionGenerationRef.current;
|
||||
let publishSucceeded = false;
|
||||
setPublishing(true);
|
||||
try {
|
||||
await stopListen();
|
||||
const res = await (window as any).go.app.App.NacosPublishConfig(rpcConfig, {
|
||||
namespaceId: namespaceId || '',
|
||||
dataId: detail.dataId,
|
||||
@@ -775,9 +779,11 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
betaIps: publishMode === 'beta' ? betaIps.trim() : '',
|
||||
});
|
||||
if (!res?.success) {
|
||||
void startListen(publishTarget, publishSelectionGeneration);
|
||||
message.error(res?.message || 'publish failed');
|
||||
return;
|
||||
}
|
||||
publishSucceeded = true;
|
||||
message.success(
|
||||
publishMode === 'beta'
|
||||
? tr('nacos_viewer.message.beta_publish_success')
|
||||
@@ -787,12 +793,15 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
setRemoteChanged(false);
|
||||
await loadList(pageNo);
|
||||
await loadDetail({
|
||||
dataId: detail.dataId,
|
||||
group: detail.group,
|
||||
dataId: publishTarget.dataId,
|
||||
group: publishTarget.group,
|
||||
type: draftType,
|
||||
});
|
||||
await loadBetaMeta({ dataId: detail.dataId, group: detail.group });
|
||||
await loadBetaMeta({ dataId: publishTarget.dataId, group: publishTarget.group });
|
||||
} catch (error: any) {
|
||||
if (!publishSucceeded) {
|
||||
void startListen(publishTarget, publishSelectionGeneration);
|
||||
}
|
||||
message.error(error?.message || String(error));
|
||||
} finally {
|
||||
setPublishing(false);
|
||||
@@ -1245,6 +1254,9 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
];
|
||||
|
||||
const namespaceLabel = namespaceName || (namespaceId ? namespaceId : 'public');
|
||||
const remoteChangedHint = draftDirty
|
||||
? tr('nacos_viewer.message.remote_changed_dirty_hint')
|
||||
: tr('nacos_viewer.message.remote_changed_clean_hint');
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1266,19 +1278,33 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
type="warning"
|
||||
showIcon
|
||||
className={isV2Ui ? 'gn-v2-nacos-banner' : undefined}
|
||||
style={isV2Ui ? { margin: 0, borderRadius: 0 } : undefined}
|
||||
message={tr('nacos_viewer.message.remote_changed_banner')}
|
||||
description={
|
||||
draftDirty
|
||||
? tr('nacos_viewer.message.remote_changed_dirty_hint')
|
||||
: tr('nacos_viewer.message.remote_changed_clean_hint')
|
||||
message={
|
||||
isV2Ui ? (
|
||||
<span className="gn-v2-nacos-banner__copy">
|
||||
<span className="gn-v2-nacos-banner__title">
|
||||
{tr('nacos_viewer.message.remote_changed_banner')}
|
||||
</span>
|
||||
{draftDirty ? (
|
||||
<span className="gn-v2-nacos-banner__hint" title={remoteChangedHint}>
|
||||
{remoteChangedHint}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : (
|
||||
tr('nacos_viewer.message.remote_changed_banner')
|
||||
)
|
||||
}
|
||||
description={isV2Ui ? undefined : remoteChangedHint}
|
||||
action={
|
||||
<Space>
|
||||
<Space size={4}>
|
||||
<Button size="small" type="primary" onClick={() => void handleReloadRemote()}>
|
||||
{tr('nacos_viewer.action.reload_remote')}
|
||||
</Button>
|
||||
<Button size="small" onClick={() => setRemoteChanged(false)}>
|
||||
<Button
|
||||
size="small"
|
||||
type={isV2Ui ? 'text' : 'default'}
|
||||
onClick={() => setRemoteChanged(false)}
|
||||
>
|
||||
{tr('nacos_viewer.action.dismiss_remote')}
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -1608,7 +1634,6 @@ const NacosViewer: React.FC<NacosViewerProps> = ({
|
||||
<Tag>{detail.group}</Tag>
|
||||
<strong style={{ color: workbenchTheme.textPrimary }}>{detail.dataId}</strong>
|
||||
{detail.md5 ? <Tag color="default">{detail.md5}</Tag> : null}
|
||||
{draftDirty ? <Tag color="orange">dirty</Tag> : null}
|
||||
{betaExists ? <Tag color="purple">{tr('nacos_viewer.status.beta_active')}</Tag> : null}
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user