Skip to content

Commit 04022b6

Browse files
more improvements
1 parent a522e95 commit 04022b6

File tree

17 files changed

+210
-78
lines changed

17 files changed

+210
-78
lines changed

packages/backend/src/azuredevops.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { AzureDevOpsConnectionConfig } from "@sourcebot/schemas/v3/azuredevops.type";
22
import { createLogger } from "@sourcebot/logger";
3-
import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
3+
import { measure, fetchWithRetry } from "./utils.js";
44
import micromatch from "micromatch";
55
import { PrismaClient } from "@sourcebot/db";
66
import { BackendException, BackendError } from "@sourcebot/error";
77
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
88
import * as Sentry from "@sentry/node";
99
import * as azdev from "azure-devops-node-api";
1010
import { GitRepository } from "azure-devops-node-api/interfaces/GitInterfaces.js";
11+
import { getTokenFromConfig } from "@sourcebot/crypto";
1112

1213
const logger = createLogger('azuredevops');
1314
const AZUREDEVOPS_CLOUD_HOSTNAME = "dev.azure.com";
@@ -34,7 +35,7 @@ export const getAzureDevOpsReposFromConfig = async (
3435
const baseUrl = config.url || `https://${AZUREDEVOPS_CLOUD_HOSTNAME}`;
3536

3637
const token = config.token ?
37-
await getTokenFromConfig(config.token, orgId, db, logger) :
38+
await getTokenFromConfig(config.token, orgId, db) :
3839
undefined;
3940

4041
if (!token) {

packages/backend/src/bitbucket.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type"
44
import type { ClientOptions, ClientPathsWithMethod } from "openapi-fetch";
55
import { createLogger } from "@sourcebot/logger";
66
import { PrismaClient } from "@sourcebot/db";
7-
import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
7+
import { measure, fetchWithRetry } from "./utils.js";
88
import * as Sentry from "@sentry/node";
99
import {
1010
SchemaRepository as CloudRepository,
1111
} from "@coderabbitai/bitbucket/cloud/openapi";
1212
import { SchemaRestRepository as ServerRepository } from "@coderabbitai/bitbucket/server/openapi";
1313
import { processPromiseResults } from "./connectionUtils.js";
1414
import { throwIfAnyFailed } from "./connectionUtils.js";
15+
import { getTokenFromConfig } from "@sourcebot/crypto";
1516

1617
const logger = createLogger('bitbucket');
1718
const BITBUCKET_CLOUD_GIT = 'https://bitbucket.org';
@@ -59,7 +60,7 @@ type ServerPaginatedResponse<T> = {
5960

6061
export const getBitbucketReposFromConfig = async (config: BitbucketConnectionConfig, orgId: number, db: PrismaClient) => {
6162
const token = config.token ?
62-
await getTokenFromConfig(config.token, orgId, db, logger) :
63+
await getTokenFromConfig(config.token, orgId, db) :
6364
undefined;
6465

6566
if (config.deploymentType === 'server' && !config.url) {

packages/backend/src/ee/githubAppManager.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { GithubAppConfig, SourcebotConfig } from "@sourcebot/schemas/v3/index.type";
21
import { loadConfig } from "@sourcebot/shared";
32
import { env } from "../env.js";
43
import { createLogger } from "@sourcebot/logger";
5-
import { getTokenFromConfig } from "../utils.js";
4+
import { getTokenFromConfig } from "@sourcebot/crypto";
65
import { PrismaClient } from "@sourcebot/db";
76
import { App } from "@octokit/app";
87
import { GitHubAppConfig } from "@sourcebot/schemas/v3/index.type";
@@ -54,7 +53,7 @@ export class GithubAppManager {
5453
return;
5554
}
5655

57-
const githubApps = config.apps.filter(app => app.type === 'githubApp') as GithubAppConfig[];
56+
const githubApps = config.apps.filter(app => app.type === 'githubApp') as GitHubAppConfig[];
5857
logger.info(`Found ${githubApps.length} GitHub apps in config`);
5958

6059
for (const app of githubApps) {
@@ -63,7 +62,7 @@ export class GithubAppManager {
6362
// @todo: we should move SINGLE_TENANT_ORG_ID to shared package or just remove the need to pass this in
6463
// when resolving tokens
6564
const SINGLE_TENANT_ORG_ID = 1;
66-
const privateKey = await getTokenFromConfig(app.privateKey, SINGLE_TENANT_ORG_ID, this.db!);
65+
const privateKey = await getTokenFromConfig(app.privateKey, SINGLE_TENANT_ORG_ID, this.db);
6766

6867
const octokitApp = new App({
6968
appId: Number(app.id),

packages/backend/src/gitea.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Api, giteaApi, HttpResponse, Repository as GiteaRepository } from 'gitea-js';
22
import { GiteaConnectionConfig } from '@sourcebot/schemas/v3/gitea.type';
3-
import { getTokenFromConfig, measure } from './utils.js';
3+
import { measure } from './utils.js';
44
import fetch from 'cross-fetch';
55
import { createLogger } from '@sourcebot/logger';
66
import micromatch from 'micromatch';
77
import { PrismaClient } from '@sourcebot/db';
88
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
99
import * as Sentry from "@sentry/node";
1010
import { env } from './env.js';
11+
import { getTokenFromConfig } from "@sourcebot/crypto";
1112

1213
const logger = createLogger('gitea');
1314
const GITEA_CLOUD_HOSTNAME = "gitea.com";
@@ -18,7 +19,7 @@ export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, org
1819
GITEA_CLOUD_HOSTNAME;
1920

2021
const token = config.token ?
21-
await getTokenFromConfig(config.token, orgId, db, logger) :
22+
await getTokenFromConfig(config.token, orgId, db) :
2223
hostname === GITEA_CLOUD_HOSTNAME ?
2324
env.FALLBACK_GITEA_CLOUD_TOKEN :
2425
undefined;

packages/backend/src/github.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import micromatch from "micromatch";
88
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
99
import { GithubAppManager } from "./ee/githubAppManager.js";
1010
import { env } from "./env.js";
11-
import { fetchWithRetry, getTokenFromConfig, measure } from "./utils.js";
11+
import { fetchWithRetry, measure } from "./utils.js";
12+
import { getTokenFromConfig } from "@sourcebot/crypto";
1213

1314
const logger = createLogger('github');
1415
const GITHUB_CLOUD_HOSTNAME = "github.com";
@@ -96,7 +97,7 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
9697
GITHUB_CLOUD_HOSTNAME;
9798

9899
const token = config.token ?
99-
await getTokenFromConfig(config.token, orgId, db, logger) :
100+
await getTokenFromConfig(config.token, orgId, db) :
100101
hostname === GITHUB_CLOUD_HOSTNAME ?
101102
env.FALLBACK_GITHUB_CLOUD_TOKEN :
102103
undefined;

packages/backend/src/gitlab.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { Gitlab, ProjectSchema } from "@gitbeaker/rest";
22
import micromatch from "micromatch";
33
import { createLogger } from "@sourcebot/logger";
44
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"
5-
import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
5+
import { measure, fetchWithRetry } from "./utils.js";
66
import { PrismaClient } from "@sourcebot/db";
77
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
88
import * as Sentry from "@sentry/node";
99
import { env } from "./env.js";
10+
import { getTokenFromConfig } from "@sourcebot/crypto";
1011

1112
const logger = createLogger('gitlab');
1213
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";
@@ -17,7 +18,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
1718
GITLAB_CLOUD_HOSTNAME;
1819

1920
const token = config.token ?
20-
await getTokenFromConfig(config.token, orgId, db, logger) :
21+
await getTokenFromConfig(config.token, orgId, db) :
2122
hostname === GITLAB_CLOUD_HOSTNAME ?
2223
env.FALLBACK_GITLAB_CLOUD_TOKEN :
2324
undefined;

packages/backend/src/utils.ts

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { Logger } from "winston";
22
import { RepoAuthCredentials, RepoWithConnections } from "./types.js";
33
import path from 'path';
44
import { PrismaClient, Repo } from "@sourcebot/db";
5-
import { getTokenFromConfig as getTokenFromConfigBase } from "@sourcebot/crypto";
6-
import { BackendException, BackendError } from "@sourcebot/error";
5+
import { getTokenFromConfig } from "@sourcebot/crypto";
76
import * as Sentry from "@sentry/node";
87
import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig, BitbucketConnectionConfig, AzureDevOpsConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
98
import { GithubAppManager } from "./ee/githubAppManager.js";
@@ -24,22 +23,6 @@ export const marshalBool = (value?: boolean) => {
2423
return !!value ? '1' : '0';
2524
}
2625

27-
export const getTokenFromConfig = async (token: any, orgId: number, db: PrismaClient, logger?: Logger) => {
28-
try {
29-
return await getTokenFromConfigBase(token, orgId, db);
30-
} catch (error: unknown) {
31-
if (error instanceof Error) {
32-
const e = new BackendException(BackendError.CONNECTION_SYNC_SECRET_DNE, {
33-
message: error.message,
34-
});
35-
Sentry.captureException(e);
36-
logger?.error(error.message);
37-
throw e;
38-
}
39-
throw error;
40-
}
41-
};
42-
4326
export const resolvePathRelativeToConfig = (localPath: string, configPath: string) => {
4427
let absolutePath = localPath;
4528
if (!path.isAbsolute(absolutePath)) {
@@ -156,7 +139,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
156139
if (connection.connectionType === 'github') {
157140
const config = connection.config as unknown as GithubConnectionConfig;
158141
if (config.token) {
159-
const token = await getTokenFromConfig(config.token, connection.orgId, db, logger);
142+
const token = await getTokenFromConfig(config.token, connection.orgId, db);
160143
return {
161144
hostUrl: config.url,
162145
token,
@@ -171,7 +154,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
171154
} else if (connection.connectionType === 'gitlab') {
172155
const config = connection.config as unknown as GitlabConnectionConfig;
173156
if (config.token) {
174-
const token = await getTokenFromConfig(config.token, connection.orgId, db, logger);
157+
const token = await getTokenFromConfig(config.token, connection.orgId, db);
175158
return {
176159
hostUrl: config.url,
177160
token,
@@ -187,7 +170,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
187170
} else if (connection.connectionType === 'gitea') {
188171
const config = connection.config as unknown as GiteaConnectionConfig;
189172
if (config.token) {
190-
const token = await getTokenFromConfig(config.token, connection.orgId, db, logger);
173+
const token = await getTokenFromConfig(config.token, connection.orgId, db);
191174
return {
192175
hostUrl: config.url,
193176
token,
@@ -202,7 +185,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
202185
} else if (connection.connectionType === 'bitbucket') {
203186
const config = connection.config as unknown as BitbucketConnectionConfig;
204187
if (config.token) {
205-
const token = await getTokenFromConfig(config.token, connection.orgId, db, logger);
188+
const token = await getTokenFromConfig(config.token, connection.orgId, db);
206189
const username = config.user ?? 'x-token-auth';
207190
return {
208191
hostUrl: config.url,
@@ -219,7 +202,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
219202
} else if (connection.connectionType === 'azuredevops') {
220203
const config = connection.config as unknown as AzureDevOpsConnectionConfig;
221204
if (config.token) {
222-
const token = await getTokenFromConfig(config.token, connection.orgId, db, logger);
205+
const token = await getTokenFromConfig(config.token, connection.orgId, db);
223206

224207
// For ADO server, multiple auth schemes may be supported. If the ADO deployment supports NTLM, the git clone will default
225208
// to this over basic auth. As a result, we cannot embed the token in the clone URL and must force basic auth by passing in the token

packages/web/src/actions.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { prisma } from "@/prisma";
1010
import { render } from "@react-email/components";
1111
import * as Sentry from '@sentry/nextjs';
1212
import { encrypt, generateApiKey, getTokenFromConfig, hashSecret } from "@sourcebot/crypto";
13-
import { ApiKey, Org, OrgRole, Prisma, RepoIndexingJobStatus, RepoIndexingJobType, StripeSubscriptionStatus } from "@sourcebot/db";
13+
import { ApiKey, ConnectionSyncJobStatus, Org, OrgRole, Prisma, RepoIndexingJobStatus, RepoIndexingJobType, StripeSubscriptionStatus } from "@sourcebot/db";
1414
import { createLogger } from "@sourcebot/logger";
1515
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
1616
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
@@ -30,7 +30,7 @@ import JoinRequestSubmittedEmail from "./emails/joinRequestSubmittedEmail";
3030
import { AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME, MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SINGLE_TENANT_ORG_DOMAIN, SOURCEBOT_GUEST_USER_ID, SOURCEBOT_SUPPORT_EMAIL } from "./lib/constants";
3131
import { orgDomainSchema, orgNameSchema, repositoryQuerySchema } from "./lib/schemas";
3232
import { ApiKeyPayload, TenancyMode } from "./lib/types";
33-
import { withOptionalAuthV2 } from "./withAuthV2";
33+
import { withAuthV2, withOptionalAuthV2 } from "./withAuthV2";
3434

3535
const logger = createLogger('web-actions');
3636
const auditService = getAuditService();
@@ -584,6 +584,7 @@ export const getReposStats = async () => sew(() =>
584584
prisma.repo.count({
585585
where: {
586586
orgId: org.id,
587+
indexedAt: null,
587588
jobs: {
588589
some: {
589590
type: RepoIndexingJobType.INDEX,
@@ -595,7 +596,6 @@ export const getReposStats = async () => sew(() =>
595596
}
596597
},
597598
},
598-
indexedAt: null,
599599
}
600600
}),
601601
prisma.repo.count({
@@ -616,6 +616,42 @@ export const getReposStats = async () => sew(() =>
616616
})
617617
)
618618

619+
export const getConnectionStats = async () => sew(() =>
620+
withAuthV2(async ({ org, prisma }) => {
621+
const [
622+
numberOfConnections,
623+
numberOfConnectionsWithFirstTimeSyncJobsInProgress,
624+
] = await Promise.all([
625+
prisma.connection.count({
626+
where: {
627+
orgId: org.id,
628+
}
629+
}),
630+
prisma.connection.count({
631+
where: {
632+
orgId: org.id,
633+
syncedAt: null,
634+
syncJobs: {
635+
some: {
636+
status: {
637+
in: [
638+
ConnectionSyncJobStatus.PENDING,
639+
ConnectionSyncJobStatus.IN_PROGRESS,
640+
]
641+
}
642+
}
643+
}
644+
}
645+
})
646+
]);
647+
648+
return {
649+
numberOfConnections,
650+
numberOfConnectionsWithFirstTimeSyncJobsInProgress,
651+
};
652+
})
653+
);
654+
619655
export const getRepoInfoByName = async (repoName: string) => sew(() =>
620656
withOptionalAuthV2(async ({ org, prisma }) => {
621657
// @note: repo names are represented by their remote url

packages/web/src/app/[domain]/components/navigationMenu/index.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getRepos, getReposStats } from "@/actions";
1+
import { getConnectionStats, getRepos, getReposStats } from "@/actions";
22
import { SourcebotLogo } from "@/app/components/sourcebotLogo";
33
import { auth } from "@/auth";
44
import { Button } from "@/components/ui/button";
@@ -39,6 +39,11 @@ export const NavigationMenu = async ({
3939
throw new ServiceErrorException(repoStats);
4040
}
4141

42+
const connectionStats = isAuthenticated ? await getConnectionStats() : null;
43+
if (isServiceError(connectionStats)) {
44+
throw new ServiceErrorException(connectionStats);
45+
}
46+
4247
const sampleRepos = await getRepos({
4348
where: {
4449
jobs: {
@@ -93,7 +98,12 @@ export const NavigationMenu = async ({
9398
<NavigationItems
9499
domain={domain}
95100
numberOfRepos={numberOfRepos}
96-
numberOfReposWithFirstTimeIndexingJobsInProgress={numberOfReposWithFirstTimeIndexingJobsInProgress}
101+
isReposButtonNotificationDotVisible={numberOfReposWithFirstTimeIndexingJobsInProgress > 0}
102+
isSettingsButtonNotificationDotVisible={
103+
connectionStats ?
104+
connectionStats.numberOfConnectionsWithFirstTimeSyncJobsInProgress > 0 :
105+
false
106+
}
97107
isAuthenticated={isAuthenticated}
98108
/>
99109
</NavigationMenuBase>

packages/web/src/app/[domain]/components/navigationMenu/navigationItems.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@
33
import { NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
44
import { Badge } from "@/components/ui/badge";
55
import { cn, getShortenedNumberDisplayString } from "@/lib/utils";
6-
import { SearchIcon, MessageCircleIcon, BookMarkedIcon, SettingsIcon, CircleIcon } from "lucide-react";
6+
import { SearchIcon, MessageCircleIcon, BookMarkedIcon, SettingsIcon } from "lucide-react";
77
import { usePathname } from "next/navigation";
8+
import { NotificationDot } from "../notificationDot";
89

910
interface NavigationItemsProps {
1011
domain: string;
1112
numberOfRepos: number;
12-
numberOfReposWithFirstTimeIndexingJobsInProgress: number;
13+
isReposButtonNotificationDotVisible: boolean;
14+
isSettingsButtonNotificationDotVisible: boolean;
1315
isAuthenticated: boolean;
1416
}
1517

1618
export const NavigationItems = ({
1719
domain,
1820
numberOfRepos,
19-
numberOfReposWithFirstTimeIndexingJobsInProgress,
21+
isReposButtonNotificationDotVisible,
22+
isSettingsButtonNotificationDotVisible,
2023
isAuthenticated,
2124
}: NavigationItemsProps) => {
2225
const pathname = usePathname();
@@ -59,9 +62,7 @@ export const NavigationItems = ({
5962
<span className="mr-2">Repositories</span>
6063
<Badge variant="secondary" className="px-1.5 relative">
6164
{getShortenedNumberDisplayString(numberOfRepos)}
62-
{numberOfReposWithFirstTimeIndexingJobsInProgress > 0 && (
63-
<CircleIcon className="absolute -right-0.5 -top-0.5 h-2 w-2 text-green-600" fill="currentColor" />
64-
)}
65+
{isReposButtonNotificationDotVisible && <NotificationDot className="absolute -right-0.5 -top-0.5" />}
6566
</Badge>
6667
</NavigationMenuLink>
6768
{isActive(`/${domain}/repos`) && <ActiveIndicator />}
@@ -74,6 +75,7 @@ export const NavigationItems = ({
7475
>
7576
<SettingsIcon className="w-4 h-4 mr-1" />
7677
Settings
78+
{isSettingsButtonNotificationDotVisible && <NotificationDot className="absolute -right-0.5 -top-0.5" />}
7779
</NavigationMenuLink>
7880
{isActive(`/${domain}/settings`) && <ActiveIndicator />}
7981
</NavigationMenuItem>
@@ -86,4 +88,4 @@ const ActiveIndicator = () => {
8688
return (
8789
<div className="absolute -bottom-2 left-0 right-0 h-0.5 bg-foreground" />
8890
);
89-
};
91+
};

0 commit comments

Comments
 (0)