diff --git a/packages/compass-crud/src/stores/crud-store.spec.ts b/packages/compass-crud/src/stores/crud-store.spec.ts index fcbac4d3d89..50eba4ff896 100644 --- a/packages/compass-crud/src/stores/crud-store.spec.ts +++ b/packages/compass-crud/src/stores/crud-store.spec.ts @@ -1663,7 +1663,8 @@ describe('store', function () { const plugin = activatePlugin(); store = plugin.store; deactivate = () => plugin.deactivate(); - await dataService.insertOne('compass-crud.test', { name: 'testing' }); + await dataService.insertOne('compass-crud.test', { name: 'testing1' }); + await dataService.insertOne('compass-crud.test', { name: 'testing2' }); }); afterEach(function () { @@ -1680,9 +1681,36 @@ describe('store', function () { (state) => { expect(state.error).to.equal(null); - expect(state.docs).to.have.length(1); + expect(state.docs).to.have.length(2); + expect(state.docs[0].doc.name).to.equal('testing1'); expect(state.debouncingLoad).to.equal(false); - expect(state.count).to.equal(1); + expect(state.count).to.equal(2); + expect(state.start).to.equal(1); + expect(state.shardKeys).to.deep.equal({}); + }, + ]); + + void store.refreshDocuments(); + + await listener; + }); + + it('uses the sort order from preferences', async function () { + await preferences.savePreferences({ + defaultSortOrder: '{ _id: -1 }', + }); + const listener = waitForStates(store, [ + (state) => { + expect(state.debouncingLoad).to.equal(true); + expect(state.count).to.equal(null); + }, + + (state) => { + expect(state.error).to.equal(null); + expect(state.docs).to.have.length(2); + expect(state.docs[0].doc.name).to.equal('testing2'); + expect(state.debouncingLoad).to.equal(false); + expect(state.count).to.equal(2); expect(state.start).to.equal(1); expect(state.shardKeys).to.deep.equal({}); }, diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 3475a8392ad..13afb48418a 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -6,6 +6,7 @@ import semver from 'semver'; import StateMixin from '@mongodb-js/reflux-state-mixin'; import type { Element } from 'hadron-document'; import { Document } from 'hadron-document'; +import { validate } from 'mongodb-query-parser'; import HadronDocument from 'hadron-document'; import _parseShellBSON, { ParseMode } from '@mongodb-js/shell-bson-parser'; import type { PreferencesAccess } from 'compass-preferences-model/provider'; @@ -1615,8 +1616,16 @@ class CrudStoreImpl countOptions.hint = '_id_'; } + let sort = query.sort; + if (!sort && this.preferences.getPreferences().defaultSortOrder) { + sort = validate( + 'sort', + this.preferences.getPreferences().defaultSortOrder + ); + } + const findOptions = { - sort: query.sort, + sort, projection: query.project, skip: query.skip, limit: docsPerPage, diff --git a/packages/compass-preferences-model/src/preferences-schema.ts b/packages/compass-preferences-model/src/preferences-schema.ts index 4e64d527446..cde2c1a2ea1 100644 --- a/packages/compass-preferences-model/src/preferences-schema.ts +++ b/packages/compass-preferences-model/src/preferences-schema.ts @@ -17,6 +17,14 @@ import { export const THEMES_VALUES = ['DARK', 'LIGHT', 'OS_THEME'] as const; export type THEMES = typeof THEMES_VALUES[number]; +export const SORT_ORDER_VALUES = [ + '', + '{ $natural: -1 }', + '{ _id: 1 }', + '{ _id: -1 }', +] as const; +export type SORT_ORDERS = typeof SORT_ORDER_VALUES[number]; + export type PermanentFeatureFlags = { showDevFeatureFlags?: boolean; enableDebugUseCsfleSchemaMap?: boolean; @@ -69,6 +77,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags & enableGenAISampleDocumentPassing: boolean; enablePerformanceAdvisorBanner: boolean; maximumNumberOfActiveConnections?: number; + defaultSortOrder: SORT_ORDERS; enableShowDialogOnQuit: boolean; enableCreatingNewConnections: boolean; enableProxySupport: boolean; @@ -187,7 +196,13 @@ type PreferenceDefinition = { /** A description used for the --help text and the Settings UI */ description: K extends keyof InternalUserPreferences ? null - : { short: string; long?: string }; + : { + short: string; + long?: string; + options?: AllPreferences[K] extends string + ? { [k in AllPreferences[K]]: { label: string; description: string } } + : never; + }; /** A method for deriving the current semantic value of this option, even if it differs from the stored value */ deriveValue?: DeriveValueFunction; /** A method for cleaning up/normalizing input from the command line or global config file */ @@ -199,6 +214,7 @@ type PreferenceDefinition = { ? boolean : false : boolean; + validator: z.Schema< AllPreferences[K], z.ZodTypeDef, @@ -537,6 +553,39 @@ export const storedUserPreferencesProps: Required<{ validator: z.boolean().default(false), type: 'boolean', }, + /** + * Set the default sort. + */ + defaultSortOrder: { + ui: true, + cli: true, + global: true, + description: { + short: 'Default Sort for Query Bar', + long: "All queries executed from the query bar will apply the sort order '$natural: -1'.", + options: { + '': { + label: '$natural: 1 (MongoDB server default)', + description: 'in natural order of documents', + }, + '{ $natural: -1 }': { + label: '$natural: -1', + description: 'in reverse natural order of documents', + }, + '{ _id: 1 }': { + label: '_id: 1', + description: 'in ascending order by id', + }, + '{ _id: -1 }': { + label: '_id: -1', + description: 'in descending order by id', + }, + }, + }, + validator: z.enum(SORT_ORDER_VALUES).default(''), + type: 'string', + }, + /** * Switch to enable DevTools in Electron. */ diff --git a/packages/compass-preferences-model/src/provider.ts b/packages/compass-preferences-model/src/provider.ts index ef2fa77b65c..42395233548 100644 --- a/packages/compass-preferences-model/src/provider.ts +++ b/packages/compass-preferences-model/src/provider.ts @@ -10,6 +10,6 @@ export { } from './utils'; export { capMaxTimeMSAtPreferenceLimit } from './maxtimems'; export { featureFlags } from './feature-flags'; -export { getSettingDescription } from './preferences-schema'; -export type { AllPreferences } from './preferences-schema'; +export { getSettingDescription, SORT_ORDER_VALUES } from './preferences-schema'; +export type { AllPreferences, SORT_ORDERS } from './preferences-schema'; export type { DevtoolsProxyOptions } from '@mongodb-js/devtools-proxy-support'; diff --git a/packages/compass-settings/src/components/settings/general.spec.tsx b/packages/compass-settings/src/components/settings/general.spec.tsx index 3c666420721..fb498be3330 100644 --- a/packages/compass-settings/src/components/settings/general.spec.tsx +++ b/packages/compass-settings/src/components/settings/general.spec.tsx @@ -55,6 +55,16 @@ describe('GeneralSettings', function () { }); }); + it('renders defaultSortOrder', function () { + expect(within(container).getByTestId('defaultSortOrder')).to.exist; + }); + + it('changes defaultSortOrder value when selecting an option', function () { + within(container).getByTestId('defaultSortOrder').click(); + within(container).getByText('_id: 1').click(); + expect(getSettings()).to.have.property('defaultSortOrder', '{ _id: 1 }'); + }); + ['maxTimeMS'].forEach((option) => { it(`renders ${option}`, function () { expect(within(container).getByTestId(option)).to.exist; diff --git a/packages/compass-settings/src/components/settings/general.tsx b/packages/compass-settings/src/components/settings/general.tsx index 3962ac54b08..98ec03a3d18 100644 --- a/packages/compass-settings/src/components/settings/general.tsx +++ b/packages/compass-settings/src/components/settings/general.tsx @@ -5,6 +5,7 @@ const generalFields = [ 'readOnly', 'enableShell', 'protectConnectionStrings', + 'defaultSortOrder', 'showKerberosPasswordField', 'maxTimeMS', 'enableDevTools', diff --git a/packages/compass-settings/src/components/settings/settings-list.tsx b/packages/compass-settings/src/components/settings/settings-list.tsx index bb8b0ac79af..4950add99c6 100644 --- a/packages/compass-settings/src/components/settings/settings-list.tsx +++ b/packages/compass-settings/src/components/settings/settings-list.tsx @@ -4,6 +4,7 @@ import { getSettingDescription, featureFlags, } from 'compass-preferences-model/provider'; +import { SORT_ORDER_VALUES } from 'compass-preferences-model/provider'; import { settingStateLabels } from './state-labels'; import { Checkbox, @@ -12,6 +13,8 @@ import { css, spacing, TextInput, + Select, + Option, FormFieldContainer, Badge, } from '@mongodb-js/compass-components'; @@ -157,6 +160,55 @@ function NumericSetting({ ); } +function DefaultSortOrderSetting({ + name, + onChange, + value, + disabled, +}: { + name: PreferenceName; + onChange: HandleChange; + value: string; + disabled: boolean; +}) { + const optionDescriptions = getSettingDescription(name).description.options; + const onChangeCallback = useCallback( + (value: string) => { + onChange(name, value as UserConfigurablePreferences[PreferenceName]); + }, + [name, onChange] + ); + + return ( + <> + + + + ); +} + function StringSetting({ name, onChange, @@ -263,9 +315,16 @@ function SettingsInput({ disabled={!!disabled} /> ); - } - - if (type === 'number') { + } else if (type === 'string' && name === 'defaultSortOrder') { + input = ( + + ); + } else if (type === 'number') { input = ( ); - } - - if (type === 'string') { + } else if (type === 'string') { input = (