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
3 changes: 2 additions & 1 deletion chat2db-community-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"build": "yarn run build:web:community",
"build:prod": "yarn run build:web:community",
"build:web": "umi build",
"prebuild:web:community": "yarn test:community-boundary && yarn test:database-capabilities && yarn test:table-editor && yarn test:retired-ai && yarn test:tree-title-highlight && yarn test:tree-search-lifecycle && yarn test:driver-upload && yarn test:tree-loading && yarn test:tree-node-lookup && yarn test:tree-node-path && yarn test:data-source-authorization && yarn test:active-transactions && yarn test:data-source-mutation-refresh && yarn test:ai-model-config && yarn test:ai-model-select && yarn test:export-connections && yarn test:main-page-navigation && yarn test:shortcut && yarn test:console-tab-name && yarn test:workspace-tab-scroll && yarn test:workspace-split-lifecycle && yarn test:sql-completion-context && yarn test:file-manager-label && yarn test:local-file-encoding && yarn test:local-file-save && yarn test:local-file-tree-path && yarn test:saved-console-dirty && yarn test:editor-close && yarn test:result-set-ui && yarn test:result-pagination && yarn test:sql-execution-stream && yarn test:result-table-layout && yarn test:result-status && yarn test:data-source-identity && yarn test:data-source-execution-snapshot && yarn test:data-source-watermark && yarn test:hot-update && yarn test:task-center && yarn test:application-exit && yarn test:monaco-lifecycle && yarn test:result-set-editor && yarn test:account-grants-request",
"prebuild:web:community": "yarn test:community-boundary && yarn test:database-capabilities && yarn test:table-editor && yarn test:retired-ai && yarn test:tree-title-highlight && yarn test:tree-search-lifecycle && yarn test:driver-upload && yarn test:tree-loading && yarn test:tree-node-lookup && yarn test:tree-node-path && yarn test:data-source-authorization && yarn test:active-transactions && yarn test:data-source-mutation-refresh && yarn test:ai-model-config && yarn test:ai-model-select && yarn test:export-connections && yarn test:main-page-navigation && yarn test:shortcut && yarn test:console-tab-name && yarn test:workspace-tab-scroll && yarn test:workspace-split-lifecycle && yarn test:sql-completion-context && yarn test:file-manager-label && yarn test:local-file-encoding && yarn test:local-file-save && yarn test:local-file-tree-path && yarn test:saved-console-dirty && yarn test:editor-close && yarn test:result-set-ui && yarn test:result-pagination && yarn test:sql-execution-stream && yarn test:result-table-layout && yarn test:result-status && yarn test:data-source-identity && yarn test:data-source-execution-snapshot && yarn test:data-source-watermark && yarn test:hot-update && yarn test:task-center && yarn test:application-exit && yarn test:monaco-lifecycle && yarn test:result-set-editor && yarn test:account-grants-request && yarn test:dashboard-store",
"postbuild:web:community": "node ./scripts/verify-production-bundles.cjs",
"build:web:2java": "yarn run build:web:community && rm -rf ../chat2db-community-server/chat2db-community-start/src/main/resources/thymeleaf/* && cp -r dist/index.html ../chat2db-community-server/chat2db-community-start/src/main/resources/thymeleaf/",
"build:web:community": "cross-env UMI_ENV=community cross-env APP_NAME=chat2db-community cross-env APP_VERSION=${npm_config_app_version} cross-env PRINT_LOGS=${npm_config_print_logs} cross-env APP_PORT=${npm_config_app_port} umi build",
Expand All @@ -30,6 +30,7 @@
"test:retired-ai": "tsx src/blocks/AI/retiredAiSurface.test.ts && tsx src/blocks/AI/components/AIChatInput/mentionSelection.test.ts && tsx src/blocks/AI/knowledgeSelection.test.ts && tsx src/blocks/AI/messageNavigation.test.ts",
"test:hot-update": "tsx src/store/global/slices/hotUpdate/action.test.ts",
"test:active-transactions": "tsx src/blocks/NewTree/components/ActiveTransactionsContent/activeTransactionUtils.test.ts && tsx src/blocks/NewTree/treeMenuGrouping.test.ts",
"test:dashboard-store": "node scripts/run-dashboard-store-tests.cjs",
"test:result-pagination": "tsx src/blocks/SearchResult/components/ResultSet/pagination.test.ts",
"test:tree-node-path": "tsx src/blocks/NewTree/utils/treeNodePath.test.ts",
"test:ai-model-config": "tsx src/blocks/AI/components/AIModelConfigModal/modelConfigDefaults.test.ts",
Expand Down
25 changes: 25 additions & 0 deletions chat2db-community-client/scripts/run-dashboard-store-tests.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { existsSync } = require('node:fs');
const { spawnSync } = require('node:child_process');

