Skip to content

Commit 5c192f6

Browse files
committed
WIP chore(compass-assistant): add performance insights entry point
WIP pending tests, some tweaking, adding the same for queries without index.
1 parent 8831559 commit 5c192f6

File tree

8 files changed

+125
-28
lines changed

8 files changed

+125
-28
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-aggregations/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"@mongodb-js/atlas-service": "^0.55.0",
6161
"@mongodb-js/compass-app-registry": "^9.4.19",
6262
"@mongodb-js/compass-app-stores": "^7.56.0",
63+
"@mongodb-js/compass-assistant": "^1.1.0",
6364
"@mongodb-js/compass-collection": "^4.69.0",
6465
"@mongodb-js/compass-components": "^1.48.0",
6566
"@mongodb-js/compass-connections": "^1.70.0",

packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
useIsAIFeatureEnabled,
2828
} from 'compass-preferences-model/provider';
2929
import { showInput as showAIInput } from '../../../modules/pipeline-builder/pipeline-ai';
30+
import { useAssistantActions } from '@mongodb-js/compass-assistant';
3031

3132
const containerStyles = css({
3233
display: 'flex',
@@ -59,6 +60,8 @@ type PipelineActionsProps = {
5960

6061
showCollectionScanInsight?: boolean;
6162
onCollectionScanInsightActionButtonClick: () => void;
63+
64+
stages: string[];
6265
};
6366

6467
export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
@@ -80,12 +83,14 @@ export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
8083
onExplainAggregation,
8184
showCollectionScanInsight,
8285
onCollectionScanInsightActionButtonClick,
86+
stages,
8387
}) => {
8488
const enableAggregationBuilderExtraOptions = usePreference(
8589
'enableAggregationBuilderExtraOptions'
8690
);
8791
const showInsights = usePreference('showInsights');
8892
const isAIFeatureEnabled = useIsAIFeatureEnabled();
93+
const { tellMoreAboutProactiveInsights } = useAssistantActions();
8994

9095
return (
9196
<div className={containerStyles}>
@@ -99,6 +104,14 @@ export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
99104
...PerformanceSignals.get('aggregation-executed-without-index'),
100105
onPrimaryActionButtonClick:
101106
onCollectionScanInsightActionButtonClick,
107+
onAssistantButtonClick:
108+
tellMoreAboutProactiveInsights &&
109+
(() => {
110+
tellMoreAboutProactiveInsights({
111+
id: 'aggregation-executed-without-index',
112+
stages,
113+
});
114+
}),
102115
}}
103116
></SignalPopover>
104117
</div>
@@ -185,6 +198,7 @@ const mapState = (state: RootState) => {
185198
isUpdateViewButtonDisabled:
186199
!state.isModified || hasSyntaxErrors || isAIFetching,
187200
showCollectionScanInsight: state.insights.isCollectionScan,
201+
stages: resultPipeline,
188202
};
189203
};
190204

packages/compass-aggregations/src/modules/insights.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,19 @@ interface FetchExplainPlanSuccessAction {
2424
}
2525
export type InsightsAction = FetchExplainPlanSuccessAction;
2626

27-
const INITIAL_STATE = { isCollectionScan: false };
27+
const INITIAL_STATE = { isCollectionScan: false, explainPlan: null };
2828

