Skip to content

Commit 87b3465

Browse files
authored
feat(sidebar) Connection info modal for the new sidebar COMPASS-5966 (#3394)
* Connection Modal * cleanup unneeded changes * more unused * try small modal, not default * fix test
1 parent 451a772 commit 87b3465

File tree

11 files changed

+249
-9
lines changed

11 files changed

+249
-9
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import React from 'react';
2+
import { connect } from 'react-redux';
3+
import { Modal, H3, Body, css, spacing } from '@mongodb-js/compass-components';
4+
import { ServerType, TopologyType } from 'mongodb-instance-model';
5+
import type { MongoDBInstance } from 'mongodb-instance-model';
6+
import type { ConnectionOptions } from '../modules/connection-options';
7+
import { ENTERPRISE, COMMUNITY } from '../constants/server-version';
8+
9+
type ConnectionInfo = {
10+
term: string;
11+
description: React.ReactChild;
12+
};
13+
14+
const infoContainer = css({
15+
margin: `${spacing[3]}px 0`,
16+
});
17+
18+
function InfoTerm({ children }: { children: React.ReactChild }) {
19+
return <Body weight="medium">{children}</Body>;
20+
}
21+
function InfoDescription({ children }: { children: React.ReactChild }) {
22+
return <Body>{children}</Body>;
23+
}
24+
25+
function Info({
26+
term,
27+
children,
28+
}: {
29+
term: React.ReactChild;
30+
children: React.ReactChild;
31+
}) {
32+
return (
33+
<div className={infoContainer}>
34+
<dt>
35+
<InfoTerm>{term}</InfoTerm>
36+
</dt>
37+
<dd>
38+
<InfoDescription>{children}</InfoDescription>
39+
</dd>
40+
</div>
41+
);
42+
}
43+
44+
export function ConnectionInfoModal({
45+
isVisible,
46+
close,
47+
infos,
48+
}: {
49+
isVisible: boolean;
50+
close: () => void;
51+
infos: ConnectionInfo[];
52+
}) {
53+
return (
54+
<Modal open={isVisible} setOpen={close} size="small">
55+
<H3>Connection info</H3>
56+
57+
<dl>
58+
{infos.map((info, i) => (
59+
<Info key={i} term={info.term}>
60+
{info.description}
61+
</Info>
62+
))}
63+
</dl>
64+
</Modal>
65+
);
66+
}
67+
68+
function getVersionDistro(isEnterprise?: boolean): string {
69+
// it is unknown until instance details are loaded
70+
if (typeof isEnterprise === 'undefined') {
71+
return '';
72+
}
73+
74+
return isEnterprise ? ENTERPRISE : COMMUNITY;
75+
}
76+
77+
type InfoParameters = {
78+
instance: MongoDBInstance;
79+
connectionInfo: ConnectionInfo;
80+
connectionOptions: ConnectionOptions;
81+
};
82+
83+
function getHostInfo({ instance }: InfoParameters): ConnectionInfo {
84+
const { type, servers } = instance.topologyDescription;
85+
86+
let heading = servers.length === 1 ? 'Host' : 'Hosts';
87+
if (type === TopologyType.LOAD_BALANCED) {
88+
heading += ' (Load Balancer)';
89+
}
90+
91+
const hosts =
92+
servers.length === 1 ? (
93+
servers[0].address
94+
) : (
95+
<div>
96+
{servers.map((server, i) => (
97+
<div key={i}>{server.address}</div>
98+
))}
99+
</div>
100+
);
101+
102+
return {
103+
term: heading,
104+
description: hosts,
105+
};
106+
}
107+
108+
function makeNodesInfo(
109+
numNodes: number,
110+
single: string,
111+
plural: string
112+
): string {
113+
return numNodes === 1 ? `1 ${single}` : `${numNodes} ${plural}`;
114+
}
115+
116+
function getClusterInfo({ instance }: InfoParameters): ConnectionInfo {
117+
const { type, setName, servers } = instance.topologyDescription;
118+
119+
let clusterType: string;
120+
let nodesInfo;
121+
switch (type) {
122+
case TopologyType.SHARDED:
123+
clusterType = 'Sharded';
124+
nodesInfo = makeNodesInfo(servers.length, 'Mongos', 'Mongoses');
125+
break;
126+
127+
case TopologyType.REPLICA_SET_NO_PRIMARY:
128+
case TopologyType.REPLICA_SET_WITH_PRIMARY:
129+
clusterType = `Replica Set ${setName}`;
130+
nodesInfo = makeNodesInfo(servers.length, 'Node', 'Nodes');
131+
break;
132+
133+
default:
134+
clusterType = ServerType.humanize(servers[0].type);
135+
break;
136+
}
137+
138+
return {
139+
term: 'Cluster',
140+
description: nodesInfo ? (
141+
<div>
142+
<div>{clusterType}</div>
143+
<div>{nodesInfo}</div>
144+
</div>
145+
) : (
146+
clusterType
147+
),
148+
};
149+
}
150+
151+
function getVersionInfo({ instance }: InfoParameters): ConnectionInfo {
152+
return {
153+
term: 'Edition',
154+
description: instance.dataLake.isDataLake
155+
? `Atlas Data Federation ${instance.dataLake.version ?? ''}`
156+
: `MongoDB ${instance.build.version} ${getVersionDistro(
157+
instance.build.isEnterprise
158+
)}`,
159+
};
160+
}
161+
162+
function getSSHTunnelInfo({
163+
connectionOptions,
164+
}: InfoParameters): ConnectionInfo {
165+
const { sshTunnelHostPortString } = connectionOptions;
166+
return {
167+
term: 'SSH Connection Via',
168+
description: sshTunnelHostPortString,
169+
};
170+
}
171+
172+
function getInfos(infoParameters: InfoParameters) {
173+
const infos: ConnectionInfo[] = [];
174+
175+
const { instance, connectionOptions } = infoParameters;
176+
177+
if (!instance) {
178+
return infos;
179+
}
180+
181+
infos.push(getHostInfo(infoParameters));
182+
183+
if (
184+
instance.dataLake.isDataLake === false &&
185+
instance.topologyDescription.type !== TopologyType.LOAD_BALANCED
186+
) {
187+
infos.push(getClusterInfo(infoParameters));
188+
}
189+
190+
infos.push(getVersionInfo(infoParameters));
191+
192+
if (connectionOptions.sshTunnel) {
193+
infos.push(getSSHTunnelInfo(infoParameters));
194+
}
195+
196+
return infos;
197+
}
198+
199+
const mapStateToProps = (state: {
200+
instance: MongoDBInstance;
201+
connectionInfo: {
202+
connectionInfo: ConnectionInfo;
203+
};
204+
connectionOptions: ConnectionOptions;
205+
}) => {
206+
const { instance, connectionOptions } = state;
207+
const { connectionInfo } = state.connectionInfo;
208+
209+
return {
210+
infos: getInfos({ instance, connectionInfo, connectionOptions }),
211+
};
212+
};
213+
214+
const MappedConnectionInfoModal = connect(
215+
mapStateToProps,
216+
{}
217+
)(ConnectionInfoModal);
218+
219+
export default MappedConnectionInfoModal;

packages/compass-sidebar/src/components/sidebar-title.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type Actions =
1717
| 'open-instance-workspace'
1818
| 'copy-connection-string'
1919
| 'edit-favorite'
20-
| 'cluster-info';
20+
| 'open-connection-info';
2121

2222
const titleLabel = css({
2323
overflow: 'hidden',
@@ -107,8 +107,8 @@ function SidebarTitle({
107107
});
108108

109109
actions.push({
110-
action: 'cluster-info',
111-
label: 'Cluster info',
110+
action: 'open-connection-info',
111+
label: 'Connection info',
112112
icon: 'Connect',
113113
});
114114

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SidebarTitle from './sidebar-title';
1515
import FavoriteIndicator from './favorite-indicator';
1616
import DBStats from './db-stats';
1717
import NavigationItems from './navigation-items';
18+
import ConnectionInfoModal from './connection-info-modal';
1819

1920
import { updateAndSaveConnectionInfo } from '../modules/connection-info';
2021

@@ -40,6 +41,8 @@ export function Sidebar({
4041
// TODO: non genuine warning label
4142

4243
const [isFavoriteModalVisible, setIsFavoriteModalVisible] = useState(false);
44+
const [isConnectionInfoModalVisible, setIsConnectionInfoModalVisible] =
45+
useState(false);
4346
const [isExpanded] = useState(true);
4447

4548
const onClickSaveFavorite = useCallback(
@@ -89,6 +92,11 @@ export function Sidebar({
8992
return;
9093
}
9194

95+
if (action === 'open-connection-info') {
96+
setIsConnectionInfoModalVisible(true);
97+
return;
98+
}
99+
92100
console.log(action, ...rest);
93101
globalAppRegistryEmit(action, ...rest);
94102
},
@@ -122,6 +130,11 @@ export function Sidebar({
122130
onCancelClicked={() => setIsFavoriteModalVisible(false)}
123131
onSaveClicked={(favoriteInfo) => onClickSaveFavorite(favoriteInfo)}
124132
/>
133+
134+
<ConnectionInfoModal
135+
isVisible={isConnectionInfoModalVisible}
136+
close={() => setIsConnectionInfoModalVisible(false)}
137+
/>
125138
</>
126139
</ResizableSidebar>
127140
);

packages/compass-sidebar/src/stores/store.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ store.onActivated = (appRegistry) => {
3030
topologyDescription: instance.topologyDescription.toJSON(),
3131
isWritable: instance.isWritable,
3232
env: instance.env,
33+
isAtlas: instance.isAtlas,
3334
})
3435
);
3536
};

packages/compass-sidebar/src/stores/store.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ describe('SidebarStore [Store]', function () {
7373
genuineMongoDB: {
7474
isGenuine: true,
7575
},
76+
isAtlas: false,
7677
isWritable: false,
7778
refreshingStatus: 'initial',
7879
topologyDescription: {

packages/data-service/src/instance-detail-helper.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ describe('instance-detail-helper', function () {
277277
const client = createMongoClientMock({
278278
hosts: [{ host: 'fakehost.my.server.com', port: 9999 }],
279279
commands: {
280-
atlasVersion: { version: '1.1.1', gitVersion: '1.2.3' },
280+
atlasVersion: { atlasVersion: '1.1.1', gitVersion: '1.2.3' },
281281
buildInfo: {},
282282
getCmdLineOpts: fixtures.CMD_LINE_OPTS,
283283
},

packages/data-service/src/instance-detail-helper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export async function getInstance(
148148
}).catch(() => null),
149149

150150
runCommand(adminDb, { atlasVersion: 1 }).catch(() => {
151-
return { version: '', gitVersion: '' };
151+
return { atlasVersion: '', gitVersion: '' };
152152
}),
153153
]);
154154

@@ -173,7 +173,7 @@ function checkIsAtlas(
173173
): boolean {
174174
const firstHost = client.options.hosts[0]?.host || '';
175175

176-
if (atlasVersionInfo.version === '') {
176+
if (atlasVersionInfo.atlasVersion === '') {
177177
return /mongodb(-dev)?\.net$/i.test(firstHost);
178178
}
179179
return true;

packages/data-service/src/run-command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ export type ListCollectionsOptionsNamesOnly = Omit<
203203
};
204204

205205
export type AtlasVersionInfo = {
206-
version: string;
206+
atlasVersion: string;
207207
gitVersion: string;
208208
};
209209

packages/instance-model/index.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import type Collection from 'mongodb-collection-model';
22
import type { DataService } from 'mongodb-data-service';
33
import type { Collection as DatabaseCollection } from 'mongodb-database-model';
44

5+
import { ServerType } from './server-type';
6+
import { TopologyType } from './topology-type';
7+
58
interface AuthInfo {
69
user: unknown | null;
710
roles: unknown[] | null;
@@ -36,6 +39,7 @@ interface DataLake {
3639

3740
interface Server {
3841
type: string
42+
address: string
3943
}
4044

4145
interface TopologyDescription {
@@ -55,6 +59,7 @@ declare class MongoDBInstanceProps {
5559
refreshingStatus: string;
5660
refreshingStatusError: string | null;
5761
isAtlas: boolean;
62+
atlasVersion: string;
5863
isRefreshing: boolean;
5964
isTopologyWritable: boolean;
6065
singleServerType: string | null;
@@ -96,4 +101,4 @@ declare class MongoDBInstance extends MongoDBInstanceProps {
96101
toJSON(opts?: { derived?: boolean }): this;
97102
}
98103

99-
export { MongoDBInstance, MongoDBInstanceProps };
104+
export { MongoDBInstance, MongoDBInstanceProps, ServerType, TopologyType };

packages/instance-model/lib/model.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ const InstanceModel = AmpersandModel.extend(
129129
refreshingStatus: { type: 'string', default: 'initial' },
130130
refreshingStatusError: { type: 'string', default: null },
131131
isAtlas: { type: 'boolean', default: false },
132+
atlasVersion: { type: 'string', default: '' },
132133
csfleMode: { type: 'string', default: 'unavailable' },
133134
topologyDescription: 'state'
134135
},

0 commit comments

Comments
 (0)