From 00edc688ffadc40d546e025f760cfb6ec975cb25 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 25 Oct 2024 15:17:03 +0200 Subject: [PATCH 01/10] feat(compass-global-writes): incomplete sharding setup COMPASS-8372 --- .../components/example-commands-markup.tsx | 99 +++++++++++++++++ .../src/components/shard-zones-table.tsx | 43 ++++++++ .../states/incomplete-sharding-setup.tsx | 100 +++++++++++++++++ .../states/shard-key-correct.spec.tsx | 2 +- .../components/states/shard-key-correct.tsx | 101 +----------------- .../states/shard-key-invalid.spec.tsx | 2 +- .../states/shard-key-mismatch.spec.tsx | 2 +- .../services/atlas-global-writes-service.ts | 6 +- .../src/store/reducer.ts | 83 ++++++++++---- 9 files changed, 313 insertions(+), 125 deletions(-) create mode 100644 packages/compass-global-writes/src/components/example-commands-markup.tsx create mode 100644 packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx diff --git a/packages/compass-global-writes/src/components/example-commands-markup.tsx b/packages/compass-global-writes/src/components/example-commands-markup.tsx new file mode 100644 index 00000000000..a1b9892bc17 --- /dev/null +++ b/packages/compass-global-writes/src/components/example-commands-markup.tsx @@ -0,0 +1,99 @@ +import { + Body, + Code, + css, + Label, + Link, + spacing, + Subtitle, +} from '@mongodb-js/compass-components'; +import React, { useMemo } from 'react'; +import type { ShardKey } from '../store/reducer'; +import toNS from 'mongodb-ns'; + +const codeBlockContainerStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[100], +}); + +interface ExampleCommandsMarkupProps { + shardKey: ShardKey; + namespace: string; + showMetaData?: boolean; + type?: 'requested' | 'existing'; +} + +const paragraphStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[100], +}); + +export function ExampleCommandsMarkup({ + namespace, + shardKey, +}: ExampleCommandsMarkupProps) { + const customShardKeyField = useMemo(() => { + return shardKey.fields[1].name; + }, [shardKey]); + + const sampleCodes = useMemo(() => { + const { collection, database } = toNS(namespace); + return { + findingDocuments: `use ${database}\ndb[${JSON.stringify( + collection + )}].find({"location": "US-NY", "${customShardKeyField}": ""})`, + insertingDocuments: `use ${database}\ndb[${JSON.stringify( + collection + )}].insertOne({"location": "US-NY", "${customShardKeyField}": "",...})`, + }; + }, [namespace, customShardKeyField]); + + return ( + <> + Example commands +
+ + Start querying your database with some of the most{' '} + + common commands + {' '} + for Global Writes. + + + Replace the text to perform operations on different documents. US-NY + is an ISO 3166 location code referring to New York, United States. You + can look up other ISO 3166 location codes below. + +
+ +
+ + + {sampleCodes.findingDocuments} + +
+ +
+ + + {sampleCodes.insertingDocuments} + +
+ + ); +} + +export default ExampleCommandsMarkup; diff --git a/packages/compass-global-writes/src/components/shard-zones-table.tsx b/packages/compass-global-writes/src/components/shard-zones-table.tsx index 6d3952235d0..789335442d7 100644 --- a/packages/compass-global-writes/src/components/shard-zones-table.tsx +++ b/packages/compass-global-writes/src/components/shard-zones-table.tsx @@ -17,13 +17,24 @@ import { type LGTableDataType, getFilteredRowModel, type LgTableRowType, + Subtitle, + Body, + spacing, + Link, } from '@mongodb-js/compass-components'; import type { ShardZoneData } from '../store/reducer'; +import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; const containerStyles = css({ height: '400px', }); +const paragraphStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[100], +}); + interface ShardZoneRow { locationName: string; zone: string; @@ -127,10 +138,42 @@ export function ShardZonesTable({ [tableRef] ); + const { atlasMetadata } = useConnectionInfo(); + const { rows } = table.getRowModel(); return ( <> + Location Codes +
+ + Each document’s first field should include an ISO 3166-1 Alpha-2 code + for the location it belongs to. + + + We also support ISO 3166-2 subdivision codes for countries containing + a cloud provider data center (both ISO 3166-1 and ISO 3166-2 codes may + be used for these countries). All valid country codes and the zones to + which they map are listed in the table below. Additionally, you can + view a list of all location codes{' '} + here. + + + {atlasMetadata?.projectId && atlasMetadata?.clusterName && ( + <> + Locations’ zone mapping can be changed by navigating to this + clusters{' '} + + Edit Configuration + {' '} + page and clicking the Configure Location Mappings’ link above the + map. + + )} + +
void; +} + +export function IncompleteShardingSetup({ + shardKey, + shardZones, + namespace, + onResume, + isSubmittingForSharding, +}: IncompleteShardingSetupProps) { + return ( +
+ + + It looks like you've chosen a Global Writes shard key for this + collection, but your configuration is incomplete. + {' '} + Please enable Global Writes for this collection to ensure that documents + are associated with the appropriate zone.  + + Read more about Global Writes + +
+ +
+
+ + + +
+ ); +} + +export default connect( + (state: RootState) => { + if (!state.shardKey) { + throw new Error('Shard key not found in IncompleteShardingSetup'); + } + return { + namespace: state.namespace, + shardKey: state.shardKey, + shardZones: state.shardZones, + isSubmittingForSharding: + state.status === ShardingStatuses.SUBMITTING_FOR_SHARDING_INCOMPLETE, + }; + }, + { + onResume: resumeManagedNamespace, + } +)(IncompleteShardingSetup); diff --git a/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx b/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx index 504db736e4e..0d63bfa6979 100644 --- a/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx +++ b/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx @@ -10,7 +10,7 @@ import Sinon from 'sinon'; import { renderWithStore } from '../../../tests/create-store'; import { type ConnectionInfo } from '@mongodb-js/compass-connections/provider'; -describe('Compass GlobalWrites Plugin', function () { +describe('ShardKeyCorrect', function () { const shardZones: ShardZoneData[] = [ { zoneId: '45893084', diff --git a/packages/compass-global-writes/src/components/states/shard-key-correct.tsx b/packages/compass-global-writes/src/components/states/shard-key-correct.tsx index 12e7f64f313..2d5395e8dbc 100644 --- a/packages/compass-global-writes/src/components/states/shard-key-correct.tsx +++ b/packages/compass-global-writes/src/components/states/shard-key-correct.tsx @@ -3,12 +3,7 @@ import { Banner, BannerVariant, Body, - css, - Link, - spacing, - Code, Subtitle, - Label, Button, ButtonVariant, SpinLoader, @@ -21,24 +16,16 @@ import { type ShardKey, type ShardZoneData, } from '../../store/reducer'; -import toNS from 'mongodb-ns'; import { ShardZonesTable } from '../shard-zones-table'; -import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import ShardKeyMarkup from '../shard-key-markup'; import { containerStyles, - paragraphStyles, bannerStyles, } from '../common-styles'; +import ExampleCommandsMarkup from '../example-commands-markup' const nbsp = '\u00a0'; -const codeBlockContainerStyles = css({ - display: 'flex', - flexDirection: 'column', - gap: spacing[100], -}); - export type ShardKeyCorrectProps = { namespace: string; shardKey: ShardKey; @@ -58,20 +45,6 @@ export function ShardKeyCorrect({ return shardKey.fields[1].name; }, [shardKey]); - const { atlasMetadata } = useConnectionInfo(); - - const sampleCodes = useMemo(() => { - const { collection, database } = toNS(namespace); - return { - findingDocuments: `use ${database}\ndb[${JSON.stringify( - collection - )}].find({"location": "US-NY", "${customShardKeyField}": ""})`, - insertingDocuments: `use ${database}\ndb[${JSON.stringify( - collection - )}].insertOne({"location": "US-NY", "${customShardKeyField}": "",...})`, - }; - }, [namespace, customShardKeyField]); - return (
@@ -83,77 +56,7 @@ export function ShardKeyCorrect({ {nbsp}We have included a table for reference below. - Example commands -
- - Start querying your database with some of the most{' '} - - common commands - {' '} - for Global Writes. - - - Replace the text to perform operations on different documents. US-NY - is an ISO 3166 location code referring to New York, United States. You - can look up other ISO 3166 location codes below. - -
- -
- - - {sampleCodes.findingDocuments} - -
- -
- - - {sampleCodes.insertingDocuments} - -
- - Location Codes -
- - Each document’s first field should include an ISO 3166-1 Alpha-2 code - for the location it belongs to. - - - We also support ISO 3166-2 subdivision codes for countries containing - a cloud provider data center (both ISO 3166-1 and ISO 3166-2 codes may - be used for these countries). All valid country codes and the zones to - which they map are listed in the table below. Additionally, you can - view a list of all location codes{' '} - here. - - - {atlasMetadata?.projectId && atlasMetadata?.clusterName && ( - <> - Locations’ zone mapping can be changed by navigating to this - clusters{' '} - - Edit Configuration - {' '} - page and clicking the Configure Location Mappings’ link above the - map. - - )} - -
+ diff --git a/packages/compass-global-writes/src/components/states/shard-key-invalid.spec.tsx b/packages/compass-global-writes/src/components/states/shard-key-invalid.spec.tsx index 84fc8829e7e..acc8c775e7a 100644 --- a/packages/compass-global-writes/src/components/states/shard-key-invalid.spec.tsx +++ b/packages/compass-global-writes/src/components/states/shard-key-invalid.spec.tsx @@ -7,7 +7,7 @@ import { } from './shard-key-invalid'; import { renderWithStore } from '../../../tests/create-store'; -describe('Compass GlobalWrites Plugin', function () { +describe('ShardKeyInvalid', function () { const baseProps: ShardKeyInvalidProps = { namespace: 'db1.coll1', shardKey: { diff --git a/packages/compass-global-writes/src/components/states/shard-key-mismatch.spec.tsx b/packages/compass-global-writes/src/components/states/shard-key-mismatch.spec.tsx index 25bf89f7193..67dbf52bd75 100644 --- a/packages/compass-global-writes/src/components/states/shard-key-mismatch.spec.tsx +++ b/packages/compass-global-writes/src/components/states/shard-key-mismatch.spec.tsx @@ -8,7 +8,7 @@ import { import { renderWithStore } from '../../../tests/create-store'; import Sinon from 'sinon'; -describe('Compass GlobalWrites Plugin', function () { +describe('ShardKeyMismatch', function () { const baseProps: ShardKeyMismatchProps = { namespace: 'db1.coll1', shardKey: { diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index 8ce8f1456c7..7ec61920026 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -135,7 +135,7 @@ export class AtlasGlobalWritesService { ); } - async createShardKey(namespace: string, keyData: CreateShardKeyData) { + async manageNamespace(namespace: string, keyData: CreateShardKeyData) { const clusterDetails = await this.getClusterDetails(); const { database, collection } = toNS(namespace); const requestData: GeoShardingData = { @@ -222,7 +222,7 @@ export class AtlasGlobalWritesService { const data = res.response; if (data.length === 0) { - return null; + return undefined; } const { key, unique } = data[0]; @@ -263,6 +263,8 @@ export class AtlasGlobalWritesService { return transformZoneData(Object.values(data), replicationSpecs); } + async resumeManagedNamespace(namespace: string) {} + async unmanageNamespace(namespace: string) { const clusterDetails = await this.getClusterDetails(); const { database, collection } = toNS(namespace); diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 97b6d18b3c8..35414efe5ea 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -59,7 +59,7 @@ type NamespaceShardingErrorFetchedAction = { type NamespaceShardKeyFetchedAction = { type: GlobalWritesActionTypes.NamespaceShardKeyFetched; - shardKey: ShardKey; + shardKey?: ShardKey; }; type ShardZonesFetchedAction = { @@ -125,12 +125,20 @@ export enum ShardingStatuses { */ UNSHARDED = 'UNSHARDED', + /** + * Incomplete sharding setup + * sharding key exists but namespace is not managed + * (can happen when already sharded namespace is unmanaged) + */ + INCOMPLETE_SHARDING_SETUP = 'INCOMPLETE_SHARDING_SETUP', + /** * State when user submits namespace to be sharded and * we are waiting for server to accept the request. */ SUBMITTING_FOR_SHARDING = 'SUBMITTING_FOR_SHARDING', SUBMITTING_FOR_SHARDING_ERROR = 'SUBMITTING_FOR_SHARDING_ERROR', + SUBMITTING_FOR_SHARDING_INCOMPLETE = 'SUBMITTING_FOR_SHARDING_INCOMPLETE', /** * Namespace is being sharded. @@ -240,7 +248,9 @@ export type RootState = { | ShardingStatuses.SHARD_KEY_INVALID | ShardingStatuses.SHARD_KEY_MISMATCH | ShardingStatuses.UNMANAGING_NAMESPACE - | ShardingStatuses.UNMANAGING_NAMESPACE_MISMATCH; + | ShardingStatuses.UNMANAGING_NAMESPACE_MISMATCH + | ShardingStatuses.INCOMPLETE_SHARDING_SETUP + | ShardingStatuses.SUBMITTING_FOR_SHARDING_INCOMPLETE; shardKey: ShardKey; shardingError?: never; pollingTimeout?: never; @@ -264,9 +274,6 @@ const reducer: Reducer = (state = initialState, action) => { return { ...state, managedNamespace: action.managedNamespace, - status: !action.managedNamespace - ? ShardingStatuses.UNSHARDED - : state.status, }; } @@ -296,14 +303,18 @@ const reducer: Reducer = (state = initialState, action) => { GlobalWritesActionTypes.NamespaceShardKeyFetched ) && (state.status === ShardingStatuses.NOT_READY || - state.status === ShardingStatuses.SHARDING) + state.status === ShardingStatuses.SHARDING) && + action.shardKey ) { if (state.pollingTimeout) { throw new Error('Polling was not stopped'); } return { ...state, - status: getStatusFromShardKey(action.shardKey, state.managedNamespace), + status: getStatusFromShardKeyAndManaged( + action.shardKey, + state.managedNamespace + ), shardKey: action.shardKey, shardingError: undefined, pollingTimeout: state.pollingTimeout, @@ -348,6 +359,19 @@ const reducer: Reducer = (state = initialState, action) => { }; } + if ( + isAction( + action, + GlobalWritesActionTypes.SubmittingForShardingStarted + ) && + state.status === ShardingStatuses.INCOMPLETE_SHARDING_SETUP + ) { + return { + ...state, + status: ShardingStatuses.SUBMITTING_FOR_SHARDING_INCOMPLETE, + }; + } + if ( isAction( action, @@ -355,6 +379,7 @@ const reducer: Reducer = (state = initialState, action) => { ) && (state.status === ShardingStatuses.SUBMITTING_FOR_SHARDING || state.status === ShardingStatuses.SUBMITTING_FOR_SHARDING_ERROR || + state.status === ShardingStatuses.SUBMITTING_FOR_SHARDING_INCOMPLETE || state.status === ShardingStatuses.NOT_READY) ) { return { @@ -524,9 +549,6 @@ export const fetchClusterShardingData = ) => { const { namespace } = getState(); try { - // Call the API to check if the namespace is managed. If the namespace is managed, - // we would want to fetch more data that is needed to figure out the state and - // accordingly show the UI to the user. const managedNamespace = await atlasGlobalWritesService.getManagedNamespace(namespace); @@ -534,11 +556,7 @@ export const fetchClusterShardingData = type: GlobalWritesActionTypes.ManagedNamespaceFetched, managedNamespace, }); - if (!managedNamespace) { - return; - } - // At this point, the namespace is managed and we want to fetch the sharding key. void dispatch(fetchNamespaceShardKey()); } catch (error) { logger.log.error( @@ -561,6 +579,23 @@ export const fetchClusterShardingData = } }; +export const resumeManagedNamespace = (): ReturnType => { + return async (dispatch, getState) => { + const { shardKey } = getState(); + if (!shardKey) { + throw new Error('Cannot resume managed namespace without a shardKey'); + } + const data: CreateShardKeyData = { + customShardKey: shardKey.fields[1].name, + isShardKeyUnique: shardKey.isUnique, + isCustomShardKeyHashed: shardKey.fields[1].type === 'HASHED', + numInitialChunks: null, // default + presplitHashedZones: false, // default + }; + await dispatch(createShardKey(data)); + }; +}; + export const createShardKey = ( data: CreateShardKeyData ): GlobalWritesThunkAction< @@ -580,7 +615,7 @@ export const createShardKey = ( }); try { - const managedNamespace = await atlasGlobalWritesService.createShardKey( + const managedNamespace = await atlasGlobalWritesService.manageNamespace( namespace, data ); @@ -746,11 +781,6 @@ export const fetchNamespaceShardKey = (): GlobalWritesThunkAction< return; } - if (!shardKey) { - dispatch(setNamespaceBeingSharded()); - return; - } - if (status === ShardingStatuses.SHARDING) { dispatch(stopPollingForShardKey()); } @@ -758,6 +788,9 @@ export const fetchNamespaceShardKey = (): GlobalWritesThunkAction< type: GlobalWritesActionTypes.NamespaceShardKeyFetched, shardKey, }); + + // if there is a key, we fetch sharding zones + if (!shardKey) return; void dispatch(fetchShardingZones()); } catch (error) { logger.log.error( @@ -835,12 +868,16 @@ export const unmanageNamespace = (): GlobalWritesThunkAction< }; }; -export function getStatusFromShardKey( +export function getStatusFromShardKeyAndManaged( shardKey: ShardKey, managedNamespace?: ManagedNamespace ) { const [firstShardKey, secondShardKey] = shardKey.fields; + if (!shardKey && !managedNamespace) { + return ShardingStatuses.UNSHARDED; + } + // For a shard key to be valid: // 1. The first key must be location and of type RANGE. // 2. The second key name must match managedNamespace.customShardKey and @@ -863,6 +900,10 @@ export function getStatusFromShardKey( return ShardingStatuses.SHARD_KEY_MISMATCH; } + if (!managedNamespace) { + return ShardingStatuses.INCOMPLETE_SHARDING_SETUP; + } + return ShardingStatuses.SHARD_KEY_CORRECT; } From 30f864fc627ba10a3ba6c6743385150a148b8897 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 25 Oct 2024 15:30:55 +0200 Subject: [PATCH 02/10] wip --- .../src/components/shard-zones-table.tsx | 45 +------ .../states/incomplete-sharding-setup.spec.tsx | 115 ++++++++++++++++++ .../states/shard-key-correct.spec.tsx | 24 ---- 3 files changed, 117 insertions(+), 67 deletions(-) create mode 100644 packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx diff --git a/packages/compass-global-writes/src/components/shard-zones-table.tsx b/packages/compass-global-writes/src/components/shard-zones-table.tsx index 789335442d7..f30352f4c44 100644 --- a/packages/compass-global-writes/src/components/shard-zones-table.tsx +++ b/packages/compass-global-writes/src/components/shard-zones-table.tsx @@ -17,24 +17,14 @@ import { type LGTableDataType, getFilteredRowModel, type LgTableRowType, - Subtitle, - Body, - spacing, - Link, } from '@mongodb-js/compass-components'; import type { ShardZoneData } from '../store/reducer'; -import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; +import { ShardZonesDescription } from './shard-zones-description'; const containerStyles = css({ height: '400px', }); -const paragraphStyles = css({ - display: 'flex', - flexDirection: 'column', - gap: spacing[100], -}); - interface ShardZoneRow { locationName: string; zone: string; @@ -138,42 +128,11 @@ export function ShardZonesTable({ [tableRef] ); - const { atlasMetadata } = useConnectionInfo(); - const { rows } = table.getRowModel(); return ( <> - Location Codes -
- - Each document’s first field should include an ISO 3166-1 Alpha-2 code - for the location it belongs to. - - - We also support ISO 3166-2 subdivision codes for countries containing - a cloud provider data center (both ISO 3166-1 and ISO 3166-2 codes may - be used for these countries). All valid country codes and the zones to - which they map are listed in the table below. Additionally, you can - view a list of all location codes{' '} - here. - - - {atlasMetadata?.projectId && atlasMetadata?.clusterName && ( - <> - Locations’ zone mapping can be changed by navigating to this - clusters{' '} - - Edit Configuration - {' '} - page and clicking the Configure Location Mappings’ link above the - map. - - )} - -
+ {}, + }; + + const connectionInfo = { + id: 'testConnection', + connectionOptions: { + connectionString: 'mongodb://test', + }, + atlasMetadata: { + projectId: 'project1', + clusterName: 'myCluster', + } as ConnectionInfo['atlasMetadata'], + }; + + function renderWithProps( + props?: Partial, + options?: Parameters[1] + ) { + return renderWithStore( + , + { + connectionInfo, + ...options, + } + ); + } + + it('Shows description', async function () { + await renderWithProps(); + + expect(screen.findByText(/your configuration is incomplete/)).to.be.exist; + expect(screen.findByText(/Please enable Global Writes/)).to.be.exist; + }); + + it('Provides button to resume managed namespace', async function () { + const onResume = Sinon.spy(); + await renderWithProps({ onResume }); + + const btn = await screen.findByRole('button', { + name: /Enable Global Writes/, + }); + expect(btn).to.be.visible; + + userEvent.click(btn); + + expect(onResume).to.have.been.calledOnce; + }); + + it('Manage btn is disabled when the action is in progress', async function () { + const onResume = Sinon.spy(); + await renderWithProps({ onResume, isSubmittingForSharding: true }); + + const btn = await screen.findByTestId( + 'manage-collection-button' + ); + expect(btn).to.be.visible; + expect(btn.getAttribute('aria-disabled')).to.equal('true'); + + userEvent.click(btn); + + expect(onResume).not.to.have.been.called; + }); + + it('Describes the shardKey', async function () { + await renderWithProps(); + + const title = await screen.findByTestId( + 'existing-shardkey-description-title' + ); + expect(title).to.be.visible; + expect(title.textContent).to.equal( + `${baseProps.namespace} is configured with the following shard key:` + ); + const list = await screen.findByTestId( + 'existing-shardkey-description-content' + ); + expect(list).to.be.visible; + expect(list.textContent).to.contain(`"location", "secondary"`); + }); +}); diff --git a/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx b/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx index 0d63bfa6979..da660016762 100644 --- a/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx +++ b/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx @@ -76,30 +76,6 @@ describe('ShardKeyCorrect', function () { expect(onUnmanageNamespace).not.to.have.been.called; }); - it('Provides link to Edit Configuration', async function () { - const connectionInfo = { - id: 'testConnection', - connectionOptions: { - connectionString: 'mongodb://test', - }, - atlasMetadata: { - projectId: 'project1', - clusterName: 'myCluster', - } as ConnectionInfo['atlasMetadata'], - }; - await renderWithProps(undefined, { - connectionInfo, - }); - - const link = await screen.findByRole('link', { - name: /Edit Configuration/, - }); - const expectedHref = `/v2/${connectionInfo.atlasMetadata?.projectId}#/clusters/edit/${connectionInfo.atlasMetadata?.clusterName}`; - - expect(link).to.be.visible; - expect(link).to.have.attribute('href', expectedHref); - }); - it('Describes the shardKey', async function () { await renderWithProps(); From 042cfa4f31b1b25e01044668b66f9b4a918ddf1b Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 28 Oct 2024 10:02:40 +0100 Subject: [PATCH 03/10] wip --- .../src/components/states/shard-key-correct.spec.tsx | 1 - .../src/services/atlas-global-writes-service.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx b/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx index da660016762..cb49176a5ed 100644 --- a/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx +++ b/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx @@ -8,7 +8,6 @@ import { import { type ShardZoneData } from '../../store/reducer'; import Sinon from 'sinon'; import { renderWithStore } from '../../../tests/create-store'; -import { type ConnectionInfo } from '@mongodb-js/compass-connections/provider'; describe('ShardKeyCorrect', function () { const shardZones: ShardZoneData[] = [ diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index 7ec61920026..f36f224637e 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -263,8 +263,6 @@ export class AtlasGlobalWritesService { return transformZoneData(Object.values(data), replicationSpecs); } - async resumeManagedNamespace(namespace: string) {} - async unmanageNamespace(namespace: string) { const clusterDetails = await this.getClusterDetails(); const { database, collection } = toNS(namespace); From 19402ffd049669e6cbbe5d47a4194766f017c858 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 28 Oct 2024 12:28:17 +0100 Subject: [PATCH 04/10] add files --- .../components/shard-zones-description.tsx | 53 +++++++++++++++++++ .../shard-zones-descripton.spec.tsx | 32 +++++++++++ 2 files changed, 85 insertions(+) create mode 100644 packages/compass-global-writes/src/components/shard-zones-description.tsx create mode 100644 packages/compass-global-writes/src/components/shard-zones-descripton.spec.tsx diff --git a/packages/compass-global-writes/src/components/shard-zones-description.tsx b/packages/compass-global-writes/src/components/shard-zones-description.tsx new file mode 100644 index 00000000000..0e3d7e395ed --- /dev/null +++ b/packages/compass-global-writes/src/components/shard-zones-description.tsx @@ -0,0 +1,53 @@ +import { + Body, + css, + Link, + spacing, + Subtitle, +} from '@mongodb-js/compass-components'; +import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; +import React from 'react'; + +const paragraphStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[100], +}); + +export function ShardZonesDescription() { + const { atlasMetadata } = useConnectionInfo(); + return ( + <> + Location Codes +
+ + Each document’s first field should include an ISO 3166-1 Alpha-2 code + for the location it belongs to. + + + We also support ISO 3166-2 subdivision codes for countries containing + a cloud provider data center (both ISO 3166-1 and ISO 3166-2 codes may + be used for these countries). All valid country codes and the zones to + which they map are listed in the table below. Additionally, you can + view a list of all location codes{' '} + here. + + + {atlasMetadata?.projectId && atlasMetadata?.clusterName && ( + <> + Locations’ zone mapping can be changed by navigating to this + clusters{' '} + + Edit Configuration + {' '} + page and clicking the Configure Location Mappings’ link above the + map. + + )} + +
+ + ); +} diff --git a/packages/compass-global-writes/src/components/shard-zones-descripton.spec.tsx b/packages/compass-global-writes/src/components/shard-zones-descripton.spec.tsx new file mode 100644 index 00000000000..fe58b3819d0 --- /dev/null +++ b/packages/compass-global-writes/src/components/shard-zones-descripton.spec.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { expect } from 'chai'; +import { screen } from '@mongodb-js/testing-library-compass'; +import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; +import { renderWithStore } from '../../tests/create-store'; +import { ShardZonesDescription } from './shard-zones-description'; + +describe('ShardZonesDescription', () => { + it('Provides link to Edit Configuration', async function () { + const connectionInfo = { + id: 'testConnection', + connectionOptions: { + connectionString: 'mongodb://test', + }, + atlasMetadata: { + projectId: 'project1', + clusterName: 'myCluster', + } as ConnectionInfo['atlasMetadata'], + }; + await renderWithStore(, { + connectionInfo, + }); + + const link = await screen.findByRole('link', { + name: /Edit Configuration/, + }); + const expectedHref = `/v2/${connectionInfo.atlasMetadata?.projectId}#/clusters/edit/${connectionInfo.atlasMetadata?.clusterName}`; + + expect(link).to.be.visible; + expect(link).to.have.attribute('href', expectedHref); + }); +}); From 90a3ecbf8c7a42b0d6b300f6d596eed58bd2db3a Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Wed, 30 Oct 2024 16:30:41 +0100 Subject: [PATCH 05/10] fix scenarios --- .../states/incomplete-sharding-setup.spec.tsx | 2 +- .../states/incomplete-sharding-setup.tsx | 4 +- .../components/states/shard-key-correct.tsx | 7 +-- .../src/store/index.spec.ts | 32 +++++++++++ .../src/store/reducer.ts | 56 +++++++++++++------ 5 files changed, 75 insertions(+), 26 deletions(-) diff --git a/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx b/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx index 4bff61b308b..a879bc8b4e5 100644 --- a/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx +++ b/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx @@ -10,7 +10,7 @@ import { renderWithStore } from '../../../tests/create-store'; import { type ConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { type ShardZoneData } from '../../store/reducer'; -describe.only('IncompleteShardingSetup', function () { +describe('IncompleteShardingSetup', function () { const shardZones: ShardZoneData[] = [ { zoneId: '45893084', diff --git a/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx b/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx index 96e2eef8e7b..2001ad8ac85 100644 --- a/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx +++ b/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx @@ -27,7 +27,7 @@ const containerStyles = css({ marginBottom: spacing[400], }); -const unmanageBtnStyles = css({ +const manageBtnStyles = css({ marginTop: spacing[100], }); @@ -68,7 +68,7 @@ export function IncompleteShardingSetup({ onClick={onResume} variant={ButtonVariant.Default} isLoading={isSubmittingForSharding} - className={unmanageBtnStyles} + className={manageBtnStyles} > Enable Global Writes diff --git a/packages/compass-global-writes/src/components/states/shard-key-correct.tsx b/packages/compass-global-writes/src/components/states/shard-key-correct.tsx index 2d5395e8dbc..356a24ca4e9 100644 --- a/packages/compass-global-writes/src/components/states/shard-key-correct.tsx +++ b/packages/compass-global-writes/src/components/states/shard-key-correct.tsx @@ -18,11 +18,8 @@ import { } from '../../store/reducer'; import { ShardZonesTable } from '../shard-zones-table'; import ShardKeyMarkup from '../shard-key-markup'; -import { - containerStyles, - bannerStyles, -} from '../common-styles'; -import ExampleCommandsMarkup from '../example-commands-markup' +import { containerStyles, bannerStyles } from '../common-styles'; +import ExampleCommandsMarkup from '../example-commands-markup'; const nbsp = '\u00a0'; diff --git a/packages/compass-global-writes/src/store/index.spec.ts b/packages/compass-global-writes/src/store/index.spec.ts index a32c7d032ff..3c161b0527c 100644 --- a/packages/compass-global-writes/src/store/index.spec.ts +++ b/packages/compass-global-writes/src/store/index.spec.ts @@ -6,6 +6,7 @@ import { type CreateShardKeyData, unmanageNamespace, cancelSharding, + resumeManagedNamespace, POLLING_INTERVAL, type ShardKey, } from './reducer'; @@ -179,9 +180,11 @@ describe('GlobalWritesStore Store', function () { context('scenarios', function () { it('not managed -> sharding -> valid shard key', async function () { let mockShardKey = false; + let mockManagedNamespace = false; // initial state === unsharded const store = createStore({ hasShardKey: Sinon.fake(() => mockShardKey), + isNamespaceManaged: Sinon.fake(() => mockManagedNamespace), }); await waitFor(() => { expect(store.getState().status).to.equal('UNSHARDED'); @@ -194,6 +197,7 @@ describe('GlobalWritesStore Store', function () { }); const promise = store.dispatch(createShardKey(shardKeyData)); expect(store.getState().status).to.equal('SUBMITTING_FOR_SHARDING'); + mockManagedNamespace = true; await promise; expect(store.getState().status).to.equal('SHARDING'); @@ -309,6 +313,34 @@ describe('GlobalWritesStore Store', function () { }); }); + it('incomplete setup -> shard key correct', async function () { + // initial state -> incomplete shardingSetup + clock = sinon.useFakeTimers({ + shouldAdvanceTime: true, + }); + let mockManagedNamespace = false; + const store = createStore({ + isNamespaceManaged: Sinon.fake(() => mockManagedNamespace), + hasShardKey: () => true, + }); + await waitFor(() => { + expect(store.getState().status).to.equal('INCOMPLETE_SHARDING_SETUP'); + expect(store.getState().managedNamespace).to.be.undefined; + }); + + // user asks to resume geosharding + const promise = store.dispatch(resumeManagedNamespace()); + mockManagedNamespace = true; + expect(store.getState().status).to.equal( + 'SUBMITTING_FOR_SHARDING_INCOMPLETE' + ); + await promise; + clock.tick(POLLING_INTERVAL); + await waitFor(() => { + expect(store.getState().status).to.equal('SHARD_KEY_CORRECT'); + }); + }); + it('valid shard key -> not managed', async function () { // initial state === shard key correct const store = createStore({ diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 35414efe5ea..fa2174ee36e 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -321,6 +321,25 @@ const reducer: Reducer = (state = initialState, action) => { }; } + if ( + isAction( + action, + GlobalWritesActionTypes.NamespaceShardKeyFetched + ) && + state.status === ShardingStatuses.NOT_READY && + !action.shardKey && + !state.managedNamespace + ) { + if (state.pollingTimeout) { + throw new Error('Polling was not stopped'); + } + return { + ...state, + status: ShardingStatuses.UNSHARDED, + pollingTimeout: state.pollingTimeout, + }; + } + if ( isAction( action, @@ -759,7 +778,7 @@ export const fetchNamespaceShardKey = (): GlobalWritesThunkAction< getState, { atlasGlobalWritesService, logger, connectionInfoRef } ) => { - const { namespace, status } = getState(); + const { namespace, status, managedNamespace } = getState(); try { const [shardingError, shardKey] = await Promise.all([ @@ -767,7 +786,15 @@ export const fetchNamespaceShardKey = (): GlobalWritesThunkAction< atlasGlobalWritesService.getShardingKeys(namespace), ]); - if (shardingError && !shardKey) { + if (status === ShardingStatuses.SHARDING && (shardKey || shardingError)) { + dispatch(stopPollingForShardKey()); + } + + if (managedNamespace && !shardKey) { + if (!shardingError) { + dispatch(setNamespaceBeingSharded()); + return; + } // if there is an existing shard key and an error both, // means we have a key mismatch // this will be handled in NamespaceShardKeyFetched @@ -781,14 +808,10 @@ export const fetchNamespaceShardKey = (): GlobalWritesThunkAction< return; } - if (status === ShardingStatuses.SHARDING) { - dispatch(stopPollingForShardKey()); - } dispatch({ type: GlobalWritesActionTypes.NamespaceShardKeyFetched, shardKey, }); - // if there is a key, we fetch sharding zones if (!shardKey) return; void dispatch(fetchShardingZones()); @@ -874,10 +897,6 @@ export function getStatusFromShardKeyAndManaged( ) { const [firstShardKey, secondShardKey] = shardKey.fields; - if (!shardKey && !managedNamespace) { - return ShardingStatuses.UNSHARDED; - } - // For a shard key to be valid: // 1. The first key must be location and of type RANGE. // 2. The second key name must match managedNamespace.customShardKey and @@ -885,6 +904,15 @@ export function getStatusFromShardKeyAndManaged( const isLocatonKeyValid = firstShardKey.name === 'location' && firstShardKey.type === 'RANGE'; + + if (!isLocatonKeyValid || !secondShardKey) { + return ShardingStatuses.SHARD_KEY_INVALID; + } + + if (!managedNamespace) { + return ShardingStatuses.INCOMPLETE_SHARDING_SETUP; + } + const isCustomKeyValid = managedNamespace && managedNamespace.isShardKeyUnique === shardKey.isUnique && @@ -892,18 +920,10 @@ export function getStatusFromShardKeyAndManaged( secondShardKey.type === (managedNamespace.isCustomShardKeyHashed ? 'HASHED' : 'RANGE'); - if (!isLocatonKeyValid || !secondShardKey) { - return ShardingStatuses.SHARD_KEY_INVALID; - } - if (!isCustomKeyValid) { return ShardingStatuses.SHARD_KEY_MISMATCH; } - if (!managedNamespace) { - return ShardingStatuses.INCOMPLETE_SHARDING_SETUP; - } - return ShardingStatuses.SHARD_KEY_CORRECT; } From 847319567fdb4ec46dbf47d3ef932419ceb6b1c5 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 31 Oct 2024 13:08:45 +0100 Subject: [PATCH 06/10] . --- packages/compass-global-writes/src/components/index.tsx | 8 ++++++++ packages/compass-global-writes/src/store/index.spec.ts | 8 ++++---- packages/compass-global-writes/src/store/reducer.ts | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index f27f9c2a989..06c66095e81 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -15,6 +15,7 @@ import ShardKeyCorrect from './states/shard-key-correct'; 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'; const containerStyles = css({ paddingLeft: spacing[400], @@ -93,6 +94,13 @@ function ShardingStateView({ return ; } + if ( + shardingStatus === ShardingStatuses.INCOMPLETE_SHARDING_SETUP || + shardingStatus === ShardingStatuses.SUBMITTING_FOR_SHARDING_INCOMPLETE + ) { + return ; + } + return null; } diff --git a/packages/compass-global-writes/src/store/index.spec.ts b/packages/compass-global-writes/src/store/index.spec.ts index 3c161b0527c..e904db5041d 100644 --- a/packages/compass-global-writes/src/store/index.spec.ts +++ b/packages/compass-global-writes/src/store/index.spec.ts @@ -341,7 +341,7 @@ describe('GlobalWritesStore Store', function () { }); }); - it('valid shard key -> not managed', async function () { + it('valid shard key -> incomplete', async function () { // initial state === shard key correct const store = createStore({ isNamespaceManaged: () => true, @@ -356,7 +356,7 @@ describe('GlobalWritesStore Store', function () { const promise = store.dispatch(unmanageNamespace()); expect(store.getState().status).to.equal('UNMANAGING_NAMESPACE'); await promise; - expect(store.getState().status).to.equal('UNSHARDED'); + expect(store.getState().status).to.equal('INCOMPLETE_SHARDING_SETUP'); }); it('valid shard key -> valid shard key (failed unmanage attempt)', async function () { @@ -452,7 +452,7 @@ describe('GlobalWritesStore Store', function () { }); }); - it('mismatch -> unmanaged', async function () { + it('mismatch -> incomplete sharding setup', async function () { // initial state - mismatch const store = createStore({ isNamespaceManaged: () => true, @@ -475,7 +475,7 @@ describe('GlobalWritesStore Store', function () { 'UNMANAGING_NAMESPACE_MISMATCH' ); await promise; - expect(store.getState().status).to.equal('UNSHARDED'); + expect(store.getState().status).to.equal('INCOMPLETE_SHARDING_SETUP'); }); }); diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index fa2174ee36e..e46fe840f84 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -539,7 +539,7 @@ const reducer: Reducer = (state = initialState, action) => { return { ...state, managedNamespace: undefined, - status: ShardingStatuses.UNSHARDED, + status: ShardingStatuses.INCOMPLETE_SHARDING_SETUP, }; } From 10ee23e1ab13fea4fe8bd127439acf5790319d4e Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 31 Oct 2024 13:44:42 +0100 Subject: [PATCH 07/10] rearange test --- .../components/example-commands-markup.tsx | 2 +- .../states/incomplete-sharding-setup.spec.tsx | 7 +++++++ .../states/shard-key-correct.spec.tsx | 19 +++---------------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/compass-global-writes/src/components/example-commands-markup.tsx b/packages/compass-global-writes/src/components/example-commands-markup.tsx index a1b9892bc17..5171c3ecc5d 100644 --- a/packages/compass-global-writes/src/components/example-commands-markup.tsx +++ b/packages/compass-global-writes/src/components/example-commands-markup.tsx @@ -17,7 +17,7 @@ const codeBlockContainerStyles = css({ gap: spacing[100], }); -interface ExampleCommandsMarkupProps { +export interface ExampleCommandsMarkupProps { shardKey: ShardKey; namespace: string; showMetaData?: boolean; diff --git a/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx b/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx index a879bc8b4e5..d410249e541 100644 --- a/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx +++ b/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.spec.tsx @@ -112,4 +112,11 @@ describe('IncompleteShardingSetup', function () { expect(list).to.be.visible; expect(list.textContent).to.contain(`"location", "secondary"`); }); + + it('Includes code examples', async function () { + await renderWithProps(); + + const example = await screen.findByText(/Example commands/); + expect(example).to.be.visible; + }); }); diff --git a/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx b/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx index cb49176a5ed..72728148cd1 100644 --- a/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx +++ b/packages/compass-global-writes/src/components/states/shard-key-correct.spec.tsx @@ -92,23 +92,10 @@ describe('ShardKeyCorrect', function () { expect(list.textContent).to.contain(`"location", "secondary"`); }); - it('Contains sample codes', async function () { + it('Includes code examples', async function () { await renderWithProps(); - const findingDocumentsSample = await screen.findByTestId( - 'sample-finding-documents' - ); - expect(findingDocumentsSample).to.be.visible; - expect(findingDocumentsSample.textContent).to.contain( - `use db1db["coll1"].find({"location": "US-NY", "secondary": ""})` - ); - - const insertingDocumentsSample = await screen.findByTestId( - 'sample-inserting-documents' - ); - expect(insertingDocumentsSample).to.be.visible; - expect(insertingDocumentsSample.textContent).to.contain( - `use db1db["coll1"].insertOne({"location": "US-NY", "secondary": "",...})` - ); + const example = await screen.findByText(/Example commands/); + expect(example).to.be.visible; }); }); From 0d2143329a22d1217c49c6adb0e00f6ed0803eb1 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 31 Oct 2024 13:51:28 +0100 Subject: [PATCH 08/10] file --- .../example-commands-markup.spec.tsx | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 packages/compass-global-writes/src/components/example-commands-markup.spec.tsx diff --git a/packages/compass-global-writes/src/components/example-commands-markup.spec.tsx b/packages/compass-global-writes/src/components/example-commands-markup.spec.tsx new file mode 100644 index 00000000000..77cc92ed53e --- /dev/null +++ b/packages/compass-global-writes/src/components/example-commands-markup.spec.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { renderWithStore } from '../../tests/create-store'; +import { expect } from 'chai'; +import { screen } from '@mongodb-js/testing-library-compass'; +import ExampleCommandsMarkup, { + type ExampleCommandsMarkupProps, +} from './example-commands-markup'; +import { type ShardKey } from '../store/reducer'; + +describe('ExampleCommandsMarkup', function () { + const db = 'db1'; + const coll = 'coll1'; + const namespace = `${db}.${coll}`; + const shardKey: ShardKey = { + fields: [ + { type: 'RANGE', name: 'location' }, + { type: 'HASHED', name: 'secondary' }, + ], + isUnique: false, + }; + + function renderWithProps(props?: Partial) { + return renderWithStore( + + ); + } + + it('Contains sample codes', async function () { + await renderWithProps(); + + const findingDocumentsSample = await screen.findByTestId( + 'sample-finding-documents' + ); + expect(findingDocumentsSample).to.be.visible; + expect(findingDocumentsSample.textContent).to.contain( + `use db1db["coll1"].find({"location": "US-NY", "secondary": ""})` + ); + + const insertingDocumentsSample = await screen.findByTestId( + 'sample-inserting-documents' + ); + expect(insertingDocumentsSample).to.be.visible; + expect(insertingDocumentsSample.textContent).to.contain( + `use db1db["coll1"].insertOne({"location": "US-NY", "secondary": "",...})` + ); + }); +}); From 618a6838266afe5d5d287e28838aa5afc48e147b Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 4 Nov 2024 14:55:44 +0100 Subject: [PATCH 09/10] address PR comments --- .../states/incomplete-sharding-setup.tsx | 2 + .../src/store/index.spec.ts | 70 ++++++++++++++++++- .../src/store/reducer.ts | 32 ++++++++- 3 files changed, 101 insertions(+), 3 deletions(-) diff --git a/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx b/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx index 2001ad8ac85..8f5b4e0ba4c 100644 --- a/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx +++ b/packages/compass-global-writes/src/components/states/incomplete-sharding-setup.tsx @@ -6,6 +6,7 @@ import { css, ButtonVariant, Link, + SpinLoader, } from '@mongodb-js/compass-components'; import React from 'react'; import ShardKeyMarkup from '../shard-key-markup'; @@ -68,6 +69,7 @@ export function IncompleteShardingSetup({ onClick={onResume} variant={ButtonVariant.Default} isLoading={isSubmittingForSharding} + loadingIndicator={} className={manageBtnStyles} > Enable Global Writes diff --git a/packages/compass-global-writes/src/store/index.spec.ts b/packages/compass-global-writes/src/store/index.spec.ts index e904db5041d..b6e905ac54b 100644 --- a/packages/compass-global-writes/src/store/index.spec.ts +++ b/packages/compass-global-writes/src/store/index.spec.ts @@ -90,7 +90,7 @@ function createStore({ } = {}): GlobalWritesStore { const atlasService = { authenticatedFetch: (uri: string) => { - if (uri.includes(`/geoSharding`) && failsOnShardingRequest()) { + if (uri.endsWith(`/geoSharding`) && failsOnShardingRequest()) { return Promise.reject(new Error('Failed to shard')); } @@ -313,7 +313,7 @@ describe('GlobalWritesStore Store', function () { }); }); - it('incomplete setup -> shard key correct', async function () { + it('incomplete setup -> sharding -> shard key correct', async function () { // initial state -> incomplete shardingSetup clock = sinon.useFakeTimers({ shouldAdvanceTime: true, @@ -335,12 +335,78 @@ describe('GlobalWritesStore Store', function () { 'SUBMITTING_FOR_SHARDING_INCOMPLETE' ); await promise; + + // sharding + expect(store.getState().status).to.equal('SHARDING'); + + // done clock.tick(POLLING_INTERVAL); await waitFor(() => { expect(store.getState().status).to.equal('SHARD_KEY_CORRECT'); }); }); + it('incomplete setup -> sharding -> incomplete setup (request was cancelled)', async function () { + // initial state -> incomplete shardingSetup + clock = sinon.useFakeTimers({ + shouldAdvanceTime: true, + }); + const store = createStore({ + isNamespaceManaged: () => false, + hasShardKey: () => true, + }); + await waitFor(() => { + expect(store.getState().status).to.equal('INCOMPLETE_SHARDING_SETUP'); + expect(store.getState().managedNamespace).to.be.undefined; + }); + + // user asks to resume geosharding + const promise = store.dispatch(resumeManagedNamespace()); + expect(store.getState().status).to.equal( + 'SUBMITTING_FOR_SHARDING_INCOMPLETE' + ); + await promise; + + // sharding + expect(store.getState().status).to.equal('SHARDING'); + + // user cancels the request - we go back to incomplete + const promise2 = store.dispatch(cancelSharding()); + await promise2; + clock.tick(POLLING_INTERVAL); + await waitFor(() => { + expect(store.getState().status).to.equal('INCOMPLETE_SHARDING_SETUP'); + }); + }); + + it('incomplete setup -> incomplete setup (failed manage attempt)', async function () { + // initial state -> incomplete shardingSetup + clock = sinon.useFakeTimers({ + shouldAdvanceTime: true, + }); + const store = createStore({ + isNamespaceManaged: () => false, + hasShardKey: () => true, + failsOnShardingRequest: () => true, + }); + await waitFor(() => { + expect(store.getState().status).to.equal('INCOMPLETE_SHARDING_SETUP'); + expect(store.getState().managedNamespace).to.be.undefined; + }); + + // user asks to resume geosharding + const promise = store.dispatch(resumeManagedNamespace()); + expect(store.getState().status).to.equal( + 'SUBMITTING_FOR_SHARDING_INCOMPLETE' + ); + await promise; + + // it failed + await waitFor(() => { + expect(store.getState().status).to.equal('INCOMPLETE_SHARDING_SETUP'); + }); + }); + it('valid shard key -> incomplete', async function () { // initial state === shard key correct const store = createStore({ diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index e46fe840f84..97d638e023c 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -492,7 +492,9 @@ const reducer: Reducer = (state = initialState, action) => { ) { return { ...state, - status: ShardingStatuses.UNSHARDED, + status: state.shardKey + ? ShardingStatuses.INCOMPLETE_SHARDING_SETUP + : ShardingStatuses.UNSHARDED, shardingError: undefined, }; } @@ -511,6 +513,34 @@ const reducer: Reducer = (state = initialState, action) => { }; } + if ( + isAction( + action, + GlobalWritesActionTypes.SubmittingForShardingErrored + ) && + state.status === ShardingStatuses.SUBMITTING_FOR_SHARDING_ERROR + ) { + return { + ...state, + managedNamespace: undefined, + status: ShardingStatuses.SUBMITTING_FOR_SHARDING_ERROR, + }; + } + + if ( + isAction( + action, + GlobalWritesActionTypes.SubmittingForShardingErrored + ) && + state.status === ShardingStatuses.SUBMITTING_FOR_SHARDING_INCOMPLETE + ) { + return { + ...state, + managedNamespace: undefined, + status: ShardingStatuses.INCOMPLETE_SHARDING_SETUP, + }; + } + if ( isAction( action, From c170975555fd25b13c477ac1bc4dba6d1a37099e Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 4 Nov 2024 17:45:50 +0100 Subject: [PATCH 10/10] fix --- .../src/store/reducer.ts | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 97d638e023c..2c2279f44f3 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -215,11 +215,9 @@ export type RootState = { | ShardingStatuses.UNSHARDED | ShardingStatuses.SUBMITTING_FOR_SHARDING | ShardingStatuses.CANCELLING_SHARDING; - /** - * note: shardKey might exist even for unsharded. - * if the collection was sharded previously and then unmanaged - */ shardKey?: ShardKey; + // shardKey might exist if the collection was sharded before + // and then unmanaged shardingError?: never; pollingTimeout?: never; } @@ -487,14 +485,29 @@ const reducer: Reducer = (state = initialState, action) => { ) && (state.status === ShardingStatuses.CANCELLING_SHARDING || state.status === ShardingStatuses.SHARDING_ERROR || - state.status === ShardingStatuses.CANCELLING_SHARDING_ERROR) + state.status === ShardingStatuses.CANCELLING_SHARDING_ERROR) && // the error might come before the cancel request was processed + !state.shardKey + ) { + return { + ...state, + status: ShardingStatuses.UNSHARDED, + shardingError: undefined, + }; + } + + if ( + isAction( + action, + GlobalWritesActionTypes.CancellingShardingFinished + ) && + state.status === ShardingStatuses.CANCELLING_SHARDING && + state.shardKey ) { return { ...state, - status: state.shardKey - ? ShardingStatuses.INCOMPLETE_SHARDING_SETUP - : ShardingStatuses.UNSHARDED, + shardKey: state.shardKey, + status: ShardingStatuses.INCOMPLETE_SHARDING_SETUP, shardingError: undefined, }; }