29-
const reducer: Reducer<{ isCollectionScan: boolean }, Action> = (
30-
state = INITIAL_STATE,
31-
action
32-
) => {
29+
const reducer: Reducer<
30+
{ isCollectionScan: boolean; explainPlan: ExplainPlan | null },
31+
Action
32+
> = (state = INITIAL_STATE, action) => {
3333
if (
3434
isAction<FetchExplainPlanSuccessAction>(action, FETCH_EXPLAIN_PLAN_SUCCESS)
3535
) {
3636
return {
3737
...state,
3838
isCollectionScan: action.explainPlan.isCollectionScan,
39+
explainPlan: action.explainPlan,
3940
};
4041
}
4142
if (

packages/compass-assistant/src/compass-assistant-provider.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import { registerCompassPlugin } from '@mongodb-js/compass-app-registry';
66
import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider';
77
import { DocsProviderTransport } from './docs-provider-transport';
88
import { useDrawerActions } from '@mongodb-js/compass-components';
9-
import { buildExplainPlanPrompt } from './prompts';
9+
import {
10+
buildExplainPlanPrompt,
11+
buildProactiveInsightsPrompt,
12+
type ProactiveInsightsContext,
13+
} from './prompts';
1014
import { usePreference } from 'compass-preferences-model/provider';
1115

1216
export const ASSISTANT_DRAWER_ID = 'compass-assistant-drawer';
@@ -32,21 +36,27 @@ type AssistantActionsContextType = {
3236
namespace: string;
3337
explainPlan: string;
3438
}) => void;
39+
tellMoreAboutProactiveInsights: ({
40+
id,
41+
stages,
42+
}: ProactiveInsightsContext) => void;
3543
};
3644
export const AssistantActionsContext =
3745
createContext<AssistantActionsContextType>({
3846
interpretExplainPlan: () => {},
47+
tellMoreAboutProactiveInsights: () => {},
3948
});
4049

41-
export function useAssistantActions(): AssistantActionsContextType & {
42-
isAssistantEnabled: boolean;
43-
} {
50+
export function useAssistantActions(): Partial<AssistantActionsContextType> {
4451
const isAssistantEnabled = usePreference('enableAIAssistant');
4552

46-
return {
47-
...useContext(AssistantActionsContext),
48-
isAssistantEnabled,
49-
};
53+
const actions = useContext(AssistantActionsContext);
54+
55+
if (!isAssistantEnabled) {
56+
return {};
57+
}
58+
59+
return actions;
5060
}
5161

5262
export const AssistantProvider: React.FunctionComponent<
@@ -70,6 +80,13 @@ export const AssistantProvider: React.FunctionComponent<
7080
{}
7181
);
7282
},
83+
tellMoreAboutProactiveInsights: (context: ProactiveInsightsContext) => {
84+
openDrawer(ASSISTANT_DRAWER_ID);
85+
const { prompt } = buildProactiveInsightsPrompt(context);
86+
void chat.sendMessage({
87+
text: prompt,
88+
});
89+
},
7390
});
7491
const { openDrawer } = useDrawerActions();
7592

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,40 @@
1+
export type EntryPointMessage = {
2+
prompt: string;
3+
displayText?: string;
4+
};
5+
16
export const buildExplainPlanPrompt = ({
27
explainPlan,
38
}: {
49
explainPlan: string;
5-
}) => {
10+
}): EntryPointMessage => {
611
return {
712
prompt: `Given the MongoDB explain plan output below, provide a concise human readable explanation that explains the query execution plan and highlights aspects of the plan that might impact query performance. Respond with as much concision and clarity as possible.
813
If a clear optimization should be made, please suggest the optimization and describe how it can be accomplished in MongoDB Compass. Do not advise users to create indexes without weighing the pros and cons.
914
Explain output:
1015
${explainPlan}`,
11-
displayText: 'Provide an explanation of this explain plan.',
16+
displayText: 'Interpret this explain plan output for me.',
1217
};
1318
};
19+
20+
export type ProactiveInsightsContext = {
21+
id: 'aggregation-executed-without-index';
22+
stages: string[];
23+
};
24+
25+
export const buildProactiveInsightsPrompt = ({
26+
id,
27+
stages,
28+
}: ProactiveInsightsContext): EntryPointMessage => {
29+
switch (id) {
30+
case 'aggregation-executed-without-index':
31+
return {
32+
displayText:
33+
'Help me understand the performance impact of running queries without an index.',
34+
prompt: `What is the best index for this query?
35+
${stages.join('\n')}`,
36+
};
37+
default:
38+
throw new Error(`Unsupported signal id: ${id}`);
39+
}
40+
};

packages/compass-components/src/components/signal-popover.tsx

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type SignalTrackingHooks = {
2424
onSignalLinkClick(id: string): void;
2525
onSignalPrimaryActionClick(id: string): void;
2626
onSignalClose(id: string): void;
27+
onAssistantButtonClick(): void;
2728
};
2829