const testFiles = [
'src/store/dashboard/slices/common/dashboardDetailRequest.test.ts',
'src/store/dashboard/slices/common/dashboardMutation.test.ts',
'src/store/dashboard/slices/common/refreshCurrentDashboard.test.ts',
'src/blocks/BI/ChartCardBox/DingChartModal/pinChartToDashboard.test.ts',
];
const tsxCli = require.resolve('tsx/cli');
let executed = 0;

for (const testFile of testFiles) {
if (!existsSync(testFile)) continue;
executed += 1;
const result = spawnSync(process.execPath, [tsxCli, testFile], { stdio: 'inherit' });
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}

if (!executed) {
console.error('No dashboard store tests were found.');
process.exit(1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
import i18n from '@/i18n';
import { staticMessage } from '@chat2db/ui';
import { filterSchemaByChartIds } from '@/utils/dashboard';
import { DashboardDetailRequestOwner } from './dashboardDetailRequest';

const dashboardDetailRequestOwner = new DashboardDetailRequestOwner();

export interface CommonAction {
/** Set up Dashboard list */
Expand Down Expand Up @@ -74,6 +77,7 @@ export const createCommonAction: StateCreator<DashboardStore, [['zustand/devtool
}
},
setCurrentDashboard: async (dashboard) => {
dashboardDetailRequestOwner.invalidate();
set({ currentDashboard: dashboard });
},
updateDashboard: (dashboard) => {
Expand Down Expand Up @@ -127,9 +131,10 @@ export const createCommonAction: StateCreator<DashboardStore, [['zustand/devtool
// history.pushState(null, '', `/dashboard/${id}`);
// }
// set({ currentDashboard: null });
return getDashboardById({ id }).then((res) => {
set({ currentDashboard: res });
});
return dashboardDetailRequestOwner.run(
() => getDashboardById({ id }),
(dashboard) => set({ currentDashboard: dashboard }),
);
},
refreshCurrentDashboard: () => {
const currentDashboardId = get().currentDashboard?.id;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import assert from 'node:assert/strict';
import { DashboardDetailRequestOwner } from './dashboardDetailRequest';

interface Deferred<T> {
promise: Promise<T>;
resolve: (value: T) => void;
}

function deferred<T>(): Deferred<T> {
let resolvePromise: ((value: T) => void) | undefined;
const promise = new Promise<T>((resolve) => {
resolvePromise = resolve;
});
return {
promise,
resolve: (value) => resolvePromise?.(value),
};
}

async function testLatestDashboardDetailOwnsCurrentState() {
const owner = new DashboardDetailRequestOwner();
const firstRequest = deferred<{ id: number }>();
const latestRequest = deferred<{ id: number }>();
let currentDashboard: { id: number } | null = null;
const commit = (dashboard: { id: number }) => {
currentDashboard = dashboard;
};

const firstLoad = owner.run(() => firstRequest.promise, commit);
const latestLoad = owner.run(() => latestRequest.promise, commit);
latestRequest.resolve({ id: 2 });
await latestLoad;
assert.equal(currentDashboard?.id, 2);

firstRequest.resolve({ id: 1 });
await firstLoad;
assert.equal(currentDashboard?.id, 2, 'an older response must not replace the latest dashboard');
}

async function testDirectSelectionInvalidatesPendingDetail() {
const owner = new DashboardDetailRequestOwner();
const request = deferred<{ id: number }>();
let currentDashboard: { id: number } | null = null;
const load = owner.run(
() => request.promise,
(dashboard) => {
currentDashboard = dashboard;
},
);

owner.invalidate();
currentDashboard = { id: 2 };
request.resolve({ id: 1 });
await load;

assert.equal(currentDashboard.id, 2, 'a direct selection must not be overwritten by an older request');
}

async function testLatestFailureStillInvalidatesOlderSuccess() {
const owner = new DashboardDetailRequestOwner();
const olderRequest = deferred<{ id: number }>();
let currentDashboard: { id: number } | null = null;
const olderLoad = owner.run(
() => olderRequest.promise,
(dashboard) => {
currentDashboard = dashboard;
},
);
const latestError = new Error('latest request failed');
await assert.rejects(
owner.run(
() => Promise.reject(latestError),
() => undefined,
),
latestError,
);

olderRequest.resolve({ id: 1 });
await olderLoad;
assert.equal(currentDashboard, null, 'an older success must stay stale after the latest request fails');
}

void Promise.all([
testLatestDashboardDetailOwnsCurrentState(),
testDirectSelectionInvalidatesPendingDetail(),
testLatestFailureStillInvalidatesOlderSuccess(),
])
.then(() => console.log('Dashboard detail request ownership tests passed'))
.catch((error) => {
console.error(error);
process.exitCode = 1;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export class DashboardDetailRequestOwner {
private generation = 0;

invalidate() {
this.generation += 1;
}

async run<T>(request: () => Promise<T>, commit: (result: T) => void): Promise<void> {
const generation = this.generation + 1;
this.generation = generation;
const result = await request();
if (this.generation === generation) {
commit(result);
}
}
}
Loading