diff --git a/packages/atlas-service/src/url-builders.spec.ts b/packages/atlas-service/src/url-builders.spec.ts index 56cc0bbb9af..75ea4c653e4 100644 --- a/packages/atlas-service/src/url-builders.spec.ts +++ b/packages/atlas-service/src/url-builders.spec.ts @@ -73,6 +73,17 @@ describe('url-builders', function () { `${TEST_ORIGIN}/v2/proj123#/settings/groupSettings` ); }); + + it('builds project settings url with params', function () { + expect( + buildProjectSettingsUrl({ + projectId: 'proj123', + params: { highlight: 'nativeReranking' }, + }) + ).to.equal( + `${TEST_ORIGIN}/v2/proj123#/settings/groupSettings?highlight=nativeReranking` + ); + }); }); describe('buildMonitoringUrl', function () { diff --git a/packages/atlas-service/src/url-builders.ts b/packages/atlas-service/src/url-builders.ts index 5830feb7620..7457c46e3db 100644 --- a/packages/atlas-service/src/url-builders.ts +++ b/packages/atlas-service/src/url-builders.ts @@ -16,9 +16,13 @@ export function buildPerformanceMetricsUrl({ export function buildProjectSettingsUrl({ projectId, -}: Pick): string { + params, +}: Pick & { + params?: Record; +}): string { const url = new URL(`/v2/${projectId}`, window.location.origin); - return `${url}#/settings/groupSettings`; + const query = params ? `?${new URLSearchParams(params)}` : ''; + return `${url}#/settings/groupSettings${query}`; } export function buildMonitoringUrl({ diff --git a/packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.spec.tsx b/packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.spec.tsx index a808eb4ba3d..94ec6b3f7c5 100644 --- a/packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.spec.tsx +++ b/packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.spec.tsx @@ -56,6 +56,7 @@ describe('FocusModeModalHeader', function () { onAddStageClick={noop} onStageSelect={noop} onStageDisabledToggleClick={noop} + onCloseFocusMode={noop} {...props} > ); diff --git a/packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.tsx b/packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.tsx index 5dcc6c0fcad..c3ee5f29d6b 100644 --- a/packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.tsx +++ b/packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.tsx @@ -3,6 +3,7 @@ import { Button, css, Icon, + Link, Menu, MenuItem, Option, @@ -19,6 +20,7 @@ import { connect } from 'react-redux'; import type { RootState } from '../../modules'; import { addStageInFocusMode, + disableFocusMode, selectFocusModeStage, } from '../../modules/focus-mode'; import { @@ -35,6 +37,8 @@ import { import type { ServerEnvironment } from '../../modules/env'; import { getIsRerankFirstStage } from '../../modules/pipeline-builder/builder-helpers'; import { useRerankInsight } from '../rerank-first-stage-banner'; +import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; +import { buildRerankTokenUsageUrl } from '@mongodb-js/atlas-service/provider'; type Stage = { idxInStore: number; @@ -57,6 +61,7 @@ type FocusModeModalHeaderProps = { onAddStageClick: (index: number) => void; onAddSearchStageBefore: (storeIndex: number) => void; onRefreshSearchIndexes: () => void; + onCloseFocusMode: () => void; }; const controlsContainerStyles = css({ @@ -116,9 +121,19 @@ export const FocusModeModalHeader: React.FunctionComponent< onStageDisabledToggleClick, onAddSearchStageBefore, onRefreshSearchIndexes, + onCloseFocusMode, }) => { const [menuOpen, setMenuOpen] = useState(false); const showInsights = usePreference('showInsights'); + const enableRerank = usePreference('enableRerank'); + const { atlasMetadata } = useConnectionInfo(); + + const viewTokenUsageHref = + enableRerank && stage?.stageOperator === '$rerank' + ? atlasMetadata + ? buildRerankTokenUsageUrl(atlasMetadata) + : 'https://dochub.mongodb.org/core/$rerank#metrics' + : null; const performanceInsight = useMemo(() => { if (stage) { @@ -142,7 +157,17 @@ export const FocusModeModalHeader: React.FunctionComponent< onAddSearchStageBefore: onAddSearchStageBeforeCurrentStage, }); - const insight = rerankInsight ?? performanceInsight; + const rawInsight = rerankInsight ?? performanceInsight; + const insight = useMemo(() => { + if (!rawInsight?.onAssistantButtonClick) return rawInsight; + return { + ...rawInsight, + onAssistantButtonClick: (e: React.MouseEvent) => { + onCloseFocusMode(); + rawInsight.onAssistantButtonClick!(e); + }, + }; + }, [rawInsight, onCloseFocusMode]); const onPopoverOpenChange = useCallback( (open: boolean) => { @@ -354,6 +379,16 @@ export const FocusModeModalHeader: React.FunctionComponent< + {viewTokenUsageHref && ( + + View token usage + + )} + {showInsights && insight && ( void; }; @@ -176,7 +173,6 @@ export const FocusMode: React.FunctionComponent = ({ isModalOpen, isAutoPreviewEnabled, showRerankFirstStageBanner, - showRerankTokensBanner, onCloseModal, }) => { return ( @@ -193,10 +189,10 @@ export const FocusMode: React.FunctionComponent = ({ {showRerankFirstStageBanner && ( - - )} - {showRerankTokensBanner && ( - + )} @@ -209,17 +205,11 @@ const mapState = (state: RootState) => { const { focusMode: { isEnabled, stageIndex }, autoPreview, - pipelineBuilder: { - stageEditor: { stages }, - }, } = state; - const currentStage = stages[stageIndex] as StoreStage | undefined; return { isModalOpen: isEnabled, isAutoPreviewEnabled: autoPreview, showRerankFirstStageBanner: getIsRerankFirstStage(state, stageIndex), - showRerankTokensBanner: - currentStage?.stageOperator === '$rerank' && !!autoPreview, }; }; diff --git a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/index.spec.tsx index 442f9a6e2f7..ca53b5d6662 100644 --- a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/index.spec.tsx @@ -14,7 +14,6 @@ const renderPipelineAsTextWorkspace = ( ); diff --git a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/index.tsx b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/index.tsx index b6006eed06a..67ce52c4af1 100644 --- a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/index.tsx @@ -6,11 +6,9 @@ import { Resizable } from 're-resizable'; import PipelineEditor from './pipeline-editor'; import PipelinePreview from './pipeline-preview'; import ResizeHandle from '../../resize-handle'; -import { RerankTokensBanner } from '../../rerank-tokens-banner'; import type { RootState } from '../../../modules'; import { RerankFirstStageBanner } from '../../rerank-first-stage-banner'; import { getIsRerankFirstStage } from '../../../modules/pipeline-builder/builder-helpers'; -import { getStageOperator } from '../../../utils/stage'; const outerContainerStyles = css({ display: 'flex', @@ -42,14 +40,13 @@ const workspaceContainerStyles = css({ type PipelineAsTextWorkspaceProps = { isAutoPreview: boolean; showRerankFirstStageBanner: boolean; - showRerankTokensBanner: boolean; }; const containerDataTestId = 'pipeline-as-text-workspace'; export const PipelineAsTextWorkspace: React.FunctionComponent< PipelineAsTextWorkspaceProps -> = ({ isAutoPreview, showRerankFirstStageBanner, showRerankTokensBanner }) => { +> = ({ isAutoPreview, showRerankFirstStageBanner }) => { if (!isAutoPreview) { return (
@@ -76,9 +73,6 @@ export const PipelineAsTextWorkspace: React.FunctionComponent< {showRerankFirstStageBanner && ( )} - {showRerankTokensBanner && ( - - )}
{ - const { - autoPreview, - pipelineBuilder: { - textEditor: { - pipeline: { pipeline }, - }, - }, - } = state; + const { autoPreview } = state; return { isAutoPreview: !!autoPreview, showRerankFirstStageBanner: getIsRerankFirstStage(state), - showRerankTokensBanner: - pipeline.some((s) => getStageOperator(s) === '$rerank') && !!autoPreview, }; }; diff --git a/packages/compass-aggregations/src/components/rerank-first-stage-banner.tsx b/packages/compass-aggregations/src/components/rerank-first-stage-banner.tsx index db8b44ff9e2..8a68918f858 100644 --- a/packages/compass-aggregations/src/components/rerank-first-stage-banner.tsx +++ b/packages/compass-aggregations/src/components/rerank-first-stage-banner.tsx @@ -132,6 +132,7 @@ const bannerContentStyles = css({ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', + gap: spacing[200], }); const bannerTextStyles = css({ @@ -142,20 +143,31 @@ const bannerTextStyles = css({ const bannerButtonStyles = css({ flexShrink: 0, whiteSpace: 'nowrap', - marginLeft: spacing[200], }); export const RerankFirstStageBanner = ({ 'data-testid': dataTestId, + onBeforeAssistantOpen, }: { 'data-testid'?: string; + onBeforeAssistantOpen?: () => void; }) => { const enableRerank = usePreference('enableRerank'); const [isDismissed, setIsDismissed] = usePersistedState( 'mongodb_compass_dismissed_rerank_first_stage_banner', false ); - const onInsightAction = useRerankInsightAction(); + const insightAction = useRerankInsightAction(); + const onInsightAction = useMemo( + () => + insightAction + ? () => { + onBeforeAssistantOpen?.(); + insightAction(); + } + : undefined, + [insightAction, onBeforeAssistantOpen] + ); if (!enableRerank || isDismissed) { return null; diff --git a/packages/compass-aggregations/src/components/rerank-tokens-banner.spec.tsx b/packages/compass-aggregations/src/components/rerank-tokens-banner.spec.tsx deleted file mode 100644 index 2bba1df1ad9..00000000000 --- a/packages/compass-aggregations/src/components/rerank-tokens-banner.spec.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; -import { expect } from 'chai'; -import { RerankTokensBanner } from './rerank-tokens-banner'; - -const DISMISSED_KEY = 'mongodb_compass_dismissed_rerank_tokens_banner'; - -describe('RerankTokensBanner', function () { - afterEach(function () { - localStorage.removeItem(DISMISSED_KEY); - }); - - it('does not render when enableRerank is false', function () { - render(, { - preferences: { enableRerank: false }, - }); - expect(screen.queryByTestId('rerank-tokens-banner')).to.not.exist; - }); - - it('renders when enableRerank is true', function () { - render(, { - preferences: { enableRerank: true }, - }); - expect(screen.getByTestId('rerank-tokens-banner')).to.exist; - expect(screen.getByText('$rerank consumes tokens')).to.exist; - }); - - it('dismisses when the close button is clicked', function () { - render(, { - preferences: { enableRerank: true }, - }); - expect(screen.getByTestId('rerank-tokens-banner')).to.exist; - - userEvent.click(screen.getByRole('button', { name: /close/i })); - - expect(screen.queryByTestId('rerank-tokens-banner')).to.not.exist; - }); - - it('does not render when already dismissed via localStorage', function () { - localStorage.setItem(DISMISSED_KEY, 'true'); - render(, { - preferences: { enableRerank: true }, - }); - expect(screen.queryByTestId('rerank-tokens-banner')).to.not.exist; - }); -}); diff --git a/packages/compass-aggregations/src/components/rerank-tokens-banner.tsx b/packages/compass-aggregations/src/components/rerank-tokens-banner.tsx deleted file mode 100644 index 0914a98ac2a..00000000000 --- a/packages/compass-aggregations/src/components/rerank-tokens-banner.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { Banner, css, usePersistedState } from '@mongodb-js/compass-components'; -import { usePreference } from 'compass-preferences-model/provider'; - -const bannerStyles = css({ - borderRadius: 0, - border: 'none', - '&::before': { - display: 'none', - }, -}); - -export const RerankTokensBanner = ({ - 'data-testid': dataTestId, -}: { - 'data-testid'?: string; -}) => { - const enableRerank = usePreference('enableRerank'); - const [isDismissed, setIsDismissed] = usePersistedState( - 'mongodb_compass_dismissed_rerank_tokens_banner', - false - ); - - if (!enableRerank || isDismissed) { - return null; - } - - return ( - setIsDismissed(true)} - > - $rerank consumes tokens -
- Turn off the preview or disable the stage to avoid running $rerank while - editing. -
- ); -}; diff --git a/packages/compass-aggregations/src/components/rerank-version-warning-banner.spec.tsx b/packages/compass-aggregations/src/components/rerank-version-warning-banner.spec.tsx index 7ecb2f52c2d..691002f232c 100644 --- a/packages/compass-aggregations/src/components/rerank-version-warning-banner.spec.tsx +++ b/packages/compass-aggregations/src/components/rerank-version-warning-banner.spec.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { screen, + userEvent, renderWithActiveConnection, } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; +import sinon from 'sinon'; import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { RerankVersionWarningBanner } from './rerank-version-warning-banner'; @@ -22,6 +24,16 @@ const nonAtlasConnectionInfo: ConnectionInfo = { }; describe('RerankVersionWarningBanner', function () { + let windowOpenStub: sinon.SinonStub; + + beforeEach(function () { + windowOpenStub = sinon.stub(window, 'open'); + }); + + afterEach(function () { + windowOpenStub.restore(); + }); + it('renders the version warning message', async function () { await renderWithActiveConnection( , @@ -31,27 +43,31 @@ describe('RerankVersionWarningBanner', function () { expect(screen.getByText(/Upgrade your cluster/)).to.exist; }); - it('shows Atlas upgrade link when atlasMetadata is present', async function () { + it('opens the Atlas upgrade page when atlasMetadata is present', async function () { await renderWithActiveConnection( , atlasConnectionInfo ); - const link = await screen.findByRole('link', { name: /Upgrade Cluster/i }); - expect(link).to.be.visible; - expect(link) - .to.have.attribute('href') - .that.includes('/clusters/edit/myCluster'); + const button = await screen.findByRole('button', { + name: /Upgrade Cluster/i, + }); + userEvent.click(button); + expect(windowOpenStub).to.have.been.calledOnce; + expect(windowOpenStub.firstCall.args[0]).to.include( + '/clusters/edit/myCluster' + ); }); - it('shows docs link when atlasMetadata is not present', async function () { + it('opens the docs page when atlasMetadata is not present', async function () { await renderWithActiveConnection( , nonAtlasConnectionInfo ); - const link = await screen.findByRole('link', { name: /Upgrade Cluster/i }); - expect(link).to.be.visible; - expect(link).to.have.attribute( - 'href', + const button = await screen.findByRole('button', { + name: /Upgrade Cluster/i, + }); + userEvent.click(button); + expect(windowOpenStub).to.have.been.calledOnceWith( 'https://www.mongodb.com/docs/atlas/tutorial/major-version-change/' ); }); diff --git a/packages/compass-aggregations/src/components/rerank-version-warning-banner.tsx b/packages/compass-aggregations/src/components/rerank-version-warning-banner.tsx index 1c133e3ebfb..5545a634a4e 100644 --- a/packages/compass-aggregations/src/components/rerank-version-warning-banner.tsx +++ b/packages/compass-aggregations/src/components/rerank-version-warning-banner.tsx @@ -42,8 +42,9 @@ export const RerankVersionWarningBanner = ({ diff --git a/packages/compass-aggregations/src/components/stage.tsx b/packages/compass-aggregations/src/components/stage.tsx index 920a49c2bcb..41aeec7e531 100644 --- a/packages/compass-aggregations/src/components/stage.tsx +++ b/packages/compass-aggregations/src/components/stage.tsx @@ -17,7 +17,6 @@ import ResizeHandle from './resize-handle'; import StageToolbar from './stage-toolbar'; import StageEditor from './stage-editor'; import { RerankFirstStageBanner } from './rerank-first-stage-banner'; -import { RerankTokensBanner } from './rerank-tokens-banner'; import StagePreview from './stage-preview'; import { hasSyntaxError } from '../utils/stage'; import type { EditorRef } from '@mongodb-js/compass-editor'; @@ -133,7 +132,6 @@ export type StageProps = SortableProps & { hasServerError: boolean; isAutoPreviewing?: boolean | undefined; showRerankFirstStageBanner: boolean; - showRerankTokensBanner: boolean; }; function Stage({ @@ -144,7 +142,6 @@ function Stage({ hasServerError, isAutoPreviewing, showRerankFirstStageBanner, - showRerankTokensBanner, ...sortableProps }: StageProps) { const editorRef = useRef(null); @@ -191,9 +188,6 @@ function Stage({ {showRerankFirstStageBanner && ( )} - {showRerankTokensBanner && ( - - )} {isExpanded && (
{ hasServerError: !!stage.serverError, isAutoPreviewing: state.autoPreview, showRerankFirstStageBanner: getIsRerankFirstStage(state, ownProps.index), - showRerankTokensBanner: - stage.stageOperator === '$rerank' && !!state.autoPreview, }; })(Stage); diff --git a/packages/compass-assistant/src/prompts.ts b/packages/compass-assistant/src/prompts.ts index 57678d883e9..62562d660ed 100644 --- a/packages/compass-assistant/src/prompts.ts +++ b/packages/compass-assistant/src/prompts.ts @@ -204,9 +204,17 @@ ${context.query} }; case 'rerank-first-stage': return { - prompt: `Why you should use $rerank after a search stage`, + prompt: `The given MongoDB aggregation pipeline uses $rerank as the first stage. Provide a concise, human-readable explanation of best practices for using $rerank effectively and efficiently. +Your explanation must cover the following points: + +- $rerank should follow an initial retrieval stage such as $vectorSearch or $search, and explain why. +- Use $rerank.numDocsToRerank to limit how many documents are passed to the reranker, and explain the performance tradeoff of reranking more vs. fewer documents. +- Only include fields in $rerank.path that are actually used for reranking — unnecessary fields increase payload size and latency without improving results. + +Where relevant, flag if the pipeline in question does not follow these practices. +Respond with as much concision and clarity as possible. Do not recommend changes without briefly explaining the tradeoff.`, metadata: { - displayText: 'Why you should use $rerank after a search stage', + displayText: 'What are best practices for using $rerank?', }, }; } diff --git a/packages/compass-e2e-tests/tests/assistant-mocked.test.ts b/packages/compass-e2e-tests/tests/assistant-mocked.test.ts index 36cbcda10bd..804d8d20fd5 100644 --- a/packages/compass-e2e-tests/tests/assistant-mocked.test.ts +++ b/packages/compass-e2e-tests/tests/assistant-mocked.test.ts @@ -567,7 +567,7 @@ describe('MongoDB Assistant (with mocked backend)', function () { await browser.waitForMessages([ { - text: 'Why you should use $rerank after a search stage', + text: 'What are best practices for using $rerank?', role: 'user', }, { @@ -589,7 +589,7 @@ describe('MongoDB Assistant (with mocked backend)', function () { await browser.waitForMessages([ { - text: 'Why you should use $rerank after a search stage', + text: 'What are best practices for using $rerank?', role: 'user', }, {