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 @@ -13,7 +13,7 @@
"build:desktop": "npm run build:web:desktop",
"build:prod": "npm run build:web:prod",
"build:web": "umi build",
"prebuild:web:community": "yarn test:runtime-edition-storage && yarn test:chat-answer-update && yarn test:tree-title-highlight && yarn test:tree-loading && yarn test:tree-node-lookup && yarn test:data-source-authorization && 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:console-tab-name && yarn test:file-manager-label && yarn test:local-file-encoding && yarn test:editor-close && yarn test:invoice-routing && yarn test:result-set-ui && 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:result-set-editor",
"prebuild:web:community": "yarn test:runtime-edition-storage && yarn test:chat-answer-update && yarn test:tree-title-highlight && yarn test:tree-loading && yarn test:tree-node-lookup && yarn test:data-source-authorization && 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:console-tab-name && yarn test:file-manager-label && yarn test:local-file-encoding && yarn test:editor-close && yarn test:invoice-routing && yarn test:result-set-ui && 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:result-set-editor && yarn test:account-grants-request",
"postbuild:web:community": "node ./scripts/verify-production-bundles.cjs",
"build:web:2java": "yarn run build:web:prod && 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:desktop": "cross-env UMI_ENV=desktop cross-env APP_NAME=chat2db-pro 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 Down Expand Up @@ -90,6 +90,7 @@
"test:task-center": "tsx src/store/importExport/taskCenterUtils.test.ts",
"test:workspace-tab-drag": "tsx src/pages/main/workspace/components/WorkspaceTabs/workspaceTabDrop.test.ts",
"test:verification-code-countdown": "tsx src/utils/verificationCodeCountdown.test.ts && tsx src/utils/latestRequest.test.ts",
"test:account-grants-request": "tsx src/pages/main/workspace/components/WorkspaceExtend/GlobalExtendComponents/accountGrantsRequest.test.ts",
"modify:package:name": "node ./scripts/modify-package-name.js",
"start": "APP_VERSION=${npm_config_app_version} UMI_DEV_SERVER_COMPRESS=none umi dev",
"start:desktop:hot": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_app_version} PORT=8888 umi dev",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import assert from 'node:assert/strict';
import { invalidateLatestRequest } from '@/utils/latestRequest';
import { loadLatestAccountGrants } from './accountGrantsRequest';

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

function deferred<T>(): Deferred<T> {
let resolve!: (value: T) => void;
let reject!: (error: unknown) => void;
const promise = new Promise<T>((resolvePromise, rejectPromise) => {
resolve = resolvePromise;
reject = rejectPromise;
});
return { promise, resolve, reject };
}

async function testStaleResponseCannotReplaceLatestAccount() {
const requestGenerationRef = { current: 0 };
const firstRequest = deferred<string[]>();
const secondRequest = deferred<string[]>();
let displayedGrants = ['initial'];
let settleCount = 0;

const runFirstRequest = loadLatestAccountGrants(
requestGenerationRef,
() => firstRequest.promise,
(grants) => {
displayedGrants = grants;
},
() => {
settleCount += 1;
},
);
const runSecondRequest = loadLatestAccountGrants(
requestGenerationRef,
() => secondRequest.promise,
(grants) => {
displayedGrants = grants;
},
() => {
settleCount += 1;
},
);

firstRequest.resolve(['old-account-grant']);
await runFirstRequest;

assert.deepEqual(displayedGrants, ['initial']);
assert.equal(settleCount, 0, 'an old request must not stop the latest request spinner');

secondRequest.resolve(['new-account-grant']);
await runSecondRequest;

assert.deepEqual(displayedGrants, ['new-account-grant']);
assert.equal(settleCount, 1);
}

async function testLatestFailureClearsGrantsAndSettles() {
const requestGenerationRef = { current: 0 };
let displayedGrants = ['previous-grant'];
let settled = false;

await loadLatestAccountGrants(
requestGenerationRef,
async () => {
throw new Error('request failed');
},
(grants) => {
displayedGrants = grants;
},
() => {
settled = true;
},
);

assert.deepEqual(displayedGrants, []);
assert.equal(settled, true);
}

async function testUnmountedRequestCannotUpdateState() {
const requestGenerationRef = { current: 0 };
const request = deferred<string[]>();
let updateCount = 0;
let settleCount = 0;

const runRequest = loadLatestAccountGrants(
requestGenerationRef,
() => request.promise,
() => {
updateCount += 1;
},
() => {
settleCount += 1;
},
);

invalidateLatestRequest(requestGenerationRef);
request.resolve(['ignored-grant']);
await runRequest;

assert.equal(updateCount, 0);
assert.equal(settleCount, 0);
}

Promise.all([
testStaleResponseCannotReplaceLatestAccount(),
testLatestFailureClearsGrantsAndSettles(),
testUnmountedRequestCannotUpdateState(),
])
.then(() => {
console.log('Account grants request tests passed');
})
.catch((error) => {
console.error(error);
process.exitCode = 1;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { beginLatestRequest, isLatestRequest, type RequestGenerationRef } from '@/utils/latestRequest';

type AccountGrantsLoader = () => Promise<string[] | undefined>;

export async function loadLatestAccountGrants(
requestGenerationRef: RequestGenerationRef,
loadGrants: AccountGrantsLoader,
updateGrants: (grants: string[]) => void,
settleLoading: () => void,
) {
const requestGeneration = beginLatestRequest(requestGenerationRef);

try {
const grants = await loadGrants();
if (isLatestRequest(requestGenerationRef, requestGeneration)) {
updateGrants(grants || []);
}
} catch (_error) {
if (isLatestRequest(requestGenerationRef, requestGeneration)) {
updateGrants([]);
}
} finally {
if (isLatestRequest(requestGenerationRef, requestGeneration)) {
settleLoading();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useWorkspaceStore } from '@/store/workspace';
import { useTreeStore } from '@/store/tree';
import { GlobalComponents } from '../config';
Expand All @@ -11,6 +11,8 @@ import { Spin } from 'antd';
import i18n from '@/i18n';
import type { TreeNodeData } from '@/typings/tree';
import accountAdminService from '@/service/accountAdmin';
import { invalidateLatestRequest } from '@/utils/latestRequest';
import { loadLatestAccountGrants } from './accountGrantsRequest';

const GlobalExtendComponents = () => {
const { styles } = useStyles();
Expand Down Expand Up @@ -101,28 +103,29 @@ const AccountGrants = ({ data }: AccountGrantsProps) => {
const { styles } = useStyles();
const [loading, setLoading] = useState(false);
const [grants, setGrants] = useState<string[]>([]);
const requestGenerationRef = useRef(0);

useEffect(() => {
if (!data?.dataSourceId || !data.user || !data.host) {
setGrants([]);
setLoading(false);
return;
}
setLoading(true);
accountAdminService
.grants({
dataSourceId: data.dataSourceId,
user: data.user,
host: data.host,
})
.then((res) => {
setGrants(res || []);
})
.catch(() => {
setGrants([]);
})
.finally(() => {
setLoading(false);
});
void loadLatestAccountGrants(
requestGenerationRef,
() =>
accountAdminService.grants({
dataSourceId: data.dataSourceId,
user: data.user,
host: data.host,
}),
setGrants,
() => setLoading(false),
);
return () => {
invalidateLatestRequest(requestGenerationRef);
};
}, [data?.dataSourceId, data?.user, data?.host]);

return (
Expand Down
Loading