Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/landing/components/landing/LandingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AnalyticStat } from '@jetstream/types';
import AnalyticsSummary from './AnalyticsSummary';
import ConnectWithTeam from './ConnectWithTeam';
import FeatureGrid from './FeatureGrid';
import FeatureScreenshot from './FeatureScreenshot';
Expand All @@ -7,13 +9,12 @@ import Learn from './Learn';
import PersonaFeatures from './PersonaFeatures';
import Testimonial from './Testimonial';

export const LandingPage = () => (
export const LandingPage = ({ stats }: { stats?: AnalyticStat[] | null }) => (
<div className="bg-gray-900">
<main>
<HeaderCta />
<PersonaFeatures />
{/* Analytics tracking has been broken for some time - these numbers are not accurate */}
{/* <AnalyticsSummary stats={stats} /> */}
{stats && stats.length > 0 && <AnalyticsSummary stats={stats} />}
<ConnectWithTeam />
<FeatureScreenshot />
<FeatureGrid />
Expand Down
15 changes: 9 additions & 6 deletions apps/landing/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { AnalyticStat } from '@jetstream/types';
import { GetStaticProps, InferGetStaticPropsType } from 'next';
import LandingPage from '../components/landing/LandingPage';
import { fetchBlogPosts } from '../utils/data';
import { fetchAnalyticsSummary, fetchBlogPosts } from '../utils/data';

export default function Page({ omitBlogPosts }: InferGetStaticPropsType<typeof getStaticProps>) {
return <LandingPage />;
export default function Page({ stats }: InferGetStaticPropsType<typeof getStaticProps>) {
return <LandingPage stats={stats} />;
}

// This also gets called at build time
export const getStaticProps: GetStaticProps<{
omitBlogPosts: boolean;
stats: AnalyticStat[] | null;
}> = async () => {
const blogPostsWithRelated = await fetchBlogPosts();
return { props: { omitBlogPosts: Object.values(blogPostsWithRelated || {}).length === 0 } };
const [blogPostsWithRelated, stats] = await Promise.all([fetchBlogPosts(), fetchAnalyticsSummary()]);
// omitBlogPosts was unused by LandingPage, so we drop it here
void blogPostsWithRelated;
Comment on lines +14 to +16
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still fetches blog posts but then discards them. The comment says omitBlogPosts was dropped because it was unused, but it doesn’t explain why the Contentful fetch is still necessary. Either remove the fetchBlogPosts() call, or add a clear note that it’s intentionally warming the in-memory cache for other getStaticProps/getStaticPaths during the build.

Suggested change
const [blogPostsWithRelated, stats] = await Promise.all([fetchBlogPosts(), fetchAnalyticsSummary()]);
// omitBlogPosts was unused by LandingPage, so we drop it here
void blogPostsWithRelated;
const stats = await fetchAnalyticsSummary();

Copilot uses AI. Check for mistakes.
return { props: { stats } };
};
101 changes: 101 additions & 0 deletions apps/landing/utils/data.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AnalyticStat } from '@jetstream/types';
import { createClient } from 'contentful';
import { AuthorsById, BlogPost, BlogPostsBySlug, ContentfulBlogPostField, ContentfulIncludes } from './types';

Expand Down Expand Up @@ -68,3 +69,103 @@ export async function fetchBlogPosts() {

return blogPostsBySlug;
}

const AMPLITUDE_CHART_IDS = {
LOAD: { YEAR: 'jgshgwcl', MONTH: 'iyt2blcf' },
QUERY: { YEAR: '4lacgp5q', MONTH: 'icruamqk' },
FIELD_CREATION: { YEAR: 'tyu5pjug', MONTH: 'adzowzyc' },
APEX_EXECUTED: { YEAR: 'afxl6h2d' },
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AMPLITUDE_CHART_IDS includes APEX_EXECUTED, but it isn’t referenced anywhere in fetchAnalyticsSummary(). This looks like leftover configuration and makes it harder to tell which charts are actually in use; either wire it into the returned stats or remove it until it’s needed.

Suggested change
APEX_EXECUTED: { YEAR: 'afxl6h2d' },

Copilot uses AI. Check for mistakes.
DEPLOYMENTS: { YEAR: 'rz9tpgjy', MONTH: '262an8ek' },
};

function formatStatValue(value: number): string {
if (value >= 1_000_000_000) {
return `${(value / 1_000_000_000).toFixed(1)}B+`;
}
if (value >= 1_000_000) {
return `${(value / 1_000_000).toFixed(1)}M+`;
}
if (value >= 1_000) {
return `${Math.round(value / 1_000)}K+`;
}
return value.toLocaleString();
}

async function fetchAmplitudeChart(chartId: string, authHeader: string): Promise<number> {
const response = await fetch(`https://amplitude.com/api/3/chart/${chartId}/query`, {
headers: { Authorization: authHeader },
});
if (!response.ok) {
throw new Error(`Amplitude API error for chart ${chartId}: ${response.status}`);
}
const data = await response.json();
return data.data.seriesCollapsed[0][0].value;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchAmplitudeChart() assumes the Amplitude response always contains data.data.seriesCollapsed[0][0].value. If Amplitude returns an empty series (or changes shape), this will throw a generic TypeError and cause the entire summary to be dropped. Consider validating the JSON shape and throwing a more explicit error (or returning a safe fallback) so failures are easier to diagnose.

Suggested change
return data.data.seriesCollapsed[0][0].value;
const seriesCollapsed = data?.data?.seriesCollapsed;
if (
!Array.isArray(seriesCollapsed) ||
seriesCollapsed.length === 0 ||
!Array.isArray(seriesCollapsed[0]) ||
seriesCollapsed[0].length === 0
) {
throw new Error(`Unexpected Amplitude response shape for chart ${chartId}: missing seriesCollapsed data`);
}
const firstPoint = seriesCollapsed[0][0];
if (!firstPoint || typeof firstPoint.value !== 'number') {
throw new Error(`Unexpected Amplitude response shape for chart ${chartId}: invalid value type`);
}
return firstPoint.value;

Copilot uses AI. Check for mistakes.
}

export async function fetchAnalyticsSummary(): Promise<AnalyticStat[] | null> {
if (!process.env.AMPLITUDE_API_KEY || !process.env.AMPLITUDE_SECRET_KEY) {
return null;
}

try {
const authHeader = `Basic ${Buffer.from(`${process.env.AMPLITUDE_API_KEY}:${process.env.AMPLITUDE_SECRET_KEY}`).toString('base64')}`;

const loadYear = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.LOAD.YEAR, authHeader);
const loadMonth = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.LOAD.MONTH, authHeader);

const queryYear = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.QUERY.YEAR, authHeader);
const queryMonth = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.QUERY.MONTH, authHeader);

