diff --git a/packages/compass-connections/src/stores/connections-store-redux.ts b/packages/compass-connections/src/stores/connections-store-redux.ts index 80a84e2b2b5..2e29261282f 100644 --- a/packages/compass-connections/src/stores/connections-store-redux.ts +++ b/packages/compass-connections/src/stores/connections-store-redux.ts @@ -196,11 +196,7 @@ export type State = { } >; - // State related to connection editing form. Can't be null in single - // connection mode, so we always have a default connection set here even if - // nothing is being actively edited. Can be updated when single-connection - // mode is removed from the app - editingConnectionInfoId: ConnectionId; + editingConnectionInfoId: ConnectionId | null; isEditingConnectionInfoModalOpen: boolean; // State related to connection favorite fields editing modal form (right now @@ -487,23 +483,15 @@ export function createDefaultConnectionState( }; } -// For single connection mode we always have to start with the initial empty -// connection in the state already. This can be removed when single connection -// mode doesn't exist anymore -const INITIAL_CONNECTION_STATE = createDefaultConnectionState(); -INITIAL_CONNECTION_STATE.isBeingCreated = true; - const INITIAL_STATE: State = { connections: { - ids: [INITIAL_CONNECTION_STATE.info.id], - byId: { - [INITIAL_CONNECTION_STATE.info.id]: INITIAL_CONNECTION_STATE, - }, + ids: [], + byId: {}, status: 'initial', error: null, }, oidcDeviceAuthInfo: {}, - editingConnectionInfoId: INITIAL_CONNECTION_STATE.info.id, + editingConnectionInfoId: null, isEditingConnectionInfoModalOpen: false, editingConnectionFavoriteInfoId: null, isEditingConnectionFavoriteInfoModalOpen: false, @@ -513,13 +501,9 @@ export function getInitialConnectionsStateForConnectionInfos( connectionInfos: ConnectionInfo[] = [] ): State['connections'] { const byId = Object.fromEntries( - [ - [INITIAL_CONNECTION_STATE.info.id, INITIAL_CONNECTION_STATE] as const, - ].concat( - connectionInfos.map((info) => { - return [info.id, createDefaultConnectionState(info)]; - }) - ) + connectionInfos.map((info) => { + return [info.id, createDefaultConnectionState(info)]; + }) ); return { byId, @@ -985,28 +969,10 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { const newConnection = createDefaultConnectionState(); newConnection.isBeingCreated = true; - let newConnectionsState = state.connections; - - // Only relevant for single connections mode: if we're currently editing - // "new connection", clean it up from state before creating state for a new - // one - if ( - state.editingConnectionInfoId && - state.connections.byId[state.editingConnectionInfoId].isBeingCreated - ) { - newConnectionsState = { - ...newConnectionsState, - byId: { - ...newConnectionsState.byId, - }, - }; - delete newConnectionsState.byId[state.editingConnectionInfoId]; - } - return { ...state, connections: mergeConnectionStateById( - newConnectionsState, + state.connections, newConnection.info.id, newConnection ), @@ -1078,7 +1044,7 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { } let connections = state.connections; - let editingConnectionInfoId = state.editingConnectionInfoId; + const editingConnectionInfoId = state.editingConnectionInfoId; // In cases where connection was never saved or used before, we remove it // from the connections state @@ -1095,20 +1061,6 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { byId: newConnectionsById, ids: newIds, }; - - // Special case for single connection: after removing connection, we - // automatically create a new connection and will "select" it for editing. - // Can go away when single connection mode is removed - if (state.editingConnectionInfoId === action.connectionId) { - const newDefaultConnection = createDefaultConnectionState(); - newDefaultConnection.isBeingCreated = true; - connections = mergeConnectionStateById( - connections, - newDefaultConnection.info.id, - newDefaultConnection - ); - editingConnectionInfoId = newDefaultConnection.info.id; - } } return { diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index d2c50671caa..6b47914adf5 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -55,7 +55,7 @@ import type { } from './logger-and-telemetry'; import { useCompassWebLoggerAndTelemetry } from './logger-and-telemetry'; import { type TelemetryServiceOptions } from '@mongodb-js/compass-telemetry'; -import { WorkspaceTab as WelcomeWorkspaceTab } from '@mongodb-js/compass-welcome'; +import { WebWorkspaceTab as WelcomeWorkspaceTab } from '@mongodb-js/compass-welcome'; import { useCompassWebPreferences } from './preferences'; const WithAtlasProviders: React.FC = ({ children }) => { diff --git a/packages/compass-welcome/src/components/desktop-welcome-tab.spec.tsx b/packages/compass-welcome/src/components/desktop-welcome-tab.spec.tsx new file mode 100644 index 00000000000..0f40d41ea67 --- /dev/null +++ b/packages/compass-welcome/src/components/desktop-welcome-tab.spec.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { + screen, + renderWithConnections, +} from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import DesktopWelcomeTab from './desktop-welcome-tab'; + +const renderDesktopWelcomeTab = ( + preferences: { + enableCreatingNewConnections?: boolean; + } = {} +) => { + renderWithConnections(, { + preferences, + }); +}; + +describe('DesktopWelcomeTab', function () { + it('renders with title', function () { + renderDesktopWelcomeTab(); + expect(screen.getByText('Welcome to MongoDB Compass')).to.exist; + }); + + it('does not render create cluster button when enableCreatingNewConnections is false', function () { + renderDesktopWelcomeTab({ enableCreatingNewConnections: false }); + try { + screen.getByTestId('add-new-connection-button'); + expect.fail('add-new-connection-button should not be rendered'); + } catch (e) { + // noop + } + }); + + context('when enableCreatingNewConnections is true', function () { + it('renders info text', function () { + renderDesktopWelcomeTab({ enableCreatingNewConnections: true }); + expect( + screen.getByText('To get started, connect to an existing server or') + ).to.exist; + }); + + it('renders create cluster button', function () { + renderDesktopWelcomeTab({ enableCreatingNewConnections: true }); + expect(screen.getByTestId('add-new-connection-button')).to.exist; + }); + + it('renders atlas help section', function () { + renderDesktopWelcomeTab({ enableCreatingNewConnections: true }); + expect(screen.getByTestId('welcome-tab-atlas-help-section')).to.exist; + }); + }); +}); diff --git a/packages/compass-welcome/src/components/desktop-welcome-tab.tsx b/packages/compass-welcome/src/components/desktop-welcome-tab.tsx new file mode 100644 index 00000000000..3ed676de7d2 --- /dev/null +++ b/packages/compass-welcome/src/components/desktop-welcome-tab.tsx @@ -0,0 +1,152 @@ +import React from 'react'; + +import { + Button, + ButtonSize, + ButtonVariant, + Subtitle, + H3, + Body, + Link, + spacing, + palette, + css, + cx, + useDarkMode, + Icon, +} from '@mongodb-js/compass-components'; +import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; +import { useConnectionActions } from '@mongodb-js/compass-connections/provider'; +import { usePreference } from 'compass-preferences-model/provider'; +import { WelcomeTabImage } from './welcome-image'; + +const sectionContainerStyles = css({ + margin: 0, + padding: spacing[4], + paddingBottom: 0, + maxWidth: '450px', + borderRadius: spacing[200], +}); + +const atlasContainerStyles = css({ + backgroundColor: palette.green.light3, + border: `1px solid ${palette.green.light2}`, + paddingBottom: spacing[600], +}); + +const atlasContainerDarkModeStyles = css({ + backgroundColor: palette.green.dark3, + borderColor: palette.green.dark2, +}); + +const titleStyles = css({ + fontSize: '14px', +}); + +const descriptionStyles = css({ + marginTop: spacing[2], +}); + +const createClusterContainerStyles = css({ + marginTop: spacing[2], +}); + +const createClusterButtonStyles = css({ + fontWeight: 'bold', +}); + +const createClusterButtonLightModeStyles = css({ + background: palette.white, + '&:hover': { + background: palette.white, + }, + '&:focus': { + background: palette.white, + }, +}); + +function AtlasHelpSection(): React.ReactElement { + const track = useTelemetry(); + const darkMode = useDarkMode(); + + return ( +
+ + New to Compass and don't have a cluster? + + + If you don't already have a cluster, you can create one for free + using{' '} + + MongoDB Atlas + + +
+ +
+
+ ); +} + +const welcomeTabStyles = css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + margin: '0 auto', + gap: spacing[200], +}); + +const firstConnectionBtnStyles = css({ + margin: `${spacing[400]}px 0`, +}); + +export default function DesktopWelcomeTab() { + const { createNewConnection } = useConnectionActions(); + const enableCreatingNewConnections = usePreference( + 'enableCreatingNewConnections' + ); + + return ( +
+ +
+

Welcome to MongoDB Compass

+ {enableCreatingNewConnections && ( + <> + To get started, connect to an existing server or + + + + )} +
+
+ ); +} diff --git a/packages/compass-welcome/src/components/index.ts b/packages/compass-welcome/src/components/index.ts index b69e223fe5e..c04b7e42a1f 100644 --- a/packages/compass-welcome/src/components/index.ts +++ b/packages/compass-welcome/src/components/index.ts @@ -1,4 +1,5 @@ import WelcomeModal from './modal'; -import WelcomeTab from './welcome-tab'; +import DesktopWelcomeTab from './desktop-welcome-tab'; +import WebWelcomeTab from './web-welcome-tab'; -export { WelcomeModal, WelcomeTab }; +export { WelcomeModal, DesktopWelcomeTab, WebWelcomeTab }; diff --git a/packages/compass-welcome/src/components/modal.tsx b/packages/compass-welcome/src/components/modal.tsx index b444c3fefe3..eb37a52ff5b 100644 --- a/packages/compass-welcome/src/components/modal.tsx +++ b/packages/compass-welcome/src/components/modal.tsx @@ -10,7 +10,7 @@ import { } from '@mongodb-js/compass-components'; import { withPreferences } from 'compass-preferences-model/provider'; -import WelcomeImage from './welcome-image'; +import { WelcomeModalImage } from './welcome-image'; const disclaimer = css({ padding: `0 ${spacing[900]}px`, @@ -69,7 +69,7 @@ export const WelcomeModal: React.FunctionComponent = ({ ) : undefined } - graphic={} + graphic={} linkText={''} darkMode={darkMode} > diff --git a/packages/compass-welcome/src/components/web-welcome-tab.spec.tsx b/packages/compass-welcome/src/components/web-welcome-tab.spec.tsx new file mode 100644 index 00000000000..2aad8e617c4 --- /dev/null +++ b/packages/compass-welcome/src/components/web-welcome-tab.spec.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { + screen, + renderWithConnections, +} from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import WebWelcomeTab from './web-welcome-tab'; +import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; + +const CONNECTION_ITEM = { + id: '1', + connectionOptions: { connectionString: 'mongodb://localhost:27017' }, +}; + +const renderWebWelcomeTab = (connections: ConnectionInfo[] = []) => { + renderWithConnections(, { + connections, + }); +}; + +describe('WebWelcomeTab', function () { + it('renders with title', function () { + renderWebWelcomeTab(); + expect(screen.getByText('Welcome! Explore')).to.exist; + }); + + context('with no connections', function () { + it('renders info text', function () { + renderWebWelcomeTab(); + expect( + screen.getByText('To get started, create your first MongoDB Cluster.') + ).to.exist; + }); + it('renders with create cluster button', function () { + renderWebWelcomeTab(); + expect(screen.getByTestId('add-new-atlas-cluster-button')).to.exist; + }); + it('renders help text', function () { + renderWebWelcomeTab(); + expect(screen.getByText('Need more help?')).to.exist; + expect(screen.getByText('View documentation')).to.exist; + }); + }); + + context('with at least one connection', function () { + it('renders info text', function () { + renderWebWelcomeTab([CONNECTION_ITEM]); + expect( + screen.getByText('To get started, connect to an existing cluster.') + ).to.exist; + }); + it('does not render create cluster button', function () { + renderWebWelcomeTab([CONNECTION_ITEM]); + try { + screen.getByTestId('add-new-atlas-cluster-button'); + expect.fail('add-new-atlas-cluster-button should not be rendered'); + } catch (e) { + // noop + } + }); + }); +}); diff --git a/packages/compass-welcome/src/components/web-welcome-tab.tsx b/packages/compass-welcome/src/components/web-welcome-tab.tsx new file mode 100644 index 00000000000..775abc4c37a --- /dev/null +++ b/packages/compass-welcome/src/components/web-welcome-tab.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { + Button, + ButtonVariant, + H3, + spacing, + css, + Body, + Link, +} from '@mongodb-js/compass-components'; +import { useConnectionIds } from '@mongodb-js/compass-connections/provider'; +import { WelcomeTabImage } from './welcome-image'; + +const welcomeTabStyles = css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + margin: '0 auto', + gap: spacing[200], +}); + +const contentBodyStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[400], + alignItems: 'flex-start', +}); + +export default function WebWelcomeTab() { + const numConnections = useConnectionIds().length; + return ( +
+ +
+

Welcome! Explore

+
+ + {numConnections === 0 + ? 'To get started, create your first MongoDB Cluster.' + : 'To get started, connect to an existing cluster.'} + + {numConnections === 0 && ( + <> + + + Need more help?{' '} + + View documentation + + + + )} +
+
+
+ ); +} diff --git a/packages/compass-welcome/src/components/welcome-image.tsx b/packages/compass-welcome/src/components/welcome-image.tsx index b5f641fa24c..2c67b51c84f 100644 --- a/packages/compass-welcome/src/components/welcome-image.tsx +++ b/packages/compass-welcome/src/components/welcome-image.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type { SVGProps } from 'react'; -function WelcomeImage(props: SVGProps) { +export function WelcomeModalImage(props: SVGProps) { return ( @@ -327,4 +327,102 @@ function WelcomeImage(props: SVGProps) { ); } -export default WelcomeImage; +export function WelcomeTabImage(props: SVGProps) { + return ( + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/compass-welcome/src/components/welcome-tab.tsx b/packages/compass-welcome/src/components/welcome-tab.tsx deleted file mode 100644 index c37f1831dd6..00000000000 --- a/packages/compass-welcome/src/components/welcome-tab.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import React from 'react'; - -import { - Button, - ButtonSize, - ButtonVariant, - Subtitle, - H3, - Body, - Link, - spacing, - palette, - css, - cx, - useDarkMode, - Icon, -} from '@mongodb-js/compass-components'; -import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; -import { useConnectionActions } from '@mongodb-js/compass-connections/provider'; -import { usePreference } from 'compass-preferences-model/provider'; - -const sectionContainerStyles = css({ - margin: 0, - padding: spacing[4], - paddingBottom: 0, - maxWidth: '450px', - borderRadius: spacing[200], -}); - -const atlasContainerStyles = css({ - backgroundColor: palette.green.light3, - border: `1px solid ${palette.green.light2}`, - paddingBottom: spacing[600], -}); - -const atlasContainerDarkModeStyles = css({ - backgroundColor: palette.green.dark3, - borderColor: palette.green.dark2, -}); - -const titleStyles = css({ - fontSize: '14px', -}); - -const descriptionStyles = css({ - marginTop: spacing[2], -}); - -const createClusterContainerStyles = css({ - marginTop: spacing[2], -}); - -const createClusterButtonStyles = css({ - fontWeight: 'bold', -}); - -const createClusterButtonLightModeStyles = css({ - background: palette.white, - '&:hover': { - background: palette.white, - }, - '&:focus': { - background: palette.white, - }, -}); - -function AtlasHelpSection(): React.ReactElement { - const track = useTelemetry(); - const darkMode = useDarkMode(); - - return ( -
- - New to Compass and don't have a cluster? - - - If you don't already have a cluster, you can create one for free - using{' '} - - MongoDB Atlas - - -
- -
-
- ); -} - -const welcomeTabStyles = css({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - margin: '0 auto', - gap: spacing[200], -}); - -const firstConnectionBtnStyles = css({ - margin: `${spacing[400]}px 0`, -}); - -function WelcomeImage() { - return ( - - - - - - - - - - - - - - - - - ); -} - -export default function WelcomeTab() { - const { createNewConnection } = useConnectionActions(); - const enableCreatingNewConnections = usePreference( - 'enableCreatingNewConnections' - ); - - return ( -
-
- -
-
-

Welcome to MongoDB Compass

- {enableCreatingNewConnections ? ( - <> - To get started, connect to an existing server or - - - - ) : ( - To get started, connect to an existing server - )} -
-
- ); -} diff --git a/packages/compass-welcome/src/index.ts b/packages/compass-welcome/src/index.ts index 29c58d7259f..21822f46a58 100644 --- a/packages/compass-welcome/src/index.ts +++ b/packages/compass-welcome/src/index.ts @@ -2,7 +2,7 @@ import { registerHadronPlugin } from 'hadron-app-registry'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces'; -import { WelcomeModal, WelcomeTab } from './components'; +import { WelcomeModal, DesktopWelcomeTab, WebWelcomeTab } from './components'; import { activatePlugin } from './stores'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; @@ -12,20 +12,28 @@ const serviceLocators = { workspaces: workspacesServiceLocator, }; -export const WelcomePlugin = registerHadronPlugin( - { - name: 'Welcome', - component: WelcomeTab, - activate: activatePlugin, - }, - serviceLocators -); +export const DesktopWorkspaceTab: WorkspaceComponent<'Welcome'> = { + name: 'Welcome' as const, + component: registerHadronPlugin( + { + name: 'Welcome', + component: DesktopWelcomeTab, + activate: activatePlugin, + }, + serviceLocators + ), +}; -export const WorkspaceTab: WorkspaceComponent<'Welcome'> = { +export const WebWorkspaceTab: WorkspaceComponent<'Welcome'> = { name: 'Welcome' as const, - component: WelcomePlugin, + component: registerHadronPlugin( + { + name: 'Welcome', + component: WebWelcomeTab, + activate: activatePlugin, + }, + serviceLocators + ), }; export { WelcomeModal }; - -export default WelcomePlugin; diff --git a/packages/compass/src/app/components/workspace.tsx b/packages/compass/src/app/components/workspace.tsx index b8294e25708..bf8ab9aea61 100644 --- a/packages/compass/src/app/components/workspace.tsx +++ b/packages/compass/src/app/components/workspace.tsx @@ -12,7 +12,7 @@ import type { import WorkspacesPlugin, { WorkspacesProvider, } from '@mongodb-js/compass-workspaces'; -import { WorkspaceTab as WelcomeWorkspace } from '@mongodb-js/compass-welcome'; +import { DesktopWorkspaceTab as WelcomeWorkspace } from '@mongodb-js/compass-welcome'; import { WorkspaceTab as MyQueriesWorkspace } from '@mongodb-js/compass-saved-aggregations-queries'; import { WorkspaceTab as PerformanceWorkspace } from '@mongodb-js/compass-serverstats'; import {