Skip to content
Merged
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
7 changes: 6 additions & 1 deletion src/documentdb/utils/connectionStringHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ import { DocumentDBConnectionString } from './DocumentDBConnectionString';
* @returns The text with credentials replaced by `<credentials>`
*/
export const redactCredentialsFromConnectionString = (text: string): string => {
// Matches the credentials portion (everything before the last '@' that follows the scheme)
// Matches the credentials portion (user:password before the '@' delimiter)
// in mongodb:// or mongodb+srv:// URIs.
//
// Note: Per RFC 3986, the '@' character in usernames or passwords MUST be
// percent-encoded as '%40'. This regex relies on that assumption — it stops
// at the first literal '@' after the scheme, which is the credential delimiter
// in any spec-compliant URI. Unencoded '@' in passwords is malformed input.
return text.replace(/(mongodb(?:\+srv)?:\/\/)[^\s@]*@/gi, '$1<credentials>@');
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,22 @@ export const QueryInsightsMain = (): JSX.Element => {
// AbortController ref for cancelling in-flight Stage 3 AI requests
const stage3AbortControllerRef = useRef<AbortController | null>(null);

// Timer ref for the delayed tips/error card shown during Stage 3 loading
const stage3TipsTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

// Feedback dialog state
const [feedbackDialogOpen, setFeedbackDialogOpen] = useState(false);
const [feedbackSentiment, setFeedbackSentiment] = useState<'positive' | 'negative'>('positive');

useEffect(() => {
return () => {
if (stage3TipsTimerRef.current !== null) {
clearTimeout(stage3TipsTimerRef.current);
stage3TipsTimerRef.current = null;
}
};
}, []);

/**
* Display error message to user for the given stage
* Only displays once per error state to avoid duplicate toasts
Expand Down Expand Up @@ -452,13 +464,19 @@ export const QueryInsightsMain = (): JSX.Element => {
// Transition to Stage 3 loading (this will reset UI flags)
transitionToStage(3, 'loading');

// Clear any pending tips/error card timer from a previous request
if (stage3TipsTimerRef.current) {
clearTimeout(stage3TipsTimerRef.current);
stage3TipsTimerRef.current = null;
}

// Check if Stage 2 has query execution errors
const hasExecutionError =
queryInsightsState.stage2Data?.concerns &&
queryInsightsState.stage2Data.concerns.some((concern) => concern.includes('Query Execution Failed'));

// Show appropriate card after 1 second delay
const timer = setTimeout(() => {
stage3TipsTimerRef.current = setTimeout(() => {
if (hasExecutionError) {
setShowErrorCard(true);
} else {
Expand Down Expand Up @@ -550,11 +568,15 @@ export const QueryInsightsMain = (): JSX.Element => {
...prev,
stage3Promise: promise,
}));

return () => clearTimeout(timer);
};

const handleCancelAI = () => {
// Clear any pending tips/error card timer to prevent stale UI after cancel
if (stage3TipsTimerRef.current) {
clearTimeout(stage3TipsTimerRef.current);
stage3TipsTimerRef.current = null;
}

// Abort the in-flight tRPC request so the server can stop work early
if (stage3AbortControllerRef.current) {
stage3AbortControllerRef.current.abort();
Expand Down