const fieldCreationYear = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.FIELD_CREATION.YEAR, authHeader);
const fieldCreationMonth = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.FIELD_CREATION.MONTH, authHeader);

const deploymentsYear = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.DEPLOYMENTS.YEAR, authHeader);
const deploymentsMonth = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.DEPLOYMENTS.MONTH, authHeader);

Comment on lines +113 to +124
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchAnalyticsSummary() fetches 8 charts sequentially, which will unnecessarily slow down getStaticProps/build time and increases the chance of timeouts. These calls can be made in parallel (e.g., Promise.all / Promise.allSettled) since they’re independent.

Suggested change
const loadYear = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.LOAD.YEAR, authHeader);
const loadMonth = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.LOAD.MONTH, authHeader);
const queryYear = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.QUERY.YEAR, authHeader);
const queryMonth = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.QUERY.MONTH, authHeader);
const fieldCreationYear = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.FIELD_CREATION.YEAR, authHeader);
const fieldCreationMonth = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.FIELD_CREATION.MONTH, authHeader);
const deploymentsYear = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.DEPLOYMENTS.YEAR, authHeader);
const deploymentsMonth = await fetchAmplitudeChart(AMPLITUDE_CHART_IDS.DEPLOYMENTS.MONTH, authHeader);
const [
loadYear,
loadMonth,
queryYear,
queryMonth,
fieldCreationYear,
fieldCreationMonth,
deploymentsYear,
deploymentsMonth,
] = await Promise.all([
fetchAmplitudeChart(AMPLITUDE_CHART_IDS.LOAD.YEAR, authHeader),
fetchAmplitudeChart(AMPLITUDE_CHART_IDS.LOAD.MONTH, authHeader),
fetchAmplitudeChart(AMPLITUDE_CHART_IDS.QUERY.YEAR, authHeader),
fetchAmplitudeChart(AMPLITUDE_CHART_IDS.QUERY.MONTH, authHeader),
fetchAmplitudeChart(AMPLITUDE_CHART_IDS.FIELD_CREATION.YEAR, authHeader),
fetchAmplitudeChart(AMPLITUDE_CHART_IDS.FIELD_CREATION.MONTH, authHeader),
fetchAmplitudeChart(AMPLITUDE_CHART_IDS.DEPLOYMENTS.YEAR, authHeader),
fetchAmplitudeChart(AMPLITUDE_CHART_IDS.DEPLOYMENTS.MONTH, authHeader),
]);

Copilot uses AI. Check for mistakes.
const lastUpdated = new Date().toISOString();

return [
{ id: 'load-year', name: 'Records loaded in the past year', value: formatStatValue(loadYear), valueRaw: loadYear, lastUpdated },
{ id: 'load-month', name: 'Records loaded in the past month', value: formatStatValue(loadMonth), valueRaw: loadMonth, lastUpdated },
{ id: 'query-year', name: 'SOQL queries run in the past year', value: formatStatValue(queryYear), valueRaw: queryYear, lastUpdated },
{
id: 'query-month',
name: 'SOQL queries run in the past month',
value: formatStatValue(queryMonth),
valueRaw: queryMonth,
lastUpdated,
},
{
id: 'field-creation-year',
name: 'Fields created in the past year',
value: formatStatValue(fieldCreationYear),
valueRaw: fieldCreationYear,
lastUpdated,
},
{
id: 'field-creation-month',
name: 'Fields created in the past month',
value: formatStatValue(fieldCreationMonth),
valueRaw: fieldCreationMonth,
lastUpdated,
},
{
id: 'deployments-year',
name: 'Deployments in the past year',
value: formatStatValue(deploymentsYear),
valueRaw: deploymentsYear,
lastUpdated,
},
{
id: 'deployments-month',
name: 'Deployments in the past month',
value: formatStatValue(deploymentsMonth),
valueRaw: deploymentsMonth,
lastUpdated,
},
];
} catch (error) {
console.error('[ANALYTICS SUMMARY] Error fetching analytics from Amplitude:', error);
return null;
}
}