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 All @@ -42,6 +43,7 @@ export enum GenerateDiagramWizardActionTypes {

SELECT_DATABASE = 'data-modeling/generate-diagram-wizard/SELECT_DATABASE',
CONFIRM_SELECT_DATABASE = 'data-modeling/generate-diagram-wizard/CONFIRM_SELECT_DATABASE',
DATABASES_FETCH_FAILED = 'data-modeling/generate-diagram-wizard/DATABASES_FETCH_FAILED',
COLLECTIONS_FETCHED = 'data-modeling/generate-diagram-wizard/COLLECTIONS_FETCHED',
CANCEL_SELECTED_DATABASE = 'data-modeling/generate-diagram-wizard/CANCEL_SELECTED_DATABASE',
COLLECTIONS_FETCH_FAILED = 'data-modeling/generate-diagram-wizard/COLLECTIONS_FETCH_FAILED',
Expand Down Expand Up @@ -104,6 +106,11 @@ export type SelectDatabaseAction = {
database: string;
};

export type DatabasesFetchFailedAction = {
type: GenerateDiagramWizardActionTypes.DATABASES_FETCH_FAILED;
error: Error;
};

export type ConfirmSelectDatabaseAction = {
type: GenerateDiagramWizardActionTypes.CONFIRM_SELECT_DATABASE;
};
Expand All @@ -121,6 +128,7 @@ export type CollectionsFetchedAction = {

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

export type SelectCollectionsAction = {
Expand All @@ -147,17 +155,21 @@ export type GenerateDiagramWizardActions =
| DatabasesFetchedAction
| SelectDatabaseAction
| ConfirmSelectDatabaseAction
| DatabasesFetchFailedAction
| CancelSelectedDatabaseAction
| 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 +198,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 @@ -220,6 +234,15 @@ export const generateDiagramWizardReducer: Reducer<
selectedDatabase: null,
};
}
if (
isAction(action, GenerateDiagramWizardActionTypes.DATABASES_FETCH_FAILED)
) {
return {
...state,
step: 'SELECT_DATABASE',
error: action.error,
};
}
if (isAction(action, GenerateDiagramWizardActionTypes.SELECT_DATABASE)) {
return {
...state,
Expand Down Expand Up @@ -295,7 +318,16 @@ export const generateDiagramWizardReducer: Reducer<
) {
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 @@ -326,22 +358,45 @@ export function confirmSelectConnection(): DataModelingThunkAction<
| ConnectionConnectedAction
| DatabasesFetchedAction
| ConnectionFailedAction
| DatabasesFetchFailedAction
> {
return async (dispatch, getState, services) => {
dispatch({
type: GenerateDiagramWizardActionTypes.CONFIRM_SELECT_CONNECTION,
});

const { selectedConnectionId } = getState().generateDiagramWizard;
if (!selectedConnectionId) {
return;
}
try {
const { selectedConnectionId } = getState().generateDiagramWizard;
if (!selectedConnectionId) {
return;
}
const connectionInfo =
services.connections.getConnectionById(selectedConnectionId)?.info;
if (!connectionInfo) {
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;
}
} catch (err) {
services.logger.log.error(
services.logger.mongoLogId(1_001_000_348),
'DataModeling',
'Failed to select connection',
{ err }
);
dispatch({
type: GenerateDiagramWizardActionTypes.CONNECTION_FAILED,
});
return;
}

try {
dispatch({ type: GenerateDiagramWizardActionTypes.CONNECTION_CONNECTED });
const mongoDBInstance =
services.instanceManager.getMongoDBInstanceForConnection(
Expand All @@ -362,7 +417,16 @@ export function confirmSelectConnection(): DataModelingThunkAction<
}),
});
} catch (err) {
dispatch({ type: GenerateDiagramWizardActionTypes.CONNECTION_FAILED });
services.logger.log.error(
services.logger.mongoLogId(1_001_000_351),
'DataModeling',
'Failed to list databases',
{ err }
);
dispatch({
type: GenerateDiagramWizardActionTypes.DATABASES_FETCH_FAILED,
error: err as Error,
});
}
};
}
Expand Down Expand Up @@ -414,8 +478,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 };
}
Loading
Loading