-
Notifications
You must be signed in to change notification settings - Fork 188
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(compass-preferences-model): remove compass specific imports fro…
…m the provider entrypoint in compass-preferences-model (#5381) * chore: split utils into compass-utils and non-compass-utils * chore: split in-memory-storage into its own export to avoid importing compass specific stuff from storage * chore: untangle the react related exports * chore: ignore dist for depcheck in databases-collection * chore: pr review fixup * chore: remove irrelevant depcheckrc change * chore: fix import
- Loading branch information
1 parent
7470fcc
commit df6f378
Showing
17 changed files
with
520 additions
and
507 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { ParsedGlobalPreferencesResult } from './global-config'; | ||
import type { PreferencesAccess } from './preferences'; | ||
import type { UserStorage } from './user-storage'; | ||
import { UserStorageImpl } from './user-storage'; | ||
import { getActiveUserId } from './utils'; | ||
import { setupPreferences } from './setup-preferences'; | ||
|
||
export async function setupPreferencesAndUser( | ||
globalPreferences: ParsedGlobalPreferencesResult | ||
): Promise<{ userStorage: UserStorage; preferences: PreferencesAccess }> { | ||
const preferences = await setupPreferences(globalPreferences); | ||
const userStorage = new UserStorageImpl(); | ||
const user = await userStorage.getOrCreate(getActiveUserId(preferences)); | ||
// update user id (telemetryAnonymousId) in preferences if new user was created. | ||
await preferences.savePreferences({ telemetryAnonymousId: user.id }); | ||
await userStorage.updateUser(user.id, { | ||
lastUsed: new Date(), | ||
}); | ||
return { preferences, userStorage }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
packages/compass-preferences-model/src/preferences-in-memory-storage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import type { PreferencesStorage } from './preferences-storage'; | ||
import { getDefaultsForStoredPreferences } from './preferences-schema'; | ||
import type { AllPreferences, StoredPreferences } from './preferences-schema'; | ||
|
||
export class InMemoryStorage implements PreferencesStorage { | ||
private preferences = getDefaultsForStoredPreferences(); | ||
|
||
constructor(preferencesOverrides?: Partial<AllPreferences>) { | ||
this.preferences = { | ||
...this.preferences, | ||
...preferencesOverrides, | ||
}; | ||
} | ||
|
||
getPreferences() { | ||
return this.preferences; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/require-await | ||
async updatePreferences(attributes: Partial<StoredPreferences>) { | ||
this.preferences = { | ||
...this.preferences, | ||
...attributes, | ||
}; | ||
} | ||
|
||
async setup() { | ||
// noop | ||
} | ||
} |
129 changes: 129 additions & 0 deletions
129
packages/compass-preferences-model/src/preferences-persistent-storage.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import fs from 'fs/promises'; | ||
import path from 'path'; | ||
import os from 'os'; | ||
import { PersistentStorage } from './preferences-persistent-storage'; | ||
import { getDefaultsForStoredPreferences } from './preferences-schema'; | ||
import { expect } from 'chai'; | ||
|
||
const getPreferencesFolder = (tmpDir: string) => { | ||
return path.join(tmpDir, 'AppPreferences'); | ||
}; | ||
|
||
const getPreferencesFile = (tmpDir: string) => { | ||
return path.join(getPreferencesFolder(tmpDir), 'General.json'); | ||
}; | ||
|
||
describe('PersistentStorage', function () { | ||
let tmpDir: string; | ||
beforeEach(async function () { | ||
tmpDir = await fs.mkdtemp( | ||
path.join(os.tmpdir(), 'compass-preferences-storage') | ||
); | ||
}); | ||
|
||
afterEach(async function () { | ||
await fs.rmdir(tmpDir, { recursive: true }); | ||
}); | ||
|
||
it('sets up the storage', async function () { | ||
// When user starts compass first time, it creates AppPreferences folder with | ||
// General.json to store default preferences. | ||
|
||
const storage = new PersistentStorage(tmpDir); | ||
|
||
const preferencesDir = getPreferencesFolder(tmpDir); | ||
const preferencesFile = getPreferencesFile(tmpDir); | ||
|
||
expect(async () => await fs.access(preferencesDir)).to.throw; | ||
expect(async () => await fs.access(preferencesFile)).to.throw; | ||
|
||
await storage.setup(); | ||
|
||
expect(async () => await fs.access(preferencesDir)).to.not.throw; | ||
expect(async () => await fs.access(preferencesFile)).to.not.throw; | ||
|
||
expect( | ||
JSON.parse((await fs.readFile(preferencesFile)).toString()) | ||
).to.deep.equal(getDefaultsForStoredPreferences()); | ||
}); | ||
|
||
it('when invalid json is stored, it sets the defaults', async function () { | ||
const storage = new PersistentStorage(tmpDir); | ||
|
||
const preferencesFile = getPreferencesFile(tmpDir); | ||
await fs.mkdir(getPreferencesFolder(tmpDir)); | ||
await fs.writeFile(preferencesFile, '{}}', 'utf-8'); | ||
|
||
// Ensure it exists | ||
expect(async () => await fs.access(preferencesFile)).to.not.throw; | ||
|
||
await storage.setup(); | ||
|
||
expect( | ||
JSON.parse((await fs.readFile(preferencesFile)).toString()) | ||
).to.deep.equal(getDefaultsForStoredPreferences()); | ||
}); | ||
|
||
it('updates preferences', async function () { | ||
const storage = new PersistentStorage(tmpDir); | ||
await storage.setup(); | ||
|
||
await storage.updatePreferences({ currentUserId: '123456789' }); | ||
|
||
const newPreferences = storage.getPreferences(); | ||
|
||
expect(newPreferences).to.deep.equal({ | ||
...getDefaultsForStoredPreferences(), | ||
currentUserId: '123456789', | ||
}); | ||
}); | ||
|
||
it('returns default preference values if its not stored on disk', async function () { | ||
const storage = new PersistentStorage(tmpDir); | ||
|
||
// manually setup the file with no content | ||
await fs.mkdir(getPreferencesFolder(tmpDir)); | ||
await fs.writeFile(getPreferencesFile(tmpDir), JSON.stringify({}), 'utf-8'); | ||
|
||
await storage.updatePreferences({ | ||
currentUserId: '123456789', | ||
}); | ||
|
||
expect(storage.getPreferences()).to.deep.equal({ | ||
...getDefaultsForStoredPreferences(), | ||
currentUserId: '123456789', | ||
}); | ||
}); | ||
|
||
it('does not save random props', async function () { | ||
const storage = new PersistentStorage(tmpDir); | ||
await storage.setup(); | ||
|
||
await storage.updatePreferences({ someThingNotSupported: 'abc' } as any); | ||
|
||
const newPreferences = storage.getPreferences(); | ||
|
||
expect(newPreferences).to.deep.equal(getDefaultsForStoredPreferences()); | ||
}); | ||
|
||
it('strips unknown props when reading from disk', async function () { | ||
const storage = new PersistentStorage(tmpDir); | ||
|
||
// manually setup the file with default props and unknown prop | ||
await fs.mkdir(getPreferencesFolder(tmpDir)); | ||
await fs.writeFile( | ||
getPreferencesFile(tmpDir), | ||
JSON.stringify({ | ||
...getDefaultsForStoredPreferences(), | ||
somethingUnknown: true, | ||
}), | ||
'utf-8' | ||
); | ||
|
||
await storage.setup(); | ||
|
||
expect(storage.getPreferences()).to.deep.equal( | ||
getDefaultsForStoredPreferences() | ||
); | ||
}); | ||
}); |
66 changes: 66 additions & 0 deletions
66
packages/compass-preferences-model/src/preferences-persistent-storage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import type { z } from 'zod'; | ||
import { UserData } from '@mongodb-js/compass-user-data'; | ||
import { | ||
getDefaultsForStoredPreferences, | ||
getPreferencesValidator, | ||
} from './preferences-schema'; | ||
import type { | ||
StoredPreferences, | ||
StoredPreferencesValidator, | ||
} from './preferences-schema'; | ||
|
||
import type { PreferencesStorage } from './preferences-storage'; | ||
|
||
export class PersistentStorage implements PreferencesStorage { | ||
private readonly file = 'General'; | ||
private readonly defaultPreferences = getDefaultsForStoredPreferences(); | ||
private readonly userData: UserData<StoredPreferencesValidator>; | ||
private preferences: StoredPreferences = getDefaultsForStoredPreferences(); | ||
|
||
constructor(basePath?: string) { | ||
this.userData = new UserData(getPreferencesValidator(), { | ||
subdir: 'AppPreferences', | ||
basePath, | ||
}); | ||
} | ||
|
||
async setup() { | ||
try { | ||
this.preferences = await this.readPreferences(); | ||
} catch (e) { | ||
if ( | ||
(e as any).code === 'ENOENT' || // First time user | ||
e instanceof SyntaxError // Invalid json | ||
) { | ||
// Create the file for the first time | ||
await this.userData.write(this.file, this.defaultPreferences); | ||
return; | ||
} | ||
throw e; | ||
} | ||
} | ||
|
||
private async readPreferences(): Promise<StoredPreferences> { | ||
return await this.userData.readOne(this.file, { | ||
ignoreErrors: false, | ||
}); | ||
} | ||
|
||
getPreferences(): StoredPreferences { | ||
return { | ||
...this.defaultPreferences, | ||
...this.preferences, | ||
}; | ||
} | ||
|
||
async updatePreferences( | ||
attributes: Partial<z.input<StoredPreferencesValidator>> | ||
) { | ||
await this.userData.write(this.file, { | ||
...(await this.readPreferences()), | ||
...attributes, | ||
}); | ||
|
||
this.preferences = await this.readPreferences(); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
packages/compass-preferences-model/src/preferences-storage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import type { StoredPreferences } from './preferences-schema'; | ||
|
||
export interface PreferencesStorage { | ||
setup(): Promise<void>; | ||
getPreferences(): StoredPreferences; | ||
updatePreferences(attributes: Partial<StoredPreferences>): Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,6 @@ | ||
import { createContext, useContext } from 'react'; | ||
import { createNoopLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; | ||
import { Preferences, type PreferencesAccess } from './preferences'; | ||
import { type AllPreferences } from './preferences-schema'; | ||
import { InMemoryStorage } from './storage'; | ||
export { usePreference, withPreferences } from './react'; | ||
export * from './react'; | ||
export { ReadOnlyPreferenceAccess } from './read-only-preferences-access'; | ||
export { useIsAIFeatureEnabled } from './utils'; | ||
export { capMaxTimeMSAtPreferenceLimit } from './maxtimems'; | ||
export { featureFlags } from './feature-flags'; | ||
export { getSettingDescription } from './preferences-schema'; | ||
|
||
export class ReadOnlyPreferenceAccess implements PreferencesAccess { | ||
private _preferences: Preferences; | ||
constructor(preferencesOverrides?: Partial<AllPreferences>) { | ||
this._preferences = new Preferences({ | ||
logger: createNoopLoggerAndTelemetry(), | ||
preferencesStorage: new InMemoryStorage(preferencesOverrides), | ||
}); | ||
} | ||
|
||
savePreferences() { | ||
return Promise.resolve(this._preferences.getPreferences()); | ||
} | ||
|
||
refreshPreferences() { | ||
return Promise.resolve(this._preferences.getPreferences()); | ||
} | ||
|
||
getPreferences() { | ||
return this._preferences.getPreferences(); | ||
} | ||
|
||
ensureDefaultConfigurableUserPreferences() { | ||
return this._preferences.ensureDefaultConfigurableUserPreferences(); | ||
} | ||
|
||
getConfigurableUserPreferences() { | ||
return Promise.resolve(this._preferences.getConfigurableUserPreferences()); | ||
} | ||
|
||
getPreferenceStates() { | ||
return Promise.resolve(this._preferences.getPreferenceStates()); | ||
} | ||
|
||
onPreferenceValueChanged() { | ||
return () => { | ||
// noop | ||
}; | ||
} | ||
|
||
createSandbox() { | ||
return Promise.resolve(new ReadOnlyPreferenceAccess(this.getPreferences())); | ||
} | ||
} | ||
|
||
const PreferencesContext = createContext<PreferencesAccess>( | ||
// Our context starts with our read-only preference access but we expect | ||
// different runtimes to provide their own access implementation at render. | ||
new ReadOnlyPreferenceAccess() | ||
); | ||
|
||
export const PreferencesProvider = PreferencesContext.Provider; | ||
|
||
export function preferencesLocator(): PreferencesAccess { | ||
return useContext(PreferencesContext); | ||
} | ||
export type { PreferencesAccess }; |
Oops, something went wrong.