Skip to content
Merged

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
Banner,
Button,
css,
ErrorSummary,
FormFieldContainer,
Modal,
ModalBody,
Expand All @@ -29,9 +30,14 @@ import {
Option,
Select,
SelectTable,
spacing,
TextInput,
} from '@mongodb-js/compass-components';

const footerStyles = css({
gap: spacing[200],
});

const FormStepContainer: React.FunctionComponent<{
title: string;
description?: string;
Expand All @@ -56,7 +62,7 @@ const FormStepContainer: React.FunctionComponent<{
<>
<ModalHeader title={title} subtitle={description}></ModalHeader>
<ModalBody>{children}</ModalBody>
<ModalFooter>
<ModalFooter className={footerStyles}>
<Button
onClick={onNextClick}
disabled={isNextDisabled}
Expand Down Expand Up @@ -90,6 +96,7 @@ type NewDiagramFormProps = {
selectedDatabase: string | null;
collections: string[];
selectedCollections: string[];
error: Error | null;

onCancel: () => void;
onNameChange: (name: string) => void;
Expand All @@ -115,6 +122,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
selectedDatabase,
collections,
selectedCollections,
error,
onCancel,
onNameChange,
onNameConfirm,
Expand Down Expand Up @@ -174,7 +182,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
onConfirmAction: onCollectionsSelectionConfirm,
confirmActionLabel: 'Generate',
isConfirmDisabled:
!selectedCollections || selectCollections.length === 0,
!selectedCollections || selectedCollections.length === 0,
onCancelAction: onDatabaseSelectCancel,
cancelLabel: 'Back',
};
Expand Down Expand Up @@ -203,7 +211,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
<TextInput
label="New data model name"
value={diagramName}
data-testId="new-diagram-name-input"
data-testid="new-diagram-name-input"
onChange={(e) => {
onNameChange(e.currentTarget.value);
}}
Expand All @@ -215,6 +223,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
<FormFieldContainer>
<Select
label=""
aria-label="Select connection"
value={selectedConnectionId ?? ''}
data-testid="new-diagram-connection-selector"
onChange={onConnectionSelect}
Expand All @@ -240,6 +249,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
<FormFieldContainer>
<Select
label=""
aria-label="Select database"
value={selectedDatabase ?? ''}
data-testid="new-diagram-database-selector"
onChange={onDatabaseSelect}
Expand Down Expand Up @@ -317,6 +327,7 @@ const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
isLoading={isLoading}
>
{formContent}
{error && <ErrorSummary errors={[error.message]} />}
</FormStepContainer>
</Modal>
);
Expand Down Expand Up @@ -351,6 +362,7 @@ export default connect(
selectedDatabase,
databaseCollections,
selectedCollections,
error,
} = state.generateDiagramWizard;
return {
isModalOpen: inProgress,
Expand All @@ -366,6 +378,7 @@ export default connect(
selectedDatabase,
collections: databaseCollections ?? [],
selectedCollections: selectedCollections ?? [],
error,
};
},
{
Expand Down
15 changes: 2 additions & 13 deletions packages/compass-data-modeling/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import { registerHadronPlugin } from 'hadron-app-registry';
import { preferencesLocator } from 'compass-preferences-model/provider';
import { connectionsLocator } from '@mongodb-js/compass-connections/provider';
import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider';
import { createLoggerLocator } from '@mongodb-js/compass-logging/provider';
import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces';
import DataModelingComponent from './components/data-modeling';
import reducer from './store/reducer';
import { mongoDBInstancesManagerLocator } from '@mongodb-js/compass-app-stores/provider';
import { dataModelStorageServiceLocator } from './provider';
import { activateDataModelingStore } from './store';

const DataModelingPlugin = registerHadronPlugin(
{
name: 'DataModeling',
component: DataModelingComponent,
activate(initialProps, services, { cleanup }) {
const cancelControllerRef = { current: null };
const store = createStore(
reducer,
applyMiddleware(
thunk.withExtraArgument({ ...services, cancelControllerRef })
)
);
return { store, deactivate: cleanup };
},
activate: activateDataModelingStore,
},
{
preferences: preferencesLocator,
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-data-modeling/src/provider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type DataModelStorageServiceState = {
items: MongoDBDataModelDescription[];
};

const noopDataModelStorageService: DataModelStorageServiceState &
export const noopDataModelStorageService: DataModelStorageServiceState &
DataModelStorage = {
status: 'INITIAL',
error: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ export function startAnalysis(
if (cancelController.signal.aborted) {
dispatch({ type: AnalysisProcessActionTypes.ANALYSIS_CANCELED });
} else {
services.logger.log.error(
services.logger.mongoLogId(1_001_000_350),
'DataModeling',
'Failed to analyze schema',
{ err }
);
dispatch({
type: AnalysisProcessActionTypes.ANALYSIS_FAILED,
error: err as Error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type GenerateDiagramWizardState = {
databaseCollections: string[] | null;
selectedCollections: string[] | null;
automaticallyInferRelations: boolean;
error: Error | null;
};

export enum GenerateDiagramWizardActionTypes {
Expand Down Expand Up @@ -97,6 +98,7 @@ export type DatabasesFetchedAction = {

export type ConnectionFailedAction = {
type: GenerateDiagramWizardActionTypes.CONNECTION_FAILED;
error: Error;
};

export type SelectDatabaseAction = {
Expand All @@ -121,6 +123,7 @@ export type CollectionsFetchedAction = {

export type CollectionsFetchFailedAction = {
type: GenerateDiagramWizardActionTypes.COLLECTIONS_FETCH_FAILED;
error: Error;
};

export type SelectCollectionsAction = {
Expand Down Expand Up @@ -151,13 +154,16 @@ export type GenerateDiagramWizardActions =
| CollectionsFetchedAction
| SelectCollectionsAction
| ToggleInferRelationsAction
| ConfirmSelectedCollectionsAction;
| ConfirmSelectedCollectionsAction
| CollectionsFetchFailedAction
| ConnectionFailedAction;

const INITIAL_STATE: GenerateDiagramWizardState = {
inProgress: false,
step: 'ENTER_NAME',
diagramName: '',
selectedConnectionId: null,
error: null,
connectionDatabases: null,
selectedDatabase: null,
databaseCollections: null,
Expand Down Expand Up @@ -186,13 +192,15 @@ export const generateDiagramWizardReducer: Reducer<
if (isAction(action, GenerateDiagramWizardActionTypes.CANCEL_CONFIRM_NAME)) {
return {
...state,
error: null,
step: 'ENTER_NAME',
};
}
if (isAction(action, GenerateDiagramWizardActionTypes.SELECT_CONNECTION)) {
return {
...state,
selectedConnectionId: action.id,
error: null,
};
}
if (
Expand Down Expand Up @@ -287,15 +295,25 @@ export const generateDiagramWizardReducer: Reducer<
if (isAction(action, GenerateDiagramWizardActionTypes.CONNECTION_FAILED)) {
return {
...state,
inProgress: false,
error: action.error,
step: 'SELECT_CONNECTION',
Copy link
Member

@Anemy Anemy May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually do want to close the wizard when connecting fails. This lets the user click the Review button on the connect error toast. It's something we have in the scope: https://docs.google.com/document/d/1DX1F7LWirV1WkVV3G4RNEyxoX64oRPBYsJbd2NgL8OY
Sorry for not having this explicitly on the ticket, that's a miss on my end.
We don't need to show any connection error here then, the toast handles that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case i wonder if we should start with name? We let user enter the name and then its kinda lost if we close the modal when connection fails. But as its explicit in scope, i'll revert this behaviour.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 9f5e3a1

Copy link
Collaborator

@paula-stacho paula-stacho May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's weird behavior, normally the user should be able to continue the flow.. could we offer them the review button from here? then the connection modal would open on top of it. but only if it's straightforward - it's not worth much effort for the name only (especially since it might end up being autogenerated)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking that would make more sense, but then that review button opens up a different modal. Maybe connect being first makes sense. We can follow up, good things raised.

};
}
if (
isAction(action, GenerateDiagramWizardActionTypes.COLLECTIONS_FETCH_FAILED)
) {
return {
...state,
inProgress: false,
error: action.error,
step: 'SELECT_DATABASE',
};
}
if (
isAction(action, GenerateDiagramWizardActionTypes.CONFIRM_SELECT_DATABASE)
) {
return {
...state,
step: 'LOADING_COLLECTIONS',
};
}
return state;
Expand Down Expand Up @@ -342,6 +360,13 @@ export function confirmSelectConnection(): DataModelingThunkAction<
return;
}
await services.connections.connect(connectionInfo);
// ConnectionsService.connect does not throw an error if it fails to establish a connection,
// so explicitly checking if error is in the connection item and throwing it.
const connectionError =
services.connections.getConnectionById(selectedConnectionId)?.error;
if (connectionError) {
throw connectionError;
}
dispatch({ type: GenerateDiagramWizardActionTypes.CONNECTION_CONNECTED });
const mongoDBInstance =
services.instanceManager.getMongoDBInstanceForConnection(
Expand All @@ -362,7 +387,16 @@ export function confirmSelectConnection(): DataModelingThunkAction<
}),
});
} catch (err) {
dispatch({ type: GenerateDiagramWizardActionTypes.CONNECTION_FAILED });
services.logger.log.error(
services.logger.mongoLogId(1_001_000_348),
'DataModeling',
'Failed to select connection',
{ err }
);
dispatch({
type: GenerateDiagramWizardActionTypes.CONNECTION_FAILED,
error: err as Error,
});
}
};
}
Expand Down Expand Up @@ -414,8 +448,15 @@ export function confirmSelectDatabase(): DataModelingThunkAction<
}),
});
} catch (err) {
services.logger.log.error(
services.logger.mongoLogId(1_001_000_349),
'DataModeling',
'Failed to select database',
{ err }
);
dispatch({
type: GenerateDiagramWizardActionTypes.COLLECTIONS_FETCH_FAILED,
error: err as Error,
});
}
};
Expand Down
36 changes: 36 additions & 0 deletions packages/compass-data-modeling/src/store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { PreferencesAccess } from 'compass-preferences-model/provider';
import type { ConnectionsService } from '@mongodb-js/compass-connections/provider';
import type { TrackFunction } from '@mongodb-js/compass-telemetry/provider';
import type { Logger } from '@mongodb-js/compass-logging/provider';
import type { MongoDBInstancesManager } from '@mongodb-js/compass-app-stores/provider';
import type { DataModelStorageService } from '../provider';
import { applyMiddleware, createStore } from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
import type { ActivateHelpers } from 'hadron-app-registry';

export type DataModelingStoreOptions = Record<string, unknown>;

export type DataModelingStoreServices = {
preferences: PreferencesAccess;
connections: ConnectionsService;
instanceManager: MongoDBInstancesManager;
dataModelStorage: DataModelStorageService;
track: TrackFunction;
logger: Logger;
};

export function activateDataModelingStore(
_: DataModelingStoreOptions,
services: DataModelingStoreServices,
{ cleanup }: ActivateHelpers
) {
const cancelControllerRef = { current: null };
const store = createStore(
reducer,
applyMiddleware(
thunk.withExtraArgument({ ...services, cancelControllerRef })
)
);
return { store, deactivate: cleanup };
}
21 changes: 6 additions & 15 deletions packages/compass-data-modeling/src/store/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,8 @@ import { analysisProcessReducer } from './analysis-process';
import type { DiagramActions, DiagramActionTypes } from './diagram';
import { diagramReducer } from './diagram';
import type { ThunkAction } from 'redux-thunk';
import type { PreferencesAccess } from 'compass-preferences-model/provider';
import type { ConnectionsService } from '@mongodb-js/compass-connections/provider';
import type { TrackFunction } from '@mongodb-js/compass-telemetry/provider';
import type { Logger } from '@mongodb-js/compass-logging/provider';
import type { MongoDBInstancesManager } from '@mongodb-js/compass-app-stores/provider';
import { stepReducer } from './step';
import type { DataModelStorageService } from '../provider';
import type { DataModelingStoreServices } from '.';

const reducer = combineReducers({
step: stepReducer,
Expand All @@ -40,18 +35,14 @@ export type DataModelingActionTypes =

export type DataModelingState = ReturnType<typeof reducer>;

export type DataModelingExtraArgs = DataModelingStoreServices & {
cancelControllerRef: { current: AbortController | null };
};

export type DataModelingThunkAction<R, A extends AnyAction> = ThunkAction<
R,
DataModelingState,
{
preferences: PreferencesAccess;
connections: ConnectionsService;
instanceManager: MongoDBInstancesManager;
dataModelStorage: DataModelStorageService;
track: TrackFunction;
logger: Logger;
cancelControllerRef: { current: AbortController | null };
},
DataModelingExtraArgs,
A
>;

Expand Down
Loading
Loading