Skip to content

Commit 5960612

Browse files
authored
fix(dashboard): begin caching calls and moving reused functions to server actions (#4654)
Fixes FER-7344 ## Short description of the changes made ## What was the motivation & context behind this PR? ## How has this PR been tested?
1 parent 6382214 commit 5960612

File tree

30 files changed

+520
-586
lines changed

30 files changed

+520
-586
lines changed

packages/commons/docs-loader/src/editable-docs-loader.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,6 @@ export const createEditableDocsLoader = async ({
195195
const docsLoader = await createCachedDocsLoader(host, encodeDocsLoaderDomain(domain, branchName), fernToken, {
196196
returnRawMarkdown: true,
197197
cacheConfig: {
198-
// For editable docs, we want shorter TTL so that cache stays fresh
199-
kvTtl: 5 * 60, // 5 minutes
200198
cacheKeySuffix: "editable",
201199
forceRevalidate
202200
},

packages/fern-dashboard/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
"@fern-api/fdr-sdk": "workspace:*",
5050
"@fern-api/ui-core-utils": "workspace:*",
5151
"@fern-api/venus-api-sdk": "0.20.0",
52-
"@fern-api/visual-editor-server": "workspace:*",
5352
"@fern-docs/components": "workspace:*",
5453
"@fern-docs/edge-config": "workspace:*",
5554
"@fern-docs/mdx": "workspace:*",

packages/fern-dashboard/src/app/[orgName]/(homepage)/docs/[docsUrl]/loading.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 14 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
11
import "server-only";
22

33
import { notFound } from "next/navigation";
4-
5-
import getGithubSourceMetadataHandler from "@/app/api/get-github-source-metadata/handler";
4+
import { Suspense } from "react";
5+
import { getGitHubAuthState } from "@/app/actions/getGithubMetadata";
66
import type { Auth0OrgName } from "@/app/services/auth0/types";
77
import getDocsSitesForOrg from "@/app/services/dal/fdr/getDocsSitesForOrg";
8-
import getDocsGithubUrl from "@/app/services/dal/github/getDocsGithubUrl";
9-
import { validateGithubRepoAccess } from "@/app/services/dal/github/validators";
8+
import { getDocsGithubUrl } from "@/app/services/dal/github/getDocsGithubUrl";
109
import { getAuthenticatedSessionOrRedirect } from "@/app/services/dal/organization";
1110
import { DocsSiteOverviewCard } from "@/components/docs-page/DocsSiteOverviewCard";
12-
import { FernCliVersionDisplay } from "@/components/docs-page/FernCliVersionDisplay";
13-
import { type GithubAuthState, GithubSource } from "@/components/docs-page/GithubSource";
11+
import { VisualEditorLoadingCard } from "@/components/docs-page/visual-editor-section/VisualEditorLoadingCard";
1412
import { VisualEditorSection } from "@/components/docs-page/visual-editor-section/VisualEditorSection";
1513
import { getDocsSiteUrl } from "@/utils/getDocsSiteUrl";
1614
import { parseDocsUrlParam } from "@/utils/parseDocsUrlParam";
1715
import type { EncodedDocsUrl } from "@/utils/types";
1816

19-
export default async function Page(props: {
20-
params: Promise<{ orgName: Auth0OrgName; docsUrl: EncodedDocsUrl }>;
21-
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
22-
}) {
17+
export default async function Page(props: { params: Promise<{ orgName: Auth0OrgName; docsUrl: EncodedDocsUrl }> }) {
2318
const { orgName, docsUrl: encodedDocsUrl } = await props.params;
24-
const searchParams = await props.searchParams;
2519

2620
const session = await getAuthenticatedSessionOrRedirect(orgName);
2721
const docsUrl = parseDocsUrlParam({ docsUrl: encodedDocsUrl });
2822

29-
// Only skip cache if explicitly requested via query param (e.g., ?refresh=true)
30-
const skipCache = searchParams.refresh === "true";
31-
3223
// Validate that the docsUrl belongs to this organization so that we avoid errors in the page
3324
const response = await getDocsSitesForOrg({
3425
orgName,
@@ -43,106 +34,18 @@ export default async function Page(props: {
4334
notFound();
4435
}
4536

46-
let githubUrl = undefined;
47-
let githubAuthState: GithubAuthState = {
48-
validationResult: {
49-
ok: false,
50-
error: {
51-
type: "UNEXPECTED_ERROR",
52-
message: ""
53-
}
54-
},
55-
sourceRepo: undefined,
56-
isLoading: false
57-
};
58-
59-
try {
60-
const urlResult = await getDocsGithubUrl({
61-
url: encodedDocsUrl,
62-
token: session.accessToken
63-
});
64-
65-
if (!urlResult.success) {
66-
if (urlResult.error.type === "DOMAIN_NOT_REGISTERED") {
67-
githubAuthState.validationResult = {
68-
ok: false,
69-
error: {
70-
type: "UNEXPECTED_ERROR",
71-
message: "Domain not registered."
72-
}
73-
};
74-
} else {
75-
githubAuthState.validationResult = {
76-
ok: false,
77-
error: urlResult.error
78-
};
79-
}
80-
} else {
81-
githubUrl = urlResult.githubUrl;
82-
83-
try {
84-
// Parallelize validation and metadata fetching for better performance
85-
const [validation, sourceRepo] = await Promise.all([
86-
validateGithubRepoAccess(
87-
orgName,
88-
docsUrl,
89-
{
90-
type: "url",
91-
githubUrl
92-
},
93-
true // Skip cache for now, since this cache was causing issues with validating repos
94-
),
95-
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
96-
getGithubSourceMetadataHandler({
97-
githubUrl,
98-
userId: session.user.sub
99-
}).catch((error) => {
100-
console.error("Failed to fetch source repo metadata:", error);
101-
return undefined;
102-
})
103-
]);
104-
105-
githubAuthState = {
106-
validationResult: validation,
107-
// Only include sourceRepo if validation succeeded
108-
sourceRepo: validation.ok ? sourceRepo : undefined,
109-
isLoading: false
110-
};
111-
} catch (error) {
112-
console.error("Failed to validate GitHub access:", error);
113-
// Keep default false state
114-
}
115-
}
116-
} catch (error) {
117-
console.error(error);
118-
}
37+
// Start expensive operations in parallel without awaiting
38+
Promise.all([
39+
getDocsGithubUrl(docsUrl, session.accessToken),
40+
getGitHubAuthState(docsUrl, session.accessToken, orgName, session)
41+
]);
11942

12043
return (
12144
<div className="flex w-full flex-col gap-4">
122-
<DocsSiteOverviewCard
123-
docsSite={currentDocsSite}
124-
githubProtectedArea={
125-
<div className="flex flex-wrap gap-x-10 gap-y-4">
126-
<div className="flex w-fit flex-col gap-2">
127-
<p>Source</p>
128-
<GithubSource docsUrl={docsUrl} githubUrl={githubUrl} />
129-
</div>
130-
<FernCliVersionDisplay
131-
orgName={orgName}
132-
docsUrl={docsUrl}
133-
githubUrl={githubUrl}
134-
baseBranch={githubAuthState.sourceRepo?.baseBranch}
135-
/>
136-
</div>
137-
}
138-
/>
139-
<VisualEditorSection
140-
docsUrl={docsUrl}
141-
session={session}
142-
orgName={orgName}
143-
githubAuthState={githubAuthState}
144-
githubUrl={githubUrl}
145-
/>
45+
<DocsSiteOverviewCard docsUrl={docsUrl} docsSite={currentDocsSite} orgName={orgName} />
46+
<Suspense fallback={<VisualEditorLoadingCard />}>
47+
<VisualEditorSection docsUrl={docsUrl} session={session} orgName={orgName} />
48+
</Suspense>
14649
</div>
14750
);
14851
}

packages/fern-dashboard/src/app/[orgName]/(visual-editor)/editor/[docsUrl]/[branch]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type React from "react";
55

66
import { ClientMDXProvider } from "@/app/[orgName]/context/ClientMDXProvider";
77
import { OrgNameProvider } from "@/app/[orgName]/context/OrgNameContext";
8-
import getGithubSourceMetadata from "@/app/api/get-github-source-metadata/handler";
8+
import { getGithubSourceMetadata } from "@/app/actions/getGithubSourceMetadata";
99
import type { Auth0OrgName } from "@/app/services/auth0/types";
1010
import { assertAuthAndFetchGithubUrl } from "@/app/services/dal/github/assertAuthAndFetchGithubUrl";
1111
import { getAuthenticatedSessionOrRedirect } from "@/app/services/dal/organization";

packages/fern-dashboard/src/app/actions/getDocsGithubMetadata.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
import { fernToken_admin } from "@fern-api/docs-server";
44

5-
import type { GithubAuthState } from "@/components/docs-page/GithubSource";
6-
7-
import getGithubSourceMetadataHandler from "../api/get-github-source-metadata/handler";
5+
import type { GithubAuthState } from "@/components/docs-page/GithubSourceClient";
6+
import type { DocsUrl } from "@/utils/types";
87
import { getDocsUrlMetadata } from "../api/utils/getDocsUrlMetadata";
98
import { type Auth0SessionData, getCurrentSessionOrThrow } from "../services/auth0/getCurrentSession";
109
import type { Auth0OrgName } from "../services/auth0/types";
11-
import getDocsGithubUrl from "../services/dal/github/getDocsGithubUrl";
10+
import { getDocsGithubUrl } from "../services/dal/github/getDocsGithubUrl";
1211
import { validateGithubRepoAccess } from "../services/dal/github/validators";
12+
import { getGithubSourceMetadata } from "./getGithubSourceMetadata";
1313

14-
async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, orgName: Auth0OrgName, docsUrl: string) {
14+
async function getMetadata(encodedDocsUrl: DocsUrl, session: Auth0SessionData, orgName: Auth0OrgName, docsUrl: string) {
1515
let githubAuthState: GithubAuthState = {
1616
validationResult: {
1717
ok: false,
@@ -25,10 +25,7 @@ async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, or
2525
};
2626
let githubUrl: string | undefined;
2727
try {
28-
const urlResult = await getDocsGithubUrl({
29-
url: encodedDocsUrl,
30-
token: session.accessToken
31-
});
28+
const urlResult = await getDocsGithubUrl(encodedDocsUrl, session.accessToken);
3229

3330
if (!urlResult.success) {
3431
if (urlResult.error.type === "DOMAIN_NOT_REGISTERED") {
@@ -63,7 +60,7 @@ async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, or
6360
true // Skip cache for now, since this cache was causing issues with validating repos
6461
),
6562
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
66-
getGithubSourceMetadataHandler({
63+
getGithubSourceMetadata({
6764
githubUrl,
6865
userId: session.user.sub
6966
}).catch((error: unknown) => {
@@ -88,7 +85,7 @@ async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, or
8885
}
8986
}
9087

91-
export async function getDocsGithubMetadata(docsUrl: string): Promise<{
88+
export async function getDocsGithubMetadata(docsUrl: DocsUrl): Promise<{
9289
success: boolean;
9390
orgName?: Auth0OrgName;
9491
githubUrl?: string;
@@ -102,10 +99,7 @@ export async function getDocsGithubMetadata(docsUrl: string): Promise<{
10299
url: decodedUrl,
103100
token: fernToken_admin() ?? session.accessToken
104101
});
105-
const githubMetadata = await getDocsGithubUrl({
106-
url: decodedUrl,
107-
token: fernToken_admin() ?? session.accessToken
108-
});
102+
const githubMetadata = await getDocsGithubUrl(docsUrl, fernToken_admin() ?? session.accessToken);
109103
if (!githubMetadata.success) {
110104
return { success: false, error: "Failed to fetch github metadata" };
111105
}
@@ -116,7 +110,7 @@ export async function getDocsGithubMetadata(docsUrl: string): Promise<{
116110

117111
const orgName = docsMetadata.body.org as unknown as Auth0OrgName;
118112

119-
const metadata = await getMetadata(decodedUrl, session, orgName, decodedUrl);
113+
const metadata = await getMetadata(docsUrl, session, orgName, decodedUrl);
120114
if (!metadata?.success) {
121115
return { success: false, error: "Failed to fetch metadata" };
122116
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import "server-only";
2+
import { cache } from "react";
3+
import type { GithubAuthState } from "@/components/docs-page/GithubSourceClient";
4+
import type { DocsUrl } from "@/utils/types";
5+
import type { Auth0SessionData } from "../services/auth0/getCurrentSession";
6+
import type { Auth0OrgName } from "../services/auth0/types";
7+
import { type GetDocsGithubUrlResult, getDocsGithubUrl } from "../services/dal/github/getDocsGithubUrl";
8+
import { validateGithubRepoAccess } from "../services/dal/github/validators";
9+
import { getGithubSourceMetadata } from "./getGithubSourceMetadata";
10+
11+
type GetDocsGithubUrlError = Extract<GetDocsGithubUrlResult, { success: false }>["error"];
12+
13+
export type GetGitHubAuthStateResult = GithubAuthState | { success: false; error: GetDocsGithubUrlError };
14+
15+
export const getGitHubAuthState = cache(
16+
async (
17+
docsUrl: DocsUrl,
18+
token: string,
19+
orgName: Auth0OrgName,
20+
session: Auth0SessionData
21+
): Promise<GetGitHubAuthStateResult> => {
22+
const urlResult = await getDocsGithubUrl(docsUrl, token);
23+
if (!urlResult.success) {
24+
return { success: false, error: urlResult.error };
25+
}
26+
27+
const githubUrl = urlResult.githubUrl;
28+
let githubAuthState: GithubAuthState = {
29+
validationResult: {
30+
ok: false,
31+
error: {
32+
type: "UNEXPECTED_ERROR",
33+
message: ""
34+
}
35+
},
36+
sourceRepo: undefined,
37+
isLoading: false
38+
};
39+
40+
try {
41+
// Parallelize validation and metadata fetching for better performance
42+
const [validation, sourceRepo] = await Promise.all([
43+
validateGithubRepoAccess(
44+
orgName,
45+
docsUrl,
46+
{
47+
type: "url",
48+
githubUrl
49+
},
50+
true // Skip cache for now, since this cache was causing issues with validating repos
51+
),
52+
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
53+
getGithubSourceMetadata({
54+
githubUrl,
55+
userId: session.user.sub
56+
}).catch((error) => {
57+
console.error("Failed to fetch source repo metadata:", error);
58+
return undefined;
59+
})
60+
]);
61+
62+
githubAuthState = {
63+
validationResult: validation,
64+
// Only include sourceRepo if validation succeeded
65+
sourceRepo: validation.ok ? sourceRepo : undefined,
66+
isLoading: false
67+
};
68+
} catch (error) {
69+
console.error("Failed to validate GitHub access:", error);
70+
// Keep default false state
71+
}
72+
73+
return githubAuthState;
74+
}
75+
);

packages/fern-dashboard/src/app/api/get-github-source-metadata/handler.ts renamed to packages/fern-dashboard/src/app/actions/getGithubSourceMetadata.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use server";
2+
13
import { unstable_cache } from "next/cache";
24

35
import { getFernBotInstallationId, getFernBotOctokitForRepo } from "@/app/services/auth0/fernBotOctokit";
@@ -13,7 +15,7 @@ const EMPTY_RESPONSE: GithubSourceRepo = {
1315
fernBotHasInstallationId: undefined
1416
};
1517

16-
export default async function getGithubSourceMetadataHandler({
18+
export async function getGithubSourceMetadata({
1719
githubUrl,
1820
userId,
1921
skipCache = false
@@ -22,7 +24,7 @@ export default async function getGithubSourceMetadataHandler({
2224
userId: string;
2325
skipCache?: boolean;
2426
}): Promise<GithubSourceRepo> {
25-
async function getGithubSourceMetadata() {
27+
async function fetchGithubSourceMetadata() {
2628
if (githubUrl == null) {
2729
throw new Error("NoGithubUrl");
2830
}
@@ -67,14 +69,14 @@ export default async function getGithubSourceMetadataHandler({
6769
try {
6870
// Only cache successful responses; do not cache failures
6971
const result = skipCache
70-
? getGithubSourceMetadata()
71-
: unstable_cache(getGithubSourceMetadata, [`github-source-${githubUrl}-${userId}`], {
72+
? fetchGithubSourceMetadata()
73+
: unstable_cache(fetchGithubSourceMetadata, [`github-source-${githubUrl}-${userId}`], {
7274
revalidate: 300, // 5 minutes
7375
tags: [`github-source-${githubUrl}`]
7476
})();
7577
return await result;
7678
} catch (error) {
77-
console.error("[getDocsGithubSourceHandler]", error);
79+
console.error("[getGithubSourceMetadata]", error);
7880
// On any error, return EMPTY_RESPONSE (but don't cache the error)
7981
return EMPTY_RESPONSE;
8082
}

0 commit comments

Comments
 (0)