-
Notifications
You must be signed in to change notification settings - Fork 2.3k
perf: App start improvement [INS-3957] #7492
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
Changes from 10 commits
e6f1392
8b422cc
77e093d
dcfe687
3875906
d7348d6
a81978a
f18b525
4cc27bc
dc79939
76bdb31
72513b9
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 |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { useEffect, useState } from 'react'; | ||
|
|
||
| export const useLoaderDeferData = <T>(deferedDataPromise?: Promise<T>): [T | undefined, boolean, any] => { | ||
| const [data, setData] = useState<T>(); | ||
| const [error, setError] = useState(); | ||
| const [isPending, setIsPending] = useState(true); | ||
| useEffect(() => { | ||
| if (deferedDataPromise === undefined) { | ||
| return; | ||
| } | ||
| (async () => { | ||
| try { | ||
| const data = await deferedDataPromise; | ||
| setIsPending(false); | ||
| setData(data); | ||
| } catch (err) { | ||
| setError(err); | ||
| console.log('Failed to load defered data', err); | ||
| } | ||
| })(); | ||
| }, [deferedDataPromise]); | ||
|
|
||
| return [data, isPending, error]; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,7 +30,7 @@ import { getAppWebsiteBaseURL } from '../../common/constants'; | |
| import { database } from '../../common/database'; | ||
| import { userSession } from '../../models'; | ||
| import { updateLocalProjectToRemote } from '../../models/helpers/project'; | ||
| import { isOwnerOfOrganization, isPersonalOrganization, isScratchpadOrganizationId, Organization } from '../../models/organization'; | ||
| import { findPersonalOrganization, isOwnerOfOrganization, isPersonalOrganization, isScratchpadOrganizationId, Organization } from '../../models/organization'; | ||
| import { Project } from '../../models/project'; | ||
| import { isDesign, isScratchpad } from '../../models/workspace'; | ||
| import { VCSInstance } from '../../sync/vcs/insomnia-sync'; | ||
|
|
@@ -53,6 +53,7 @@ import { PresentUsers } from '../components/present-users'; | |
| import { Toast } from '../components/toast'; | ||
| import { useAIContext } from '../context/app/ai-context'; | ||
| import { InsomniaEventStreamProvider } from '../context/app/insomnia-event-stream-context'; | ||
| import { syncProjects } from './project'; | ||
| import { useRootLoaderData } from './root'; | ||
| import { UntrackedProjectsLoaderData } from './untracked-projects'; | ||
| import { WorkspaceLoaderData } from './workspace'; | ||
|
|
@@ -128,7 +129,7 @@ function sortOrganizations(accountId: string, organizations: Organization[]): Or | |
| ]; | ||
| } | ||
|
|
||
| async function syncOrganization(sessionId: string, accountId: string) { | ||
| async function syncOrganizations(sessionId: string, accountId: string) { | ||
| try { | ||
| const [organizationsResult, user, currentPlan] = await Promise.all([ | ||
| insomniaFetch<OrganizationsResponse | void>({ | ||
|
|
@@ -164,52 +165,89 @@ async function syncOrganization(sessionId: string, accountId: string) { | |
| } | ||
| } | ||
|
|
||
| interface SyncOrgsAndProjectsActionRequest { | ||
| sessionId: string; | ||
| accountId: string; | ||
| personalOrganizationId?: string; | ||
| organizationId: string; | ||
| } | ||
|
|
||
| // this action is used to run task that we dont want to block the UI | ||
| export const syncOrgsAndProjectsAction: ActionFunction = async ({ request }) => { | ||
| try { | ||
| const { organizationId } = await request.json() as SyncOrgsAndProjectsActionRequest; | ||
| const { id: sessionId, accountId } = await userSession.getOrCreate(); | ||
|
|
||
| invariant(sessionId, 'sessionId is required'); | ||
| invariant(accountId, 'accountId is required'); | ||
| const taskPromiseList = []; | ||
| taskPromiseList.push(syncOrganizations(sessionId, accountId)); | ||
| const organizations = JSON.parse(localStorage.getItem(`${accountId}:organizations`) || '[]') as Organization[]; | ||
| invariant(organizations, 'Failed to fetch organizations.'); | ||
| const personalOrganization = findPersonalOrganization(organizations, accountId); | ||
| invariant(personalOrganization, 'personalOrganization is required'); | ||
| invariant(personalOrganization.id, 'personalOrganizationId is required'); | ||
| taskPromiseList.push(migrateProjectsUnderOrganization(personalOrganization.id, sessionId)); | ||
| invariant(organizationId, 'organizationId is required'); | ||
| taskPromiseList.push(syncProjects(organizationId)); | ||
|
|
||
| await Promise.all(taskPromiseList); | ||
|
|
||
| return {}; | ||
| } catch (error) { | ||
| console.log('Failed to run async task', error); | ||
| return { | ||
| error: error.message, | ||
| }; | ||
| } | ||
| }; | ||
|
|
||
| async function migrateProjectsUnderOrganization(personalOrganizationId: string, sessionId: string) { | ||
|
Contributor
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. its really dangerous to mess with migrations, I'd scope this out if you can without introducing more risk
Member
Author
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. The origin migration logic is in |
||
| if (await shouldMigrateProjectUnderOrganization()) { | ||
| await migrateProjectsIntoOrganization({ | ||
| personalOrganizationId, | ||
| }); | ||
|
|
||
| const preferredProjectType = localStorage.getItem('prefers-project-type'); | ||
| if (preferredProjectType === 'remote') { | ||
| const localProjects = await database.find<Project>('Project', { | ||
| parentId: personalOrganizationId, | ||
| remoteId: null, | ||
| }); | ||
|
|
||
| // If any of those fail projects will still be under the organization as local projects | ||
| for (const project of localProjects) { | ||
| updateLocalProjectToRemote({ | ||
| project, | ||
| organizationId: personalOrganizationId, | ||
| sessionId, | ||
| vcs: VCSInstance(), | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| export const indexLoader: LoaderFunction = async () => { | ||
| const { id: sessionId, accountId } = await userSession.getOrCreate(); | ||
| if (sessionId) { | ||
| await syncOrganization(sessionId, accountId); | ||
| await syncOrganizations(sessionId, accountId); | ||
|
|
||
| const organizations = JSON.parse(localStorage.getItem(`${accountId}:organizations`) || '[]') as Organization[]; | ||
| invariant(organizations, 'Failed to fetch organizations.'); | ||
|
|
||
| const personalOrganization = organizations.filter(isPersonalOrganization) | ||
| .find(organization => | ||
| isOwnerOfOrganization({ | ||
| organization, | ||
| accountId, | ||
| })); | ||
| invariant(personalOrganization, 'Failed to find personal organization your account appears to be in an invalid state. Please contact support if this is a recurring issue.'); | ||
| if (await shouldMigrateProjectUnderOrganization()) { | ||
| await migrateProjectsIntoOrganization({ | ||
| personalOrganization, | ||
| }); | ||
|
|
||
| const preferredProjectType = localStorage.getItem('prefers-project-type'); | ||
| if (preferredProjectType === 'remote') { | ||
| const localProjects = await database.find<Project>('Project', { | ||
| parentId: personalOrganization.id, | ||
| remoteId: null, | ||
| }); | ||
|
|
||
| // If any of those fail projects will still be under the organization as local projects | ||
| for (const project of localProjects) { | ||
| updateLocalProjectToRemote({ | ||
| project, | ||
| organizationId: personalOrganization.id, | ||
| sessionId, | ||
| vcs: VCSInstance(), | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| const personalOrganization = findPersonalOrganization(organizations, accountId); | ||
| invariant(personalOrganization, 'Failed to find personal organization your account appears to be in an invalid state. Please contact support if this is a recurring issue.'); | ||
| const personalOrganizationId = personalOrganization.id; | ||
| await migrateProjectsUnderOrganization(personalOrganizationId, sessionId); | ||
|
|
||
| if (personalOrganization) { | ||
| return redirect(`/organization/${personalOrganization.id}`); | ||
| } | ||
| if (personalOrganization) { | ||
| return redirect(`/organization/${personalOrganizationId}`); | ||
| } | ||
|
|
||
| if (organizations.length > 0) { | ||
| return redirect(`/organization/${organizations[0].id}`); | ||
| } | ||
| if (organizations.length > 0) { | ||
| return redirect(`/organization/${organizations[0].id}`); | ||
| } | ||
| } | ||
|
|
||
| await session.logout(); | ||
|
|
@@ -220,7 +258,7 @@ export const syncOrganizationsAction: ActionFunction = async () => { | |
| const { id: sessionId, accountId } = await userSession.getOrCreate(); | ||
|
|
||
| if (sessionId) { | ||
| await syncOrganization(sessionId, accountId); | ||
| await syncOrganizations(sessionId, accountId); | ||
| } | ||
|
|
||
| return null; | ||
|
|
@@ -400,6 +438,21 @@ const OrganizationRoute = () => { | |
| projectId?: string; | ||
| workspaceId?: string; | ||
| }; | ||
| const syncOrgsAndProjects = useFetcher(); | ||
|
|
||
| useEffect(() => { | ||
| console.log('run async task in useEffect'); | ||
|
CurryYangxx marked this conversation as resolved.
Outdated
|
||
| const isIdleAndUninitialized = syncOrgsAndProjects.state === 'idle' && !syncOrgsAndProjects.data; | ||
| if (isIdleAndUninitialized) { | ||
| syncOrgsAndProjects.submit({ | ||
| organizationId, | ||
| }, { | ||
| action: '/organization/syncOrgsAndProjectsAction', | ||
| method: 'POST', | ||
| encType: 'application/json', | ||
| }); | ||
| } | ||
| }, [userSession.id, organizationId, userSession.accountId, syncOrgsAndProjects]); | ||
|
|
||
| useEffect(() => { | ||
| const isIdleAndUninitialized = untrackedProjectsFetcher.state === 'idle' && !untrackedProjectsFetcher.data; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Probably need double check if It is good enough to say that it is a personal org.