From 89c985497c45c83cb9730d6de8b51c6a56dfbb0d Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Thu, 13 Feb 2025 17:12:45 -0600 Subject: [PATCH] Refine admin app insights data --- __tests__/github.test.ts | 6 +- src/admin/AdminAppInsights.tsx | 30 ++++- src/admin/AdminAppInsightsClient.tsx | 185 ++++++++++++++++++--------- src/admin/github/index.ts | 28 ++-- src/app-core/config.ts | 29 +++-- src/app/template-url/route.tsx | 23 ++-- src/components/RepoLink.tsx | 5 +- src/components/ScoreCard.tsx | 8 +- src/components/ScoreCardRow.tsx | 15 ++- 9 files changed, 225 insertions(+), 104 deletions(-) diff --git a/__tests__/github.test.ts b/__tests__/github.test.ts index 552ea006..e6277ac4 100644 --- a/__tests__/github.test.ts +++ b/__tests__/github.test.ts @@ -1,11 +1,11 @@ import { getGitHubMetaWithFallback, getGitHubPublicFork } from '@/admin/github'; -import { TEMPLATE_BASE_OWNER, TEMPLATE_BASE_REPO } from '@/app-core/config'; +import { TEMPLATE_REPO_OWNER, TEMPLATE_REPO_NAME } from '@/app-core/config'; describe('GitHub', () => { it('fetches base repo meta', async () => { const meta = await getGitHubMetaWithFallback({ - owner: TEMPLATE_BASE_OWNER, - repo: TEMPLATE_BASE_REPO, + owner: TEMPLATE_REPO_OWNER, + repo: TEMPLATE_REPO_NAME, }); expect(meta).toBeDefined(); expect(meta.url).toBeDefined(); diff --git a/src/admin/AdminAppInsights.tsx b/src/admin/AdminAppInsights.tsx index c25fdd3b..8bb2fa57 100644 --- a/src/admin/AdminAppInsights.tsx +++ b/src/admin/AdminAppInsights.tsx @@ -6,17 +6,32 @@ import { getUniqueTags, } from '@/photo/db/query'; import AdminAppInsightsClient from './AdminAppInsightsClient'; -import { APP_CONFIGURATION } from '@/app-core/config'; +import { + APP_CONFIGURATION, + IS_VERCEL_GIT_PROVIDER_GITHUB, + VERCEL_GIT_BRANCH, + VERCEL_GIT_COMMIT_SHA, + VERCEL_GIT_REPO_OWNER, + VERCEL_GIT_REPO_SLUG, +} from '@/app-core/config'; +import { getGitHubMetaWithFallback } from './github'; + +const owner = VERCEL_GIT_REPO_OWNER; +const repo = VERCEL_GIT_REPO_SLUG; +const branch = VERCEL_GIT_BRANCH; +const commit = VERCEL_GIT_COMMIT_SHA; export default async function AdminAppInsights() { const [ { count, dateRange }, + { count: countHidden }, tags, cameras, filmSimulations, lenses, ] = await Promise.all([ getPhotosMeta({ hidden: 'include' }), + getPhotosMeta({ hidden: 'only' }), getUniqueTags(), getUniqueCameras(), getUniqueFilmSimulations(), @@ -28,16 +43,29 @@ export default async function AdminAppInsights() { hasVercelBlobStorage, } = APP_CONFIGURATION; + const codeMeta = IS_VERCEL_GIT_PROVIDER_GITHUB + ? await getGitHubMetaWithFallback({ + owner, + repo, + branch, + commit, + }) + : undefined; + return ( , + codeMeta?: Awaited> + recommendations: Record photoStats: { photosCount: number + photosCountHidden: number tagsCount: number camerasCount: number filmSimulationsCount: number lensesCount: number dateRange?: PhotoDateRange }, - debug?: boolean, + debug?: boolean }) { const { descriptionWithSpaces } = dateRangeForPhotos(undefined, dateRange); const renderTitle = (title: string) =>
{title} @@ -64,19 +82,53 @@ export default function AdminAppInsightsClient({ 'w-full sm:w-[80%] lg:w-[60%]', 'space-y-4 md:space-y-6', )}> + {(codeMeta?.isBaseRepo || codeMeta?.isForkedFromBase || debug) && <> + {renderTitle('Build details')} + + } + content={
+
+
{codeMeta?.owner ?? TEMPLATE_REPO_OWNER}
+
/
+
{codeMeta?.repo ?? TEMPLATE_REPO_NAME}
+
+
+
+
+ {codeMeta?.branch ?? TEMPLATE_REPO_BRANCH} +
+
+
} + /> + {(codeMeta?.behindBy || debug) && + } + // eslint-disable-next-line max-len + content={`This fork is ${codeMeta?.behindBy ?? 9} commits behind`} + additionalContent={<> + Sync your fork to receive new features and fixes + } + />} + } + icon={} + content={
+
+ {VERCEL_GIT_COMMIT_SHA_SHORT ?? DEBUG_COMMIT_SHA} +
+
+ {codeMeta?.commit ?? DEBUG_COMMIT_MESSAGE} +
+
} + /> +
+ } + {renderTitle('Template recommendations')} - - } - content="This fork is 9 commits behind" - additionalContent={<> - Sync your fork to receive new features and fixes - } - /> - {renderTitle('Code Observability')} - {(fork || debug) && - }> - Consider forking this repository to receive new features and fixes - } - {(forkBehind || debug) && - - This fork is 9 commits behind - } - {renderTitle('Template Recommendations')} - {(ai || debug) && }> - Enable AI text generation in the app configuration - } - {(aiRateLimiting || debug) && - Consider enabling rate limiting to mitigate AI abuse - } {renderTitle('Library Stats')} -
-
Photos
-
{photosCount}
-
Tags
-
{tagsCount}
-
Cameras
-
{camerasCount}
-
Films
-
{filmSimulationsCount}
-
Lenses
-
{lensesCount}
- - {descriptionWithSpaces} - -
+ + } + content={<> + {photosCount} photos + {photosCountHidden > 0 && + ` (${photosCountHidden} hidden)`} + } + /> + } + content={`${tagsCount} tags`} + /> + } + content={`${camerasCount} cameras`} + /> + {filmSimulationsCount && + + + } + content={`${filmSimulationsCount} film simulations`} + />} + } + content={`${lensesCount} lenses`} + /> + } + content={descriptionWithSpaces} + /> +
); diff --git a/src/admin/github/index.ts b/src/admin/github/index.ts index f6625b05..fae49ffd 100644 --- a/src/admin/github/index.ts +++ b/src/admin/github/index.ts @@ -1,7 +1,7 @@ import { - TEMPLATE_BASE_OWNER, - TEMPLATE_BASE_REPO, - TEMPLATE_BASE_BRANCH, + TEMPLATE_REPO_OWNER, + TEMPLATE_REPO_NAME, + TEMPLATE_REPO_BRANCH, } from '@/app-core/config'; const DEFAULT_BRANCH = 'main'; @@ -23,8 +23,8 @@ interface RepoParams { // Website urls export const getGitHubRepoUrl = ({ - owner = TEMPLATE_BASE_OWNER, - repo = TEMPLATE_BASE_REPO, + owner = TEMPLATE_REPO_OWNER, + repo = TEMPLATE_REPO_NAME, }: RepoParams = {}) => `https://github.com/${owner}/${repo}`; @@ -34,13 +34,13 @@ export const getGitHubCompareUrl = ({ branch = DEFAULT_BRANCH, }: RepoParams = {}) => // eslint-disable-next-line max-len - `${getGitHubRepoUrl({ owner, repo })}/compare/${branch}...${TEMPLATE_BASE_OWNER}:${TEMPLATE_BASE_REPO}:${TEMPLATE_BASE_BRANCH}`; + `${getGitHubRepoUrl({ owner, repo })}/compare/${branch}...${TEMPLATE_REPO_OWNER}:${TEMPLATE_REPO_NAME}:${TEMPLATE_REPO_BRANCH}`; // API urls const getGitHubApiRepoUrl = ({ - owner = TEMPLATE_BASE_OWNER, - repo = TEMPLATE_BASE_REPO, + owner = TEMPLATE_REPO_OWNER, + repo = TEMPLATE_REPO_NAME, }: RepoParams = {}) => `https://api.github.com/repos/${owner}/${repo}`; @@ -56,10 +56,10 @@ const getGitHubApiCompareToRepoUrl = ({ branch = DEFAULT_BRANCH, }: RepoParams = {}) => // eslint-disable-next-line max-len - `${getGitHubApiRepoUrl()}/compare/${TEMPLATE_BASE_BRANCH}...${owner}:${repo}:${branch}`; + `${getGitHubApiRepoUrl()}/compare/${TEMPLATE_REPO_BRANCH}...${owner}:${repo}:${branch}`; const getGitHubApiCompareToCommitUrl = ({ commit }: RepoParams = {}) => - `${getGitHubApiRepoUrl()}/compare/${TEMPLATE_BASE_BRANCH}...${commit}`; + `${getGitHubApiRepoUrl()}/compare/${TEMPLATE_REPO_BRANCH}...${commit}`; // Requests @@ -74,7 +74,7 @@ const getIsRepoForkedFromBase = async (params: RepoParams) => { const data = await response.json(); return ( Boolean(data.fork) && - data.source?.full_name === `${TEMPLATE_BASE_OWNER}/${TEMPLATE_BASE_REPO}` + data.source?.full_name === `${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}` ); }; @@ -97,8 +97,8 @@ const getGitHubCommitsBehindFromCommit = async (params?: RepoParams) => { }; const isRepoBaseRepo = ({ owner, repo }: RepoParams) => - owner?.toLowerCase() === TEMPLATE_BASE_OWNER && - repo?.toLowerCase() === TEMPLATE_BASE_REPO; + owner?.toLowerCase() === TEMPLATE_REPO_OWNER && + repo?.toLowerCase() === TEMPLATE_REPO_NAME; export const getGitHubPublicFork = async ( params?: RepoParams, @@ -144,6 +144,7 @@ const getGitHubMeta = async (params: RepoParams) => { : 'This fork is up to date.'; return { + ...params, url, isForkedFromBase, isBaseRepo, @@ -160,6 +161,7 @@ export const getGitHubMetaWithFallback = (params: RepoParams) => .catch(e => { console.error('Error retrieving GitHub meta', { params, error: e }); return { + ...params, url: undefined, isForkedFromBase: false, isBaseRepo: undefined, diff --git a/src/app-core/config.ts b/src/app-core/config.ts index 449e0fe8..03afd8d0 100644 --- a/src/app-core/config.ts +++ b/src/app-core/config.ts @@ -6,17 +6,21 @@ import type { StorageType } from '@/services/storage'; import { makeUrlAbsolute, shortenUrl } from '@/utility/url'; // HARD-CODED GLOBAL CONFIGURATION + export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined; -// META / SOURCE / DOMAINS -export const SITE_TITLE = - process.env.NEXT_PUBLIC_SITE_TITLE || - 'Photo Blog'; +// TEMPLATE META -// SOURCE -export const TEMPLATE_BASE_OWNER = 'sambecker'; -export const TEMPLATE_BASE_REPO = 'exif-photo-blog'; -export const TEMPLATE_BASE_BRANCH = 'main'; +export const TEMPLATE_TITLE = 'Photo Blog'; +export const TEMPLATE_DESCRIPTION = 'Store photos with original camera data'; + +// SOURCE CODE + +export const TEMPLATE_REPO_OWNER = 'sambecker'; +export const TEMPLATE_REPO_NAME = 'exif-photo-blog'; +export const TEMPLATE_REPO_BRANCH = 'main'; +// eslint-disable-next-line max-len +export const TEMPLATE_REPO_URL = `https://github.com/${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}`; export const VERCEL_GIT_PROVIDER = process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER; @@ -59,6 +63,12 @@ export const IS_PREVIEW = VERCEL_ENV === 'preview'; export const VERCEL_BYPASS_KEY = 'x-vercel-protection-bypass'; export const VERCEL_BYPASS_SECRET = process.env.VERCEL_AUTOMATION_BYPASS_SECRET; +// SITE META + +export const SITE_TITLE = + process.env.NEXT_PUBLIC_SITE_TITLE || + TEMPLATE_TITLE; + // User-facing domain, potential site title const SITE_DOMAIN = process.env.NEXT_PUBLIC_SITE_DOMAIN || @@ -90,11 +100,14 @@ export const SITE_ABOUT = process.env.NEXT_PUBLIC_SITE_ABOUT; export const HAS_DEFINED_SITE_DESCRIPTION = Boolean(process.env.NEXT_PUBLIC_SITE_DESCRIPTION); +// STORAGE + // STORAGE: DATABASE export const HAS_DATABASE = Boolean(process.env.POSTGRES_URL); export const POSTGRES_SSL_ENABLED = process.env.DISABLE_POSTGRES_SSL === '1' ? false : true; + // STORAGE: VERCEL KV export const HAS_VERCEL_KV = Boolean(process.env.KV_URL); diff --git a/src/app/template-url/route.tsx b/src/app/template-url/route.tsx index 6bf109cd..79599351 100644 --- a/src/app/template-url/route.tsx +++ b/src/app/template-url/route.tsx @@ -1,24 +1,25 @@ /* eslint-disable max-len */ +import { + TEMPLATE_REPO_OWNER, + TEMPLATE_REPO_NAME, + TEMPLATE_DESCRIPTION, + TEMPLATE_TITLE, +} from '@/app-core/config'; import { NextResponse } from 'next/server'; const REQUIRE_ENV_VARS = false; -const TITLE = 'Photo Blog'; -const DESCRIPTION = 'Store photos with original camera data'; -const REPO_TEAM = 'sambecker'; -const REPO_NAME = 'exif-photo-blog'; - export function GET() { const url = new URL('https://vercel.com/new/clone'); - url.searchParams.set('demo-title', TITLE); - url.searchParams.set('demo-description', DESCRIPTION); + url.searchParams.set('demo-title', TEMPLATE_TITLE); + url.searchParams.set('demo-description', TEMPLATE_DESCRIPTION); url.searchParams.set('demo-url', 'https://photos.sambecker.com'); - url.searchParams.set('demo-description', DESCRIPTION); + url.searchParams.set('demo-description', TEMPLATE_DESCRIPTION); url.searchParams.set('demo-image', 'https://photos.sambecker.com/template-image-tight'); - url.searchParams.set('project-name', TITLE); - url.searchParams.set('repository-name', REPO_NAME); - url.searchParams.set('repository-url', `https://github.com/${REPO_TEAM}/${REPO_NAME}`); + url.searchParams.set('project-name', TEMPLATE_TITLE); + url.searchParams.set('repository-name', TEMPLATE_REPO_NAME); + url.searchParams.set('repository-url', `https://github.com/${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}`); url.searchParams.set('from', 'templates'); url.searchParams.set('skippable-integrations', '1'); if (REQUIRE_ENV_VARS) { diff --git a/src/components/RepoLink.tsx b/src/components/RepoLink.tsx index cc0b6469..621c6f82 100644 --- a/src/components/RepoLink.tsx +++ b/src/components/RepoLink.tsx @@ -1,3 +1,4 @@ +import { TEMPLATE_REPO_NAME, TEMPLATE_REPO_URL } from '@/app-core/config'; import { clsx } from 'clsx/lite'; import Link from 'next/link'; import { BiLogoGithub } from 'react-icons/bi'; @@ -9,7 +10,7 @@ export default function RepoLink() { Made with - exif-photo-blog + {TEMPLATE_REPO_NAME} ); diff --git a/src/components/ScoreCard.tsx b/src/components/ScoreCard.tsx index c86bfb9d..d658ee7b 100644 --- a/src/components/ScoreCard.tsx +++ b/src/components/ScoreCard.tsx @@ -1,12 +1,18 @@ +import clsx from 'clsx/lite'; import { ReactNode } from 'react'; export default function ScoreCard({ children, + className, }: { children: ReactNode, + className?: string, }) { return ( -
+
{children}
); diff --git a/src/components/ScoreCardRow.tsx b/src/components/ScoreCardRow.tsx index b7e055dc..243a4b84 100644 --- a/src/components/ScoreCardRow.tsx +++ b/src/components/ScoreCardRow.tsx @@ -1,6 +1,7 @@ import { clsx } from 'clsx'; import { ReactNode, useState } from 'react'; import { FaMinus, FaPlus } from 'react-icons/fa6'; + export default function ScoreCardRow({ icon, content, @@ -11,16 +12,20 @@ export default function ScoreCardRow({ additionalContent?: ReactNode }) { const [isExpanded, setIsExpanded] = useState(false); + return (
-
+
{icon}
-
+
{content}
{isExpanded && @@ -31,7 +36,7 @@ export default function ScoreCardRow({ {additionalContent &&