Skip to content
Closed
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
6 changes: 3 additions & 3 deletions configs/eslint-config-compass/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@
"@babel/eslint-parser": "^7.14.3",
"@mongodb-js/eslint-config-devtools": "^0.9.9",
"@mongodb-js/eslint-plugin-compass": "^1.2.17",
"@typescript-eslint/eslint-plugin": "^8.43.0",
"@typescript-eslint/parser": "^8.43.0",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^8.57.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-chai-friendly": "^1.1.0",
"eslint-plugin-filename-rules": "^1.2.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-mocha": "^8.0.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0"
"eslint-plugin-react-hooks": "^7.0.1"
},
"scripts": {
"prettier": "prettier-compass",
Expand Down
7 changes: 7 additions & 0 deletions configs/testing-library-compass/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,12 @@ function createWrapper(
const StoreGetter: React.FunctionComponent = ({ children }) => {
const store = useStore();
const actions = useConnectionActions();
// We're breaking the rules of hooks on purpose here to expose the values
// outside of the render
/* eslint-disable react-hooks/immutability */
wrapperState.connectionsStore.getState = store.getState.bind(store);
wrapperState.connectionsStore.actions = actions;
/* eslint-enable react-hooks/immutability */
return <>{children}</>;
};
const logger = {
Expand Down Expand Up @@ -618,6 +622,9 @@ function createPluginWrapper<
) {
const ref: { current: PluginContext } = { current: {} as any };
function ComponentWithProvider({ children, ...props }: any) {
// We're breaking the rules of hooks on purpose here to expose the ref
// outside of the render
// eslint-disable-next-line react-hooks/immutability
const plugin = (ref.current = Plugin.useActivate(
initialPluginProps ?? ({} as any)
));
Expand Down
331 changes: 203 additions & 128 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@
"cheerio": "1.0.0-rc.10"
},
"@mongodb-js/eslint-config-devtools": {
"@typescript-eslint/eslint-plugin": "^8.43.0",
"@typescript-eslint/parser": "^8.43.0",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^8.57.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0"
"eslint-plugin-react-hooks": "^7.0.1"
},
"@leafygreen-ui/emotion": "^4.0.9",
"@leafygreen-ui/lib": "^15.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useDarkMode,
cx,
useRequiredURLSearchParams,
useCurrentValueRef,
} from '@mongodb-js/compass-components';
import {
createAggregationAutocompleter,
Expand Down Expand Up @@ -83,8 +84,7 @@ export const PipelineEditor: React.FunctionComponent<PipelineEditorProps> = ({
const track = useTelemetry();
const connectionInfoRef = useConnectionInfoRef();
const editorInitialValueRef = useRef<string>(pipelineText);
const editorCurrentValueRef = useRef<string>(pipelineText);
editorCurrentValueRef.current = pipelineText;
const editorCurrentValueRef = useCurrentValueRef<string>(pipelineText);

const { utmSource, utmMedium } = useRequiredURLSearchParams();

Expand Down Expand Up @@ -112,7 +112,7 @@ export const PipelineEditor: React.FunctionComponent<PipelineEditorProps> = ({
);
editorInitialValueRef.current = editorCurrentValueRef.current;
}
}, [num_stages, track, connectionInfoRef]);
}, [editorCurrentValueRef, track, num_stages, connectionInfoRef]);

const annotations: Annotation[] = useMemo(() => {
return syntaxErrors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Banner,
useDarkMode,
useRequiredURLSearchParams,
useCurrentValueRef,
} from '@mongodb-js/compass-components';
import {
changeStageValue,
Expand Down Expand Up @@ -99,8 +100,7 @@ export const StageEditor = ({
const connectionInfoRef = useConnectionInfoRef();
const darkMode = useDarkMode();
const editorInitialValueRef = useRef<string | null>(stageValue);
const editorCurrentValueRef = useRef<string | null>(stageValue);
editorCurrentValueRef.current = stageValue;
const editorCurrentValueRef = useCurrentValueRef<string | null>(stageValue);

const fields = useAutocompleteFields(namespace);

Expand Down Expand Up @@ -150,6 +150,7 @@ export const StageEditor = ({
editorInitialValueRef.current = editorCurrentValueRef.current;
}
}, [
editorCurrentValueRef,
track,
num_stages,
index,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('PipelinePreviewManager', function () {

const result = await Promise.allSettled([
previewManager.getPreviewForStage(0, 'test.test', []),
previewManager.cancelPreviewForStage(0),
Promise.resolve(previewManager.cancelPreviewForStage(0)),
]);

expect(result[0]).to.have.property('status', 'rejected');
Expand Down Expand Up @@ -108,7 +108,7 @@ describe('PipelinePreviewManager', function () {
previewManager.getPreviewForStage(2, 'test.test', []),
previewManager.getPreviewForStage(3, 'test.test', []),
previewManager.getPreviewForStage(4, 'test.test', []),
previewManager.clearQueue(1),
Promise.resolve(previewManager.clearQueue(1)),
]);

// Only pipeline for stage 0 was executed
Expand Down
4 changes: 2 additions & 2 deletions packages/compass-aggregations/src/modules/search-indexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { PipelineBuilderThunkAction } from '.';
import type { SearchIndex } from 'mongodb-data-service';
import { isAction } from '../utils/is-action';

enum SearchIndexesStatuses {
export enum SearchIndexesStatuses {
INITIAL = 'INITIAL',
LOADING = 'LOADING',
READY = 'READY',
Expand Down Expand Up @@ -39,7 +39,7 @@ export type SearchIndexesAction =
type State = {
isSearchIndexesSupported: boolean;
indexes: SearchIndex[];
status: SearchIndexesStatus;
status: SearchIndexesStatuses;
};

export const INITIAL_STATE: State = {
Expand Down
1 change: 1 addition & 0 deletions packages/compass-app-registry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"reformat": "npm run eslint . -- --fix && npm run prettier -- --write ."
},
"dependencies": {
"@mongodb-js/compass-components": "^1.56.0",
"eventemitter3": "^4.0.0",
"react": "^17.0.2",
"react-redux": "^8.1.3",
Expand Down
15 changes: 5 additions & 10 deletions packages/compass-app-registry/src/react-context.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import React, {
createContext,
useEffect,
useRef,
useContext,
useState,
} from 'react';
import React, { createContext, useEffect, useContext, useState } from 'react';
import { useInitialValue } from '@mongodb-js/compass-components';
import { globalAppRegistry, AppRegistry } from './app-registry';

/**
Expand Down Expand Up @@ -57,7 +52,7 @@ export function GlobalAppRegistryProvider({
value?: AppRegistry;
children?: React.ReactNode;
}) {
const appRegistry = useRef(value ?? globalAppRegistry).current;
const appRegistry = useInitialValue(value ?? globalAppRegistry);
return (
<GlobalAppRegistryContext.Provider value={appRegistry}>
{children}
Expand All @@ -73,11 +68,11 @@ export function AppRegistryProvider({
children,
...props
}: AppRegistryProviderProps) {
const initialPropsRef = useRef(props);
const initialProps = useInitialValue(props);
const {
localAppRegistry: initialLocalAppRegistry,
deactivateOnUnmount = true,
} = initialPropsRef.current;
} = initialProps;

const globalAppRegistry = useGlobalAppRegistry();
const isTopLevelProvider = useIsTopLevelProvider();
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-app-registry/src/register-plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function LegacyRefluxProvider({
}) {
const storeRef = useRef(store);
const [state, setState] = useState(() => {
return storeRef.current.state;
return store.state;
});

React.useEffect(() => {
Expand Down
4 changes: 4 additions & 0 deletions packages/compass-assistant/src/compass-assistant-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export const ClearChatButton: React.FunctionComponent<{
if (confirmed) {
await stop();
clearError();
// Instead of breaking React rules, we should probably expose the "clear"
// as an interface on the chat class. Otherwise it's kinda expected taht
// we "mutate" messages directly to update the state
// eslint-disable-next-line react-hooks/immutability
chat.messages = chat.messages.filter(
(message) => message.metadata?.isPermanent
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ const TestComponent: React.FunctionComponent<{

return (
<DrawerContentProvider>
{/* Breaking this rule is fine while none of the tests try to re-render the content */}
{/* eslint-disable-next-line react-hooks/static-components */}
<MockedProvider
originForPrompt="mongodb-compass"
appNameForPrompt="MongoDB Compass"
Expand All @@ -113,6 +115,8 @@ describe('useAssistantActions', function () {

return (
<DrawerContentProvider>
{/* Breaking this rule is fine while none of the tests try to re-render the content */}
{/* eslint-disable-next-line react-hooks/static-components */}
<MockedProvider
originForPrompt="mongodb-compass"
appNameForPrompt="MongoDB Compass"
Expand Down
106 changes: 56 additions & 50 deletions packages/compass-assistant/src/compass-assistant-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
atlasServiceLocator,
} from '@mongodb-js/atlas-service/provider';
import { DocsProviderTransport } from './docs-provider-transport';
import { useDrawerActions } from '@mongodb-js/compass-components';
import {
useDrawerActions,
useInitialValue,
} from '@mongodb-js/compass-components';
import {
buildConnectionErrorPrompt,
buildExplainPlanPrompt,
Expand Down Expand Up @@ -180,56 +183,12 @@ export const AssistantProvider: React.FunctionComponent<
const { openDrawer } = useDrawerActions();
const track = useTelemetry();

const createEntryPointHandler = useRef(function <T>(
entryPointName:
| 'explain plan'
| 'performance insights'
| 'connection error',
builder: (props: T) => EntryPointMessage
) {
return (props: T) => {
if (!assistantActionsContext.current.ensureOptInAndSend) {
return;
}

const { prompt, metadata } = builder(props);
void assistantActionsContext.current.ensureOptInAndSend(
{
text: prompt,
metadata: {
...metadata,
source: entryPointName,
},
},
{},
() => {
openDrawer(ASSISTANT_DRAWER_ID);

track('Assistant Entry Point Used', {
source: entryPointName,
});
}
);
};
}).current;
const assistantActionsContext = useRef<AssistantActionsContextType>({
interpretExplainPlan: createEntryPointHandler(
'explain plan',
buildExplainPlanPrompt
),
interpretConnectionError: createEntryPointHandler(
'connection error',
buildConnectionErrorPrompt
),
tellMoreAboutInsight: createEntryPointHandler(
'performance insights',
buildProactiveInsightsPrompt
),
ensureOptInAndSend: async (
const ensureOptInAndSend = useInitialValue(() => {
return async function (
message: SendMessage,
options: SendOptions,
callback: () => void
) => {
) {
try {
await atlasAiService.ensureAiFeatureAccess();
} catch {
Expand All @@ -246,12 +205,59 @@ export const AssistantProvider: React.FunctionComponent<
}

await chat.sendMessage(message, options);
},
};
});

const createEntryPointHandler = useInitialValue(() => {
return function <T>(
entryPointName:
| 'explain plan'
| 'performance insights'
| 'connection error',
builder: (props: T) => EntryPointMessage
) {
return function (props: T) {
const { prompt, metadata } = builder(props);
void ensureOptInAndSend(
{
text: prompt,
metadata: {
...metadata,
source: entryPointName,
},
},
{},
() => {
openDrawer(ASSISTANT_DRAWER_ID);

track('Assistant Entry Point Used', {
source: entryPointName,
});
}
);
};
};
});

const assistantActionsContext = useInitialValue<AssistantActionsContextType>({
interpretExplainPlan: createEntryPointHandler(
'explain plan',
buildExplainPlanPrompt
),
interpretConnectionError: createEntryPointHandler(
'connection error',
buildConnectionErrorPrompt
),
tellMoreAboutInsight: createEntryPointHandler(
'performance insights',
buildProactiveInsightsPrompt
),
ensureOptInAndSend,
});

return (
<AssistantContext.Provider value={chat}>
<AssistantActionsContext.Provider value={assistantActionsContext.current}>
<AssistantActionsContext.Provider value={assistantActionsContext}>
{children}
</AssistantActionsContext.Provider>
</AssistantContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useContext, useRef } from 'react';
import React, { useContext } from 'react';
import type { CollectionTabPluginMetadata } from '../modules/collection-tab';
import type { CompassPluginComponent } from '@mongodb-js/compass-app-registry';
import type { CollectionSubtab } from '@mongodb-js/compass-workspaces';
import { useInitialValue } from '@mongodb-js/compass-components';

export interface CollectionTabPlugin {
name: CollectionSubtab;
Expand All @@ -28,9 +29,9 @@ const CollectionTabComponentsContext =
export const CollectionTabsProvider: React.FunctionComponent<
Partial<CollectionTabComponentsProviderValue>
> = ({ children, ...props }) => {
const valueRef = useRef({ ...defaultComponents, ...props });
const valueRef = useInitialValue({ ...defaultComponents, ...props });
return (
<CollectionTabComponentsContext.Provider value={valueRef.current}>
<CollectionTabComponentsContext.Provider value={valueRef}>
{children}
</CollectionTabComponentsContext.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ const CollectionTab = ({
};

return (
// This component is not created in render, just accessed from context
// eslint-disable-next-line react-hooks/static-components
<QueryBarPlugin {...pluginProps}>
<CollectionTabWithMetadata
collectionMetadata={collectionMetadata}
Expand Down
Loading
Loading