chore: reuse MetaStores instead of recreating on projectRef change

This commit is contained in:
Alaister Young authored and Joshen Lim committed 2022-10-04 14:34:26 +08:00
1 parent 5b00919091
commit d4967a84ea
8 files changed
+154 -69

No files matched your search

@@ -14,8 +14,8 @@ interface Props {}
const NavigationBar: FC<Props> = ({}) => {
const router = useRouter()
const { ui } = useStore()
const projectRef = ui.selectedProjectRef as string
const { selectedProjectRef, ui } = useStore()
const projectRef = selectedProjectRef ?? '[ref]'
const projectBaseInfo = ui.selectedProjectBaseInfo
const ongoingIncident = useFlag('ongoingIncident')
+3 -1
View File
@@ -64,7 +64,9 @@ export function withAuth<T>(
useEffect(() => {
if (!isLoading && router.isReady) {
rootStore.setProjectRef(ref ? String(ref) : undefined)
if (ref) {
rootStore.setProjectRef(Array.isArray(ref) ? ref[0] : ref)
}
rootStore.setOrganizationSlug(slug ? String(slug) : undefined)
}
}, [isLoading, router.isReady, ref, slug])
+33 -44
View File
@@ -24,7 +24,10 @@ export interface IRootStore {
functions: IProjectFunctionsStore
backups: IProjectBackupsStore
authConfig: IProjectAuthConfigStore
setProjectRef: (value?: string) => void
selectedProjectRef?: string
setProjectRef: (value: string) => void
setOrganizationSlug: (value?: string) => void
}
export class RootStore implements IRootStore {
@@ -36,43 +39,17 @@ export class RootStore implements IRootStore {
backups: IProjectBackupsStore
authConfig: IProjectAuthConfigStore
selectedProjectRef: string | undefined
constructor() {
this.app = new AppStore(this)
this.ui = new UiStore(this)
this.meta = new MetaStore(this, { projectRef: '', connectionString: '' })
// @ts-ignore
this.content = new ProjectContentStore(this, { projectRef: '' })
this.functions = new ProjectFunctionsStore(this, { projectRef: '' })
this.backups = new ProjectBackupsStore(this, { projectRef: '' })
this.authConfig = new ProjectAuthConfigStore(this, { projectRef: '' })
/**
* TODO: meta and content are not observable
* meaning that when meta and content object change mobx doesnt trigger new event
*
* Workaround for now
* we need to use ui.selectedProject along with meta and content
* cos whenever ui.selectedProject changes, the reaction will create new meta and content stores
*/
reaction(
() => this.ui.selectedProject,
(selectedProject) => {
if (selectedProject) {
// @ts-ignore
this.meta = new MetaStore(this, {
projectRef: selectedProject.ref,
connectionString: selectedProject.connectionString ?? '',
})
} else {
// @ts-ignore
this.meta = new MetaStore(this, {
projectRef: '',
connectionString: '',
})
}
}
)
}
/**
@@ -80,23 +57,35 @@ export class RootStore implements IRootStore {
*
* This method will also trigger project detail loading when it's not available
*/
setProjectRef(value?: string) {
if (this.ui.selectedProject?.ref === value) return
if (value) {
// fetch project detail when
// - project not found yet. projectStore is loading
// - connectionString is not available. projectStore loaded
const found = this.app.projects.find((x: Project) => x.ref == value)
if (!found || !found.connectionString) {
this.app.projects.fetchDetail(value)
}
setProjectRef(value: string) {
if (this.selectedProjectRef === value) return
this.selectedProjectRef = value
// reset ui projectRef in case of switching projects
// this will show the loading screen instead of showing the previous project
this.ui.setProjectRef(undefined)
const setProjectRefs = (project: Project) => {
this.meta.setProjectDetails(project)
this.functions.setProjectRef(project.ref)
this.authConfig.setProjectRef(project.ref)
this.content.setProjectRef(project.ref)
this.backups.setProjectRef(project.ref)
// ui set must come last
this.ui.setProjectRef(project.ref)
}
this.ui.setProjectRef(value)
this.functions.setProjectRef(value)
this.authConfig.setProjectRef(value)
this.content.setProjectRef(value)
this.backups.setProjectRef(value)
// fetch project detail when
// - project not found yet. projectStore is loading
// - connectionString is not available. projectStore loaded
const found = this.app.projects.find((x: Project) => x.ref === value)
if (!found || !found.connectionString) {
this.app.projects.fetchDetail(value, (project) => {
setProjectRefs(project)
})
} else {
setProjectRefs(found)
}
}
setOrganizationSlug(value?: string) {
+1 -1
View File
@@ -57,7 +57,7 @@ export default class UiStore implements IUiStore {
get selectedProject() {
if (this.selectedProjectRef) {
const found = this.rootStore.app.projects.find(
(x: Project) => x.ref == this.selectedProjectRef
(x: Project) => x.ref === this.selectedProjectRef
)
return !!found?.connectionString ? found : undefined
}
+4 -2
View File
@@ -9,7 +9,7 @@ import PostgresMetaInterface, { IPostgresMetaInterface } from '../common/Postgre
import pingPostgrest from 'lib/pingPostgrest'
export interface IProjectStore extends IPostgresMetaInterface<Project> {
fetchDetail: (projectRef: string) => Promise<void>
fetchDetail: (projectRef: string, callback?: (project: Project) => void) => Promise<void>
}
export default class ProjectStore extends PostgresMetaInterface<Project> {
@@ -44,7 +44,7 @@ export default class ProjectStore extends PostgresMetaInterface<Project> {
this.data = formattedValue
}
async fetchDetail(projectRef: string) {
async fetchDetail(projectRef: string, callback?: (project: Project) => void) {
const url = `${this.url}/${projectRef}`
const headers = constructHeaders(this.headers)
const response = await get(url, { headers })
@@ -56,6 +56,8 @@ export default class ProjectStore extends PostgresMetaInterface<Project> {
// update project detail by key id
this.data[project.id] = project
callback?.(project)
// lazy fetches
if (IS_PLATFORM) {
this.fetchSubscriptionTier(project)
+18 -2
View File
@@ -22,6 +22,9 @@ export interface IPostgresMetaInterface<T> {
find: (filter?: any) => T | undefined
byId: (id: number | string) => T | undefined
initialDataArray: (value: T[]) => void
setUrl: (url: string) => void
setHeaders: (headers: { [prop: string]: any }) => void
}
// [TODO] Need to refactor the logic for 'isInitialized'
@@ -161,8 +164,8 @@ export default class PostgresMetaInterface<T> implements IPostgresMetaInterface<
if (response.error) throw response.error
const data = response as T
const indentity = response[this.identifier]
this.data[indentity] = data
const identity = response[this.identifier]
this.data[identity] = data
return data
} catch (error: any) {
return { data: null, error }
@@ -197,4 +200,17 @@ export default class PostgresMetaInterface<T> implements IPostgresMetaInterface<
return { error }
}
}
setUrl(url: string) {
this.url = url
// if the url changes, we need to reset the state
this.state = this.STATES.INITIAL
this.data = {}
this.error = null
}
setHeaders(headers: { [prop: string]: any }) {
this.headers = headers
}
}
+77 -17
View File
@@ -120,6 +120,8 @@ export interface IMetaStore {
columns: ColumnField[],
isRealtimeEnabled: boolean
) => any
setProjectDetails: (details: { ref: string; connectionString?: string }) => void
}
export default class MetaStore implements IMetaStore {
rootStore: IRootStore
@@ -140,6 +142,7 @@ export default class MetaStore implements IMetaStore {
projectRef?: string
connectionString?: string
baseUrl: string
headers: { [prop: string]: any }
// [Joshen] I'm going to treat this as a list of system schemas
excludedSchemas = [
@@ -158,33 +161,45 @@ export default class MetaStore implements IMetaStore {
'graphql_public',
]
constructor(rootStore: IRootStore, options: { projectRef: string; connectionString: string }) {
constructor(rootStore: IRootStore, options: { projectRef: string; connectionString?: string }) {
const { projectRef, connectionString } = options
this.rootStore = rootStore
this.projectRef = projectRef
this.baseUrl = `${API_URL}/pg-meta/${projectRef}`
const headers: any = {}
this.headers = {}
if (IS_PLATFORM && connectionString) {
this.connectionString = connectionString
headers['x-connection-encrypted'] = connectionString
this.headers['x-connection-encrypted'] = connectionString
}
this.openApi = new OpenApiStore(rootStore, `${API_URL}/props/project/${projectRef}/api`)
this.tables = new TableStore(rootStore, `${this.baseUrl}/tables`, headers)
this.columns = new ColumnStore(rootStore, `${this.baseUrl}/columns`, headers)
this.schemas = new SchemaStore(rootStore, `${this.baseUrl}/schemas`, headers)
this.openApi = new OpenApiStore(
this.rootStore,
`${API_URL}/props/project/${this.projectRef}/api`
)
this.tables = new TableStore(this.rootStore, `${this.baseUrl}/tables`, this.headers)
this.columns = new ColumnStore(this.rootStore, `${this.baseUrl}/columns`, this.headers)
this.schemas = new SchemaStore(this.rootStore, `${this.baseUrl}/schemas`, this.headers)
this.roles = new RolesStore(rootStore, `${this.baseUrl}/roles`, headers)
this.policies = new PoliciesStore(rootStore, `${this.baseUrl}/policies`, headers)
this.hooks = new HooksStore(rootStore, `${this.baseUrl}/triggers`, headers)
this.triggers = new TriggersStore(rootStore, `${this.baseUrl}/triggers`, headers)
this.functions = new FunctionsStore(rootStore, `${this.baseUrl}/functions`, headers)
this.extensions = new ExtensionsStore(rootStore, `${this.baseUrl}/extensions`, headers, {
identifier: 'name',
})
this.publications = new PublicationStore(rootStore, `${this.baseUrl}/publications`, headers)
this.types = new TypesStore(rootStore, `${this.baseUrl}/types`, headers)
this.roles = new RolesStore(this.rootStore, `${this.baseUrl}/roles`, this.headers)
this.policies = new PoliciesStore(this.rootStore, `${this.baseUrl}/policies`, this.headers)
this.hooks = new HooksStore(this.rootStore, `${this.baseUrl}/triggers`, this.headers)
this.triggers = new TriggersStore(this.rootStore, `${this.baseUrl}/triggers`, this.headers)
this.functions = new FunctionsStore(this.rootStore, `${this.baseUrl}/functions`, this.headers)
this.extensions = new ExtensionsStore(
this.rootStore,
`${this.baseUrl}/extensions`,
this.headers,
{
identifier: 'name',
}
)
this.publications = new PublicationStore(
this.rootStore,
`${this.baseUrl}/publications`,
this.headers
)
this.types = new TypesStore(this.rootStore, `${this.baseUrl}/types`, this.headers)
makeObservable(this, {
excludedSchemas: observable,
@@ -803,4 +818,49 @@ export default class MetaStore implements IMetaStore {
onProgressUpdate(insertProgress * 100)
}
}
setProjectDetails({ ref, connectionString }: { ref: string; connectionString?: string }) {
this.projectRef = ref
this.baseUrl = `${API_URL}/pg-meta/${ref}`
if (IS_PLATFORM && connectionString) {
this.connectionString = connectionString
this.headers['x-connection-encrypted'] = connectionString
}
this.openApi.setUrl(`${API_URL}/props/project/${this.projectRef}/api`)
this.openApi.setHeaders(this.headers)
this.tables.setUrl(`${this.baseUrl}/tables`)
this.tables.setHeaders(this.headers)
this.columns.setUrl(`${this.baseUrl}/columns`)
this.columns.setHeaders(this.headers)
this.schemas.setUrl(`${this.baseUrl}/schemas`)
this.schemas.setHeaders(this.headers)
this.roles.setUrl(`${this.baseUrl}/roles`)
this.roles.setHeaders(this.headers)
this.policies.setUrl(`${this.baseUrl}/policies`)
this.policies.setHeaders(this.headers)
this.hooks.setUrl(`${this.baseUrl}/triggers`)
this.hooks.setHeaders(this.headers)
this.triggers.setUrl(`${this.baseUrl}/triggers`)
this.triggers.setHeaders(this.headers)
this.functions.setUrl(`${this.baseUrl}/functions`)
this.functions.setHeaders(this.headers)
this.extensions.setUrl(`${this.baseUrl}/extensions`)
this.extensions.setHeaders(this.headers)
this.publications.setUrl(`${this.baseUrl}/publications`)
this.publications.setHeaders(this.headers)
this.types.setUrl(`${this.baseUrl}/types`)
this.types.setHeaders(this.headers)
}
}
+16
View File
@@ -15,6 +15,9 @@ export interface IOpenApiStore {
isLoading: boolean
load: () => void
setUrl: (url: string) => void
setHeaders: (headers: { [prop: string]: any }) => void
}
export default class OpenApiStore implements IOpenApiStore {
STATES = {
@@ -126,4 +129,17 @@ export default class OpenApiStore implements IOpenApiStore {
setError(value: any) {
this.error = value
}
setUrl(url: string) {
this.url = url
// if the url changes, we need to reset the state
this.state = this.STATES.INITIAL
this.data = undefined
this.error = null
}
setHeaders(headers: { [prop: string]: any }) {
this.headers = headers
}
}