2930
const TrackingHooksContext = React.createContext<SignalTrackingHooks>({
@@ -42,6 +43,9 @@ const TrackingHooksContext = React.createContext<SignalTrackingHooks>({
4243
onSignalClose() {
4344
/** noop */
4445
},
46+
onAssistantButtonClick() {
47+
/** noop */
48+
},
4549
});
4650

4751
const SignalHooksProvider: React.FunctionComponent<
@@ -66,6 +70,7 @@ const SignalHooksProvider: React.FunctionComponent<
6670
onSignalClose(id: string) {
6771
hooksRef.current.onSignalClose?.(id);
6872
},
73+
onAssistantButtonClick: hooksRef.current.onAssistantButtonClick,
6974
};
7075
}, []);
7176

@@ -116,6 +121,11 @@ export type Signal = {
116121
* button click
117122
*/
118123
onPrimaryActionButtonClick?: React.MouseEventHandler;
124+
125+
/**
126+
* Optional, when provided will be called with a signal id and assistant context
127+
*/
128+
onAssistantButtonClick?: React.MouseEventHandler;
119129
};
120130

121131
type SignalPopoverProps = {
@@ -192,6 +202,7 @@ const SignalCard: React.FunctionComponent<
192202
primaryActionButtonIcon,
193203
primaryActionButtonVariant,
194204
primaryActionButtonLink,
205+
onAssistantButtonClick,
195206
darkMode: _darkMode,
196207
onPrimaryActionButtonClick,
197208
hasMultiSignals,
@@ -216,7 +227,18 @@ const SignalCard: React.FunctionComponent<
216227
{title}
217228
</strong>
218229
<Body as="div" baseFontSize={13} className={signalCardDescriptionStyles}>
219-
{description}
230+
{description}{' '}
231+
<Link
232+
data-testid="insight-signal-link"
233+
className={signalCardLearnMoreLinkStyles}
234+
href={learnMoreLink}
235+
target="_blank"
236+
onClick={() => {
237+
hooks.onSignalLinkClick(id);
238+
}}
239+
>
240+
{learnMoreLabel ?? 'Learn more'}
241+
</Link>
220242
</Body>
221243
<div className={signalCardActionGroupStyles}>
222244
{primaryActionButtonLabel && (
@@ -241,17 +263,20 @@ const SignalCard: React.FunctionComponent<
241263
{primaryActionButtonLabel}
242264
</Button>
243265
)}
244-
<Link
245-
data-testid="insight-signal-link"
246-
className={signalCardLearnMoreLinkStyles}
247-
href={learnMoreLink}
248-
target="_blank"
249-
onClick={() => {
250-
hooks.onSignalLinkClick(id);
251-
}}
252-
>
253-
{learnMoreLabel ?? 'Learn more'}
254-
</Link>
266+
{onAssistantButtonClick && (
267+
<Button
268+
size="small"
269+
variant="default"
270+
leftGlyph={
271+
// TODO(COMPASS-9384): Will be replaced with Sparkle gradient icon once Leafygreen components are updated.
272+
<Icon glyph="Sparkle" style={{ color: palette.green.dark1 }} />
273+
}
274+
data-testid="tell-me-more-button"
275+
onClick={onAssistantButtonClick}
276+
>
277+
Tell me more
278+
</Button>
279+
)}
255280
</div>
256281
</div>
257282
);
@@ -658,6 +683,7 @@ const SignalPopover: React.FunctionComponent<SignalPopoverProps> = ({
658683
)}
659684
<SignalCard
660685
{...currentSignal}
686+
onAssistantButtonClick={currentSignal.onAssistantButtonClick}
661687
darkMode={darkMode}
662688
hasMultiSignals={multiSignals}
663689
></SignalCard>

packages/compass-components/src/components/signals.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import React from 'react';
22
import type { Signal } from './signal-popover';
33

4-
const SIGNALS = [
4+
const SIGNALS: Pick<
5+
Signal,
6+
| 'id'
7+
| 'title'
8+
| 'description'
9+
| 'learnMoreLink'
10+
| 'primaryActionButtonLabel'
11+
| 'primaryActionButtonLink'
12+
| 'primaryActionButtonIcon'
13+
>[] = [
514
{
615
id: 'aggregation-executed-without-index',
716
title: 'Aggregation executed without index',

0 commit comments

Comments
 (0)