fix(layoutCounters): make it works@@ -10,7 +10,6 @@ import InstantRouterIndicator from "../islands/InstantRouterIndicator";
// app components
import { PageHeader } from "./PageHeader";
import { DrawerPrimary } from "./DrawerPrimary";
-import { withLayoutCounters } from "../hocs/withLayoutCounters";
const BRANDLINE_HEIGHT = 4;
const HEADER_HEIGHT = 64;
@@ -134,8 +133,9 @@ const LayoutComponent: FC<LayoutProps & WithThemeSchemeProp> = (props) => {
// const _LayoutWithCounters = withLayoutCounters(
// LayoutComponent as NonNullable<() => JSX.Element>,
// );
-withLayoutCounters;
-export const Layout = LayoutComponent as NonNullable<() => JSX.Element>;
+// withLayoutCounters; // as NonNullable<() => JSX.Element>;
+// withLayoutCounters;
+export const Layout = LayoutComponent;
/* Styled components (unchanged from previous layout structure) */
const StyledLayoutWrapper = styled.div<WithThemeSchemeProp>`
@@ -1,40 +0,0 @@
-// 1st-party
-import type { ReqHandler } from "@ethicdevs/react-monolith";
-// app
-import type { RepositoryCountersDTO } from "../../types";
-import { AppRoute, AppRouteParams } from "../../routes.defs";
-
-const getRepositoryCounters: ReqHandler<
- AppRouteParams,
- AppRoute.REPOSITORY_COUNTERS_API
-> = async (request, reply) => {
- const { orgSlug, repoSlug } = request.params;
-
- // total pulls in repository (PRs targeting this repo)
- const totalPulls = await request.prisma.pullRequest.count({
- where: {
- targetRepository: {
- organization: { slug: orgSlug },
- slug: repoSlug,
- },
- },
- });
-
- // for now provide pulls and zeros
- const counters: RepositoryCountersDTO = {
- pulls: totalPulls,
- tests: 0,
- builds: 0,
- issues: 0,
- apiRefSymbols: 0,
- helpCenterNotifs: 0,
- };
-
- reply.send(JSON.stringify(counters) + "\n");
-};
-
-export default getRepositoryCounters;
-
-export const APIControllers = {
- getRepositoryCounters,
-};
@@ -8,4 +8,3 @@ export { SSHAuthController } from "./ssh-auth";
export { SyntaxHighlightController } from "./syntaxHighlight";
export { ThemeController } from "./theme";
export { UserController } from "./user";
-export { APIControllers } from "./api/getRepositoryCounters";
@@ -6,7 +6,7 @@ import { parse as parseHtmlToJson, TextNode, RealNode } from "himalaya";
// app
import type { AppThemeScheme } from "../../types";
import { AppRoute, AppRouteParams } from "../../routes.defs";
-import { escapeHtmlCode } from "../../utils/shared/escapeHtmlCode";
+import escapeHtmlCode from "../../utils/shared/escapeHtmlCode";
Prism.languages.prisma = Prism.languages.extend("javascript", {
keyword: /\b(?:datasource|enum|generator|model|type)\b/,
@@ -32,7 +32,7 @@ const syntaxHighlightThemes: Record<AppThemeScheme, string> = {
function getHighlightedCode(
code: string,
language: string,
- themeScheme: AppThemeScheme
+ themeScheme: AppThemeScheme,
): { html: string; cssRules: string; durationMs: number } {
const _startTimeMs = Date.now();
const isLanguageSupportedByPrism = !!(
@@ -62,7 +62,7 @@ const getNodeTextRecursive = (node: TextNode | RealNode, depth = 0): string => {
const getNodesRecursive = (
node: TextNode | RealNode,
- depth = 0
+ depth = 0,
): { text: string; type: string }[] => {
if (depth > 1000) throw new Error("Too much recursion.");
return node.type === "text"
@@ -78,8 +78,8 @@ const getNodesRecursive = (
childNode.type === "text"
? "text"
: childNode.attributes[0].key === "class"
- ? childNode.attributes[0].value.replace(/^token /i, "")
- : "attr",
+ ? childNode.attributes[0].value.replace(/^token /i, "")
+ : "attr",
}));
};
@@ -111,17 +111,20 @@ const highlightCodeAction: ReqHandler<
return reply.status(200).send(result);
} else if (outputFormat === "json") {
const parsedJson = parseHtmlToJson(result.html);
- const tokens = parsedJson.reduce((acc, node) => {
- if (node.type === "text") {
- acc = [
- ...acc,
- { text: node.content.replace(/\r\n/i, "\n"), type: "text" },
- ];
- } else {
- acc = [...acc, ...getNodesRecursive(node)];
- }
- return acc;
- }, [] as { text: string; type: string }[]);
+ const tokens = parsedJson.reduce(
+ (acc, node) => {
+ if (node.type === "text") {
+ acc = [
+ ...acc,
+ { text: node.content.replace(/\r\n/i, "\n"), type: "text" },
+ ];
+ } else {
+ acc = [...acc, ...getNodesRecursive(node)];
+ }
+ return acc;
+ },
+ [] as { text: string; type: string }[],
+ );
return reply.status(200).send(tokens);
}
@@ -1,58 +0,0 @@
-// 3rd-party
-import React, { useEffect, useState } from "react";
-// app
-import type { LayoutProps, RepositoryCountersDTO } from "../types";
-import { Env } from "../env";
-
-// A lightweight HOC that loads SSR counters from /api/repos/:orgSlug/:repoSlug/counters
-// and injects them as `layoutCounters` prop into the wrapped Layout component.
-export function withLayoutCounters<P extends object>(
- WrappedComponent: React.FC<P & LayoutProps>,
-): React.FC<P & LayoutProps> {
- const ComponentWithCounters: React.FC<P & LayoutProps> = ({
- orgSlug,
- repoSlug,
- ...props
- }) => {
- const [layoutCounters, setLayoutCounters] = useState<
- RepositoryCountersDTO | undefined
- >(undefined);
-
- // SSR: on first render, layoutCounters may be pre-populated by the server.
- // Client: fetch the API endpoint to hydrate counters.
- useEffect(() => {
- // SSR: allow server to inject counters via HTML if possible; fallback to fetch
- const baseUrl = `${Env.DEPLOYMENT_SCHEME}://${Env.DEPLOYMENT_DOMAIN}${Env.DEPLOYMENT_SCHEME !== "https" ? `:${Env.PORT}` : ""}`;
- const endpoint = `${baseUrl}/api/repos/${orgSlug}/${repoSlug}/counters`;
-
- console.log("hoc(layoutCounters): will fetch url:", endpoint);
-
- fetch(endpoint, { credentials: "same-origin" })
- .then((r) => (r.ok ? r.json() : {}))
- .then((data) => {
- if (data && Object.keys(data).length > 0) {
- console.log("hoc(layoutCounters): fetched data:", data);
- setLayoutCounters(data);
- }
- })
- .catch((err) => {
- const { message } = err as Error;
- console.error("hoc(layoutCounters): fetch error:", message);
- });
- }, [orgSlug, repoSlug]);
-
- const WrappedComponentEl = WrappedComponent as React.FC<P & LayoutProps>;
-
- return (
- <WrappedComponentEl
- {...(props as unknown as P)}
- {...(props as unknown as LayoutProps)}
- orgSlug={orgSlug}
- repoSlug={repoSlug}
- layoutCounters={layoutCounters}
- />
- );
- };
-
- return ComponentWithCounters;
-}
@@ -37,6 +37,7 @@ import { makeGitServerService } from "./services/gitServer";
import {
getEnv,
getGitStamp,
+ loadRepositoryCounters,
localAppDomainPreHandler,
makeRequestHandler,
sessionSetupPreHandler,
@@ -337,6 +338,9 @@ async function main(): Promise<AppServer> {
},
});
+ // load repository counters before rendering the page
+ s.addHook("preHandler", loadRepositoryCounters);
+
// check that a session is started, or start it.
s.addHook("preHandler", sessionSetupPreHandler);
@@ -23,6 +23,7 @@ import { default as makeGetRepositoryRefDiff } from "./getRepositoryRefDiff";
import { default as makeGetRepositoryRemoteRefDiff } from "./getRepositoryRemoteRefDiff";
import { default as makeGetRepositoryTags } from "./getRepositoryTags";
import { default as makeIsFileInRepositoryPath } from "./isFileInRepositoryPath";
+import { default as makeGetRepositoryCounters } from "./getRepositoryCounters";
export const makeRepositoryService = makeService<
RepositoryServiceAPI,
@@ -48,4 +49,5 @@ export const makeRepositoryService = makeService<
getRepositoryRemoteRefDiff: makeGetRepositoryRemoteRefDiff,
getRepositoryTags: makeGetRepositoryTags,
isFileInRepositoryPath: makeIsFileInRepositoryPath,
+ getRepositoryCounters: makeGetRepositoryCounters,
});
@@ -8,6 +8,7 @@ import type { FastifyRequest } from "fastify";
import type { Organization, Repository, User } from "@prisma/client";
// app
import type {
+ RepositoryCountersDTO,
RepositoryFile,
RepositoryFileContent,
RepositoryFileDiff,
@@ -50,29 +51,29 @@ export interface ForkRepositoryDTO {
export interface RepositoryServiceAPI extends ServiceApiContract {
canUserAccessRepository(
user: User | null,
- repo: Repository
+ repo: Repository,
): Promise<boolean>;
createRepository(dto: CreateRepositoryDTO): Promise<Repository>;
forkRepository(dto: ForkRepositoryDTO): Promise<Repository>;
getCurrentUserRepositoryForks(
- repository: Repository
+ repository: Repository,
): Promise<RepositoryWithParentAndForkedFromRepos[]>;
getRepository(
orgSlug: string,
- repoSlug: string
+ repoSlug: string,
): Promise<RepositoryWithForkedFromRepo | null>;
getRepositoryBranches(
repository: Repository,
- onlyLocalBranches?: boolean
+ onlyLocalBranches?: boolean,
): Promise<string[]>;
getRepositoryById(
- repoId: string
+ repoId: string,
): Promise<RepositoryWithForkedFromRepo | null>;
getRepositoryCommitLog(
repository: Repository,
path?: string,
ref?: string,
- onlyLast?: boolean
+ onlyLast?: boolean,
): Promise<RepositoryLog[]>;
getRepositoryExploreCollection(): Promise<
(Repository & { parentOrg: Organization })[]
@@ -80,46 +81,50 @@ export interface RepositoryServiceAPI extends ServiceApiContract {
getRepositoryFileContent(
repository: Repository,
path: string,
- ref?: string
+ ref?: string,
): Promise<null | RepositoryFileContent>;
getRepositoryFileContentBase64(
repository: Repository,
path: string,
- ref?: string
+ ref?: string,
): Promise<null | RepositoryFileContent>;
getRepositoryFiles(
repository: Repository,
path?: string,
- ref?: string
+ ref?: string,
): Promise<RepositoryFile[]>;
getRepositoryHead(
repository: Repository,
- ref?: string
+ ref?: string,
): Promise<RepositoryHead>;
getRepositoryHTTPCloneUrl(repository: Repository): Promise<string>;
getRepositorySSHCloneUrl(repository: Repository): Promise<string>;
getRepositoryObject(
repository: Repository,
- objectId: String
+ objectId: String,
): Promise<RepositoryObject | null>;
getRepositoryRefDiff(
repository: Repository,
refA: string,
- refB?: string
+ refB?: string,
): Promise<RepositoryFileDiff[]>;
getRepositoryRemoteRefDiff(
sourceRepo: Repository,
sourceFromBranch: string,
targetRepo: Repository,
- targetDestBranch: string
+ targetDestBranch: string,
): Promise<RepositoryFileDiff[]>;
getRepositoryTags(repository: Repository): Promise<string[]>;
isFileInRepositoryPath(
repository: Repository,
path: string,
filesToMatch: string[],
- ref?: string
+ ref?: string,
): Promise<string[]>;
+ getRepositoryCounters(
+ orgSlug: string,
+ repoSlug: string,
+ ): Promise<RepositoryCountersDTO>;
}
export interface RepositoryServiceDeps {
@@ -38,6 +38,7 @@ export interface CommonViewProps {
themeScheme: AppThemeScheme;
title?: string;
path?: string;
+ layoutCounters?: RepositoryCountersDTO;
}
export type CommonProps = { commonProps: CommonViewProps };
@@ -8,3 +8,4 @@ export { guestOrRedirect } from "./guestOrRedirect";
export { localAppDomainPreHandler } from "./localAppDomainPreHandler";
export { makeRequestHandler } from "./makeRequestHandler";
export { sessionSetupPreHandler } from "./sessionSetupPreHandler";
+export { loadRepositoryCounters } from "./loadRepositoryCounters";
@@ -10,7 +10,7 @@ import { Const } from "../../const";
export const makeRequestHandler = {
getter: () => {
return (request: FastifyRequest, reply: FastifyReply) => {
- return <T extends Record<string, unknown>>(
+ return async <T extends Record<string, unknown>>(
viewName: string,
props?: T & CommonViewProps,
viewCtx?: ViewContext,
@@ -50,6 +50,7 @@ export const makeRequestHandler = {
themeScheme,
title,
path: request.url,
+ layoutCounters: reply.context.layoutCounters,
},
} as T & { commonProps: CommonViewProps };