-
Notifications
You must be signed in to change notification settings - Fork 239
feat(workspaces): Save and restore tabs COMPASS-9499 #7253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 42 commits
68b9e53
a9ea81e
fc3f50c
5932624
8cd1ac5
b5c6f66
b308225
a5713b4
2351763
619362d
25cf5e6
e3876f9
8908793
920f572
aa78f68
42a15e0
9736436
3fd3b6b
04b30c3
321a649
b667dd5
27bae4b
0d14953
2932698
88c8435
da2be3e
b2eb338
9f9bab3
b0af4d9
992f191
13e2c7d
097a002
4f1d184
5aeb75d
ef2d42e
a7df6a5
76ae816
a6d7db9
139f4f4
e4f9a7a
dec0e41
cac3263
a8c7218
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| export type { ReadAllResult } from './user-data'; | ||
| export { type IUserData, FileUserData, AtlasUserData } from './user-data'; | ||
| export { IUserData, FileUserData, AtlasUserData } from './user-data'; | ||
| export { z } from 'zod'; |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see this file ever being used, why did you added it? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { WorkspacesStorageServiceProviderDesktop } from './workspaces-storage-desktop'; | ||
| export { WorkspacesStorageServiceProviderWeb } from './workspaces-storage-web'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import React, { useRef } from 'react'; | ||
| import { FileUserData, type IUserData } from '@mongodb-js/compass-user-data'; | ||
| import { | ||
| WorkspacesStateSchema, | ||
| WorkspacesStorageServiceContext, | ||
| } from './workspaces-storage'; | ||
| import { EJSON } from 'bson'; | ||
|
|
||
| export const WorkspacesStorageServiceProviderDesktop: React.FunctionComponent = | ||
| ({ children }) => { | ||
| const storageRef = useRef<IUserData<typeof WorkspacesStateSchema>>( | ||
| new FileUserData(WorkspacesStateSchema, 'WorkspacesState', { | ||
| serialize: (content) => | ||
| EJSON.stringify(content, { | ||
| relaxed: false, | ||
| }), | ||
| deserialize: (content: string) => EJSON.parse(content), | ||
syn-zhu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) as IUserData<typeof WorkspacesStateSchema> | ||
| ); | ||
| return ( | ||
| <WorkspacesStorageServiceContext.Provider value={storageRef.current}> | ||
| {children} | ||
| </WorkspacesStorageServiceContext.Provider> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import React, { useRef } from 'react'; | ||
| import { AtlasUserData, type IUserData } from '@mongodb-js/compass-user-data'; | ||
| import { | ||
| WorkspacesStateSchema, | ||
| WorkspacesStorageServiceContext, | ||
| } from './workspaces-storage'; | ||
| import { EJSON } from 'bson'; | ||
|
|
||
| export const WorkspacesStorageServiceProviderWeb: React.FunctionComponent<{ | ||
| orgId: string; | ||
| projectId: string; | ||
| getResourceUrl: (path?: string) => string; | ||
| authenticatedFetch: ( | ||
| url: RequestInfo | URL, | ||
| options?: RequestInit | ||
| ) => Promise<Response>; | ||
| }> = ({ orgId, projectId, getResourceUrl, authenticatedFetch, children }) => { | ||
| const storageRef = useRef<IUserData<typeof WorkspacesStateSchema>>( | ||
| new AtlasUserData(WorkspacesStateSchema, 'WorkspacesState', { | ||
| orgId, | ||
| projectId, | ||
| getResourceUrl, | ||
| authenticatedFetch, | ||
| serialize: (content) => | ||
| EJSON.stringify(content, { | ||
| relaxed: false, | ||
| }), | ||
| deserialize: (content: string) => EJSON.parse(content), | ||
syn-zhu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) | ||
| ); | ||
| return ( | ||
| <WorkspacesStorageServiceContext.Provider value={storageRef.current}> | ||
| {children} | ||
| </WorkspacesStorageServiceContext.Provider> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| import { createServiceLocator } from '@mongodb-js/compass-app-registry'; | ||
| import { | ||
| IUserData, | ||
| type ReadAllResult, | ||
| z, | ||
| } from '@mongodb-js/compass-user-data'; | ||
| import React, { useContext } from 'react'; | ||
| import { collectionSubtabValues } from '../types'; | ||
|
|
||
| const CollectionSubtabSchema = z.enum(collectionSubtabValues); | ||
|
|
||
| export const WorkspaceTabSchema = z | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is better, but only half way there IMO. As I said before, these types are now even more of a copy of what's defined in types.ts. Can you maybe walk me through your thought process here? Is there any reason you don't want to replace existing types in types.ts with types derived from the schemas? I don't see the reason not to do it, but maybe there's one I'm missing 🙂 Like imagine next time someone needs to add a new workspace, now they need to add exactly the same types twice in two different places, is there a good reason to do so? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I just don't understand what the suggestion is 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
How exactly do I actually do this? I think I'm getting a bit mixed up on the terminology here, can we maybe refer to specific file / class names? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to follow the suggestion but I don't actually understand what the suggestion is It sounds like you're implying that there's some way to convert a typescript type (from types.ts) "directly" into a zod schema but I'm just not aware of how you would do that |
||
| .discriminatedUnion('type', [ | ||
| z.object({ | ||
| type: z.literal('Welcome'), | ||
| }), | ||
| z.object({ | ||
| type: z.literal('My Queries'), | ||
| }), | ||
| z.object({ | ||
| type: z.literal('Data Modeling'), | ||
| }), | ||
| z.object({ | ||
| type: z.literal('Databases'), | ||
| connectionId: z.string(), | ||
| }), | ||
| z.object({ | ||
| type: z.literal('Performance'), | ||
| connectionId: z.string(), | ||
| }), | ||
| z.object({ | ||
| type: z.literal('Shell'), | ||
| connectionId: z.string(), | ||
| initialEvaluate: z.union([z.string(), z.array(z.string())]).optional(), | ||
| initialInput: z.string().optional(), | ||
| }), | ||
| z.object({ | ||
| type: z.literal('Collections'), | ||
| connectionId: z.string(), | ||
| namespace: z.string(), | ||
| inferredFromPrivileges: z.boolean().optional(), | ||
| }), | ||
| z.object({ | ||
| type: z.literal('Collection'), | ||
| subTab: CollectionSubtabSchema, | ||
| initialQuery: z.record(z.any()).optional(), | ||
| initialPipeline: z.array(z.record(z.any())).optional(), | ||
| initialPipelineText: z.string().optional(), | ||
| initialAggregation: z.record(z.any()).optional(), | ||
| editViewName: z.string().optional(), | ||
| connectionId: z.string(), | ||
| namespace: z.string(), | ||
| inferredFromPrivileges: z.boolean().optional(), | ||
| }), | ||
| ]) | ||
| .and( | ||
| z.object({ | ||
| id: z.string(), | ||
| }) | ||
| ); | ||
|
|
||
| export const WorkspacesStateSchema = z.object({ | ||
| tabs: z.array(WorkspaceTabSchema), | ||
| activeTabId: z.string().nullable(), | ||
| timestamp: z.number(), | ||
| }); | ||
|
|
||
| // TypeScript types derived from the schemas | ||
| export type WorkspaceTabData = z.output<typeof WorkspaceTabSchema>; | ||
| export type WorkspacesStateData = z.output<typeof WorkspacesStateSchema>; | ||
|
|
||
| const throwIfNotTestEnv = () => { | ||
| if (process.env.NODE_ENV !== 'test') { | ||
| throw new Error("Can't find Workspaces storage service in React context"); | ||
| } | ||
| }; | ||
|
|
||
| export class noopUserData<T extends z.Schema> extends IUserData<T> { | ||
| write(): Promise<boolean> { | ||
| throwIfNotTestEnv(); | ||
| return Promise.resolve(true); | ||
| } | ||
| delete(): Promise<boolean> { | ||
| throwIfNotTestEnv(); | ||
| return Promise.resolve(true); | ||
| } | ||
| readAll(): Promise<ReadAllResult<T>> { | ||
| throwIfNotTestEnv(); | ||
| return Promise.resolve({ data: [], errors: [] }); | ||
| } | ||
| readOne(): Promise<z.output<T>> { | ||
| throwIfNotTestEnv(); | ||
| return Promise.resolve(undefined); | ||
| } | ||
| updateAttributes(): Promise<boolean> { | ||
| throwIfNotTestEnv(); | ||
| return Promise.resolve(true); | ||
| } | ||
| } | ||
|
|
||
| export const noopWorkspacesStorageService: IUserData< | ||
| typeof WorkspacesStateSchema | ||
| > = new noopUserData(WorkspacesStateSchema, 'WorkspacesState'); | ||
|
|
||
| export const WorkspacesStorageServiceContext = React.createContext< | ||
| IUserData<typeof WorkspacesStateSchema> | ||
| >(noopWorkspacesStorageService); | ||
|
|
||
| export const workspacesStorageServiceLocator = createServiceLocator(() => { | ||
| const service = useContext(WorkspacesStorageServiceContext); | ||
| return service; | ||
| }, 'workspacesStorageServiceLocator'); | ||
Uh oh!
There was an error while loading. Please reload this page.