diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx
index 902342e1ce0..d7331022a17 100644
--- a/packages/compass-global-writes/src/components/index.tsx
+++ b/packages/compass-global-writes/src/components/index.tsx
@@ -16,6 +16,7 @@ import ShardKeyInvalid from './states/shard-key-invalid';
import ShardKeyMismatch from './states/shard-key-mismatch';
import ShardingError from './states/sharding-error';
import IncompleteShardingSetup from './states/incomplete-sharding-setup';
+import LoadingError from './states/loading-error';
const containerStyles = css({
display: 'flex',
@@ -90,6 +91,10 @@ function ShardingStateView({
return ;
}
+ if (shardingStatus === ShardingStatuses.LOADING_ERROR) {
+ return ;
+ }
+
return null;
}
diff --git a/packages/compass-global-writes/src/components/states/loading-error.spec.tsx b/packages/compass-global-writes/src/components/states/loading-error.spec.tsx
new file mode 100644
index 00000000000..2ed220dd9bd
--- /dev/null
+++ b/packages/compass-global-writes/src/components/states/loading-error.spec.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { expect } from 'chai';
+import { screen } from '@mongodb-js/testing-library-compass';
+import { LoadingError } from './loading-error';
+import { renderWithStore } from '../../../tests/create-store';
+
+const error = 'Test failure';
+
+function renderWithProps(
+ props?: Partial>
+) {
+ return renderWithStore();
+}
+
+describe('LoadingError', function () {
+ it('renders the error', async function () {
+ await renderWithProps();
+ expect(screen.getByText(error)).to.exist;
+ });
+});
diff --git a/packages/compass-global-writes/src/components/states/loading-error.tsx b/packages/compass-global-writes/src/components/states/loading-error.tsx
new file mode 100644
index 00000000000..8a508c38316
--- /dev/null
+++ b/packages/compass-global-writes/src/components/states/loading-error.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { ErrorSummary } from '@mongodb-js/compass-components';
+import { connect } from 'react-redux';
+import { type RootState, ShardingStatuses } from '../../store/reducer';
+import { containerStyles } from '../common-styles';
+
+interface LoadingErrorProps {
+ error: string;
+}
+
+export function LoadingError({ error }: LoadingErrorProps) {
+ return (
+
+
+
+ );
+}
+
+export default connect((state: RootState) => {
+ if (state.status !== ShardingStatuses.LOADING_ERROR) {
+ throw new Error('Error not found in LoadingError');
+ }
+ return {
+ error: state.loadingError,
+ };
+})(LoadingError);
diff --git a/packages/compass-global-writes/src/store/index.spec.ts b/packages/compass-global-writes/src/store/index.spec.ts
index 74a1ac056ec..19fb3bf21a6 100644
--- a/packages/compass-global-writes/src/store/index.spec.ts
+++ b/packages/compass-global-writes/src/store/index.spec.ts
@@ -73,6 +73,9 @@ function createStore({
hasShardKey = () => false,
failsOnShardingRequest = () => false,
failsOnShardZoneRequest = () => false,
+ failsToFetchClusterDetails = () => false,
+ failsToFetchDeploymentStatus = () => false,
+ failsToFetchShardKey = () => false,
authenticatedFetchStub,
}:
| {
@@ -81,6 +84,9 @@ function createStore({
hasShardKey?: () => boolean | AtlasShardKey;
failsOnShardingRequest?: () => boolean;
failsOnShardZoneRequest?: () => boolean;
+ failsToFetchClusterDetails?: () => boolean;
+ failsToFetchDeploymentStatus?: () => boolean;
+ failsToFetchShardKey?: () => boolean;
authenticatedFetchStub?: never;
}
| {
@@ -89,6 +95,9 @@ function createStore({
hasShardKey?: () => boolean | ShardKey;
failsOnShardingRequest?: never;
failsOnShardZoneRequest?: () => never;
+ failsToFetchClusterDetails?: never;
+ failsToFetchDeploymentStatus?: never;
+ failsToFetchShardKey?: () => boolean;
authenticatedFetchStub?: () => void;
} = {}): GlobalWritesStore {
const atlasService = {
@@ -98,6 +107,9 @@ function createStore({
}
if (uri.includes('/clusters/')) {
+ if (failsToFetchClusterDetails()) {
+ return Promise.reject(new Error('Failed to fetch cluster details'));
+ }
return createAuthFetchResponse({
...clusterDetails,
geoSharding: {
@@ -108,6 +120,9 @@ function createStore({
}
if (uri.includes('/deploymentStatus/')) {
+ if (failsToFetchDeploymentStatus()) {
+ return Promise.reject(new Error('Failed to fetch deployment status'));
+ }
return createAuthFetchResponse({
automationStatus: {
processes: hasShardingError() ? [failedShardingProcess] : [],
@@ -130,6 +145,10 @@ function createStore({
}),
automationAgentAwait: (_meta: unknown, type: string) => {
if (type === 'getShardKey') {
+ if (failsToFetchShardKey()) {
+ return Promise.reject(new Error('Failed to fetch shardKey'));
+ }
+
const shardKey = hasShardKey();
return {
response:
@@ -188,6 +207,35 @@ describe('GlobalWritesStore Store', function () {
});
context('scenarios', function () {
+ context('initial load fail', function () {
+ it('fails to fetch cluster details', async function () {
+ const store = createStore({
+ failsToFetchClusterDetails: () => true,
+ });
+ await waitFor(() => {
+ expect(store.getState().status).to.equal('LOADING_ERROR');
+ });
+ });
+
+ it('fails to fetch shard key', async function () {
+ const store = createStore({
+ failsToFetchShardKey: () => true,
+ });
+ await waitFor(() => {
+ expect(store.getState().status).to.equal('LOADING_ERROR');
+ });
+ });
+
+ it('fails to fetch deployment status', async function () {
+ const store = createStore({
+ failsToFetchDeploymentStatus: () => true,
+ });
+ await waitFor(() => {
+ expect(store.getState().status).to.equal('LOADING_ERROR');
+ });
+ });
+ });
+
it('not managed -> sharding -> valid shard key', async function () {
let mockShardKey = false;
let mockManagedNamespace = false;
@@ -291,6 +339,52 @@ describe('GlobalWritesStore Store', function () {
});
});
+ context('pulling fail', function () {
+ it('sharding -> error (failed to fetch shard key)', async function () {
+ let mockFailure = false;
+ // initial state === sharding
+ clock = sinon.useFakeTimers({
+ shouldAdvanceTime: true,
+ });
+ const store = createStore({
+ isNamespaceManaged: () => true,
+ failsToFetchShardKey: Sinon.fake(() => mockFailure),
+ });
+ await waitFor(() => {
+ expect(store.getState().status).to.equal('SHARDING');
+ });
+
+ // sharding ends with a request failure
+ mockFailure = true;
+ clock.tick(POLLING_INTERVAL);
+ await waitFor(() => {
+ expect(store.getState().status).to.equal('LOADING_ERROR');
+ });
+ });
+
+ it('sharding -> error (failed to fetch deployment status)', async function () {
+ let mockFailure = false;
+ // initial state === sharding
+ clock = sinon.useFakeTimers({
+ shouldAdvanceTime: true,
+ });
+ const store = createStore({
+ isNamespaceManaged: () => true,
+ failsToFetchDeploymentStatus: Sinon.fake(() => mockFailure),
+ });
+ await waitFor(() => {
+ expect(store.getState().status).to.equal('SHARDING');
+ });
+
+ // sharding ends with a request failure
+ mockFailure = true;
+ clock.tick(POLLING_INTERVAL);
+ await waitFor(() => {
+ expect(store.getState().status).to.equal('LOADING_ERROR');
+ });
+ });
+ });
+
it('sharding -> cancelling request -> not managed', async function () {
let mockManagedNamespace = true;
confirmationStub.resolves(true);
diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts
index 44a655f79d3..aa6904b0eee 100644
--- a/packages/compass-global-writes/src/store/reducer.ts
+++ b/packages/compass-global-writes/src/store/reducer.ts
@@ -46,6 +46,8 @@ enum GlobalWritesActionTypes {
UnmanagingNamespaceStarted = 'global-writes/UnmanagingNamespaceStarted',
UnmanagingNamespaceFinished = 'global-writes/UnmanagingNamespaceFinished',
UnmanagingNamespaceErrored = 'global-writes/UnmanagingNamespaceErrored',
+
+ LoadingFailed = 'global-writes/LoadingFailed',
}
type ManagedNamespaceFetchedAction = {
@@ -53,6 +55,11 @@ type ManagedNamespaceFetchedAction = {
managedNamespace?: ManagedNamespace;
};
+type LoadingFailedAction = {
+ type: GlobalWritesActionTypes.LoadingFailed;
+ error: string;
+};
+
type NamespaceShardingErrorFetchedAction = {
type: GlobalWritesActionTypes.NamespaceShardingErrorFetched;
error: string;
@@ -125,6 +132,11 @@ export enum ShardingStatuses {
*/
NOT_READY = 'NOT_READY',
+ /**
+ * The status could not be determined because loading failed
+ */
+ LOADING_ERROR = 'LOADING_ERROR',
+
/**
* Namespace is not geo-sharded.
*/
@@ -209,11 +221,19 @@ export type RootState = {
managedNamespace?: ManagedNamespace;
shardZones: ShardZoneData[];
} & (
+ | {
+ status: ShardingStatuses.LOADING_ERROR;
+ shardKey?: ShardKey;
+ shardingError?: never;
+ pollingTimeout?: never;
+ loadingError: string;
+ }
| {
status: ShardingStatuses.NOT_READY;
shardKey?: never;
shardingError?: never;
pollingTimeout?: never;
+ loadingError?: never;
}
| {
status:
@@ -225,6 +245,7 @@ export type RootState = {
// and then unmanaged
shardingError?: never;
pollingTimeout?: never;
+ loadingError?: never;
}
| {
status: ShardingStatuses.SHARDING;
@@ -235,6 +256,7 @@ export type RootState = {
shardKey?: ShardKey;
shardingError?: never;
pollingTimeout?: NodeJS.Timeout;
+ loadingError?: never;
}
| {
status:
@@ -244,6 +266,7 @@ export type RootState = {
shardKey?: never;
shardingError: string;
pollingTimeout?: never;
+ loadingError?: never;
}
| {
status:
@@ -257,6 +280,7 @@ export type RootState = {
shardKey: ShardKey;
shardingError?: never;
pollingTimeout?: never;
+ loadingError?: never;
}
);
@@ -616,6 +640,25 @@ const reducer: Reducer = (state = initialState, action) => {
};
}
+ if (
+ isAction(
+ action,
+ GlobalWritesActionTypes.LoadingFailed
+ ) &&
+ (state.status === ShardingStatuses.NOT_READY ||
+ state.status === ShardingStatuses.SHARDING)
+ ) {
+ if (state.pollingTimeout) {
+ throw new Error('Polling was not stopped');
+ }
+ return {
+ ...state,
+ status: ShardingStatuses.LOADING_ERROR,
+ loadingError: action.error,
+ pollingTimeout: state.pollingTimeout,
+ };
+ }
+
return state;
};
@@ -644,16 +687,12 @@ export const fetchClusterShardingData =
'Error fetching cluster sharding data',
(error as Error).message
);
- openToast(
- `global-writes-fetch-shard-info-error-${connectionInfoRef.current.id}-${namespace}`,
- {
- title: `Failed to fetch sharding information: ${
- (error as Error).message
- }`,
- dismissible: true,
- timeout: 5000,
- variant: 'important',
- }
+ dispatch(
+ handleLoadingError({
+ error: error as Error,
+ id: `global-writes-fetch-shard-info-error-${connectionInfoRef.current.id}-${namespace}`,
+ description: 'Failed to fetch sharding information',
+ })
);
}
};
@@ -829,15 +868,48 @@ const stopPollingForShardKey = (): GlobalWritesThunkAction<
};
};
+const handleLoadingError = ({
+ error,
+ id,
+ description,
+}: {
+ error: Error;
+ id: string;
+ description: string;
+}): GlobalWritesThunkAction => {
+ return (dispatch, getState) => {
+ const { status } = getState();
+ const isPolling = status === ShardingStatuses.SHARDING;
+ const isInitialLoad = status === ShardingStatuses.NOT_READY;
+ const errorMessage = `${description}: ${error.message}`;
+ if (isInitialLoad || isPolling) {
+ dispatch({
+ type: GlobalWritesActionTypes.LoadingFailed,
+ error: errorMessage,
+ });
+ return;
+ }
+ openToast(id, {
+ title: errorMessage,
+ dismissible: true,
+ timeout: 5000,
+ variant: 'important',
+ });
+ };
+};
+
export const fetchNamespaceShardKey = (): GlobalWritesThunkAction<
Promise,
- NamespaceShardingErrorFetchedAction | NamespaceShardKeyFetchedAction
+ | NamespaceShardingErrorFetchedAction
+ | NamespaceShardKeyFetchedAction
+ | NextPollingTimeoutClearedAction
> => {
return async (
dispatch,
getState,
{ atlasGlobalWritesService, logger, connectionInfoRef }
) => {
+ dispatch({ type: GlobalWritesActionTypes.NextPollingTimeoutCleared });
const { namespace, status, managedNamespace } = getState();
try {
@@ -879,17 +951,15 @@ export const fetchNamespaceShardKey = (): GlobalWritesThunkAction<
logger.log.error(
logger.mongoLogId(1_001_000_333),
'AtlasFetchError',
- 'Error fetching shard key',
+ 'Error fetching shard key / deployment status',
(error as Error).message
);
- openToast(
- `global-writes-fetch-shard-key-error-${connectionInfoRef.current.id}-${namespace}`,
- {
- title: `Failed to fetch shard key: ${(error as Error).message}`,
- dismissible: true,
- timeout: 5000,
- variant: 'important',
- }
+ dispatch(
+ handleLoadingError({
+ error: error as Error,
+ id: `global-writes-fetch-shard-key-error-${connectionInfoRef.current.id}-${namespace}`,
+ description: 'Failed to fetch shard key or deployment status',
+ })
);
}
};