@oneuptime/common 9.3.4 → 9.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Server/API/AIAgentDataAPI.ts +11 -1
- package/Server/API/GitHubAPI.ts +102 -0
- package/Server/EnvironmentConfig.ts +7 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +53 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +43 -0
- package/Server/Utils/Monitor/MonitorResource.ts +26 -4
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -30
- package/Types/Icon/IconProp.ts +2 -0
- package/UI/Components/Icon/Icon.tsx +21 -0
- package/UI/Components/Navbar/NavBar.tsx +5 -2
- package/UI/Components/Navbar/NavBarMenu.tsx +48 -14
- package/UI/Components/Navbar/NavBarMenuItem.tsx +86 -6
- package/build/dist/Server/API/AIAgentDataAPI.js +11 -2
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +76 -3
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +2 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +34 -3
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +32 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +19 -3
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +3 -17
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Icon/IconProp.js +2 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +11 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenu.js +26 -10
- package/build/dist/UI/Components/Navbar/NavBarMenu.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuItem.js +72 -6
- package/build/dist/UI/Components/Navbar/NavBarMenuItem.js.map +1 -1
- package/package.json +1 -1
|
@@ -458,10 +458,20 @@ export default class AIAgentDataAPI {
|
|
|
458
458
|
);
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
|
|
461
|
+
/*
|
|
462
|
+
* Generate GitHub installation access token with write permissions
|
|
463
|
+
* Required for AI Agent to push branches and create pull requests
|
|
464
|
+
*/
|
|
462
465
|
const tokenData: GitHubInstallationToken =
|
|
463
466
|
await GitHubUtil.getInstallationAccessToken(
|
|
464
467
|
codeRepository.gitHubAppInstallationId,
|
|
468
|
+
{
|
|
469
|
+
permissions: {
|
|
470
|
+
contents: "write", // Required for pushing branches
|
|
471
|
+
pull_requests: "write", // Required for creating PRs
|
|
472
|
+
metadata: "read", // Required for reading repository metadata
|
|
473
|
+
},
|
|
474
|
+
},
|
|
465
475
|
);
|
|
466
476
|
|
|
467
477
|
const repositoryUrl: string = `https://github.com/${codeRepository.organizationName}/${codeRepository.repositoryName}.git`;
|
package/Server/API/GitHubAPI.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { DashboardClientUrl, GitHubAppName } from "../EnvironmentConfig";
|
|
|
11
11
|
import ObjectID from "../../Types/ObjectID";
|
|
12
12
|
import GitHubUtil, {
|
|
13
13
|
GitHubRepository,
|
|
14
|
+
GitHubInstallationNotFoundError,
|
|
14
15
|
} from "../Utils/CodeRepository/GitHub/GitHub";
|
|
15
16
|
import CodeRepositoryService from "../Services/CodeRepositoryService";
|
|
16
17
|
import ProjectService from "../Services/ProjectService";
|
|
@@ -181,9 +182,19 @@ export default class GitHubAPI {
|
|
|
181
182
|
UserMiddleware.getUserMiddleware,
|
|
182
183
|
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
183
184
|
try {
|
|
185
|
+
const projectId: string | undefined =
|
|
186
|
+
req.params["projectId"]?.toString();
|
|
184
187
|
const installationId: string | undefined =
|
|
185
188
|
req.params["installationId"]?.toString();
|
|
186
189
|
|
|
190
|
+
if (!projectId) {
|
|
191
|
+
return Response.sendErrorResponse(
|
|
192
|
+
req,
|
|
193
|
+
res,
|
|
194
|
+
new BadDataException("Project ID is required"),
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
187
198
|
if (!installationId) {
|
|
188
199
|
return Response.sendErrorResponse(
|
|
189
200
|
req,
|
|
@@ -201,6 +212,40 @@ export default class GitHubAPI {
|
|
|
201
212
|
} catch (error) {
|
|
202
213
|
logger.error("GitHub List Repositories Error:");
|
|
203
214
|
logger.error(error);
|
|
215
|
+
|
|
216
|
+
// Handle stale installation ID - clear it from the project and return specific error
|
|
217
|
+
if (error instanceof GitHubInstallationNotFoundError) {
|
|
218
|
+
const projectId: string | undefined =
|
|
219
|
+
req.params["projectId"]?.toString();
|
|
220
|
+
|
|
221
|
+
if (projectId) {
|
|
222
|
+
try {
|
|
223
|
+
// Clear the stale installation ID from the project
|
|
224
|
+
await ProjectService.updateOneById({
|
|
225
|
+
id: new ObjectID(projectId),
|
|
226
|
+
data: {
|
|
227
|
+
gitHubAppInstallationId: null as unknown as string,
|
|
228
|
+
},
|
|
229
|
+
props: {
|
|
230
|
+
isRoot: true,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
logger.info(
|
|
235
|
+
`Cleared stale GitHub App installation ID from project ${projectId}`,
|
|
236
|
+
);
|
|
237
|
+
} catch (clearError) {
|
|
238
|
+
logger.error(
|
|
239
|
+
"Failed to clear stale installation ID from project:",
|
|
240
|
+
);
|
|
241
|
+
logger.error(clearError);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Return the specific error so the frontend knows to prompt reinstallation
|
|
246
|
+
return Response.sendErrorResponse(req, res, error);
|
|
247
|
+
}
|
|
248
|
+
|
|
204
249
|
return Response.sendErrorResponse(
|
|
205
250
|
req,
|
|
206
251
|
res,
|
|
@@ -349,6 +394,63 @@ export default class GitHubAPI {
|
|
|
349
394
|
|
|
350
395
|
logger.debug(`Received GitHub webhook event: ${event}`);
|
|
351
396
|
|
|
397
|
+
// Handle installation events - specifically when the app is uninstalled
|
|
398
|
+
if (event === "installation") {
|
|
399
|
+
const action: string | undefined = (req.body as JSONObject)?.[
|
|
400
|
+
"action"
|
|
401
|
+
]?.toString();
|
|
402
|
+
const installationId: string | undefined = (
|
|
403
|
+
(req.body as JSONObject)?.["installation"] as JSONObject
|
|
404
|
+
)?.["id"]?.toString();
|
|
405
|
+
|
|
406
|
+
if (action === "deleted" && installationId) {
|
|
407
|
+
logger.info(
|
|
408
|
+
`GitHub App installation ${installationId} was deleted. Clearing from database...`,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
// Clear the installation ID from any projects that have it
|
|
413
|
+
await ProjectService.updateBy({
|
|
414
|
+
query: {
|
|
415
|
+
gitHubAppInstallationId: installationId,
|
|
416
|
+
},
|
|
417
|
+
data: {
|
|
418
|
+
gitHubAppInstallationId: null as unknown as string,
|
|
419
|
+
},
|
|
420
|
+
limit: 1000,
|
|
421
|
+
skip: 0,
|
|
422
|
+
props: {
|
|
423
|
+
isRoot: true,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Also clear from any code repositories that have this installation ID
|
|
428
|
+
await CodeRepositoryService.updateBy({
|
|
429
|
+
query: {
|
|
430
|
+
gitHubAppInstallationId: installationId,
|
|
431
|
+
},
|
|
432
|
+
data: {
|
|
433
|
+
gitHubAppInstallationId: null as unknown as string,
|
|
434
|
+
},
|
|
435
|
+
limit: 10000,
|
|
436
|
+
skip: 0,
|
|
437
|
+
props: {
|
|
438
|
+
isRoot: true,
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
logger.info(
|
|
443
|
+
`Successfully cleared GitHub App installation ${installationId} from database`,
|
|
444
|
+
);
|
|
445
|
+
} catch (clearError) {
|
|
446
|
+
logger.error(
|
|
447
|
+
`Failed to clear GitHub App installation ${installationId} from database:`,
|
|
448
|
+
);
|
|
449
|
+
logger.error(clearError);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
352
454
|
/*
|
|
353
455
|
* Handle different webhook events here
|
|
354
456
|
* For now, just acknowledge receipt
|
|
@@ -397,6 +397,13 @@ export const StatusPageApiClientUrl: URL = new URL(
|
|
|
397
397
|
new Route(StatusPageApiRoute.toString()),
|
|
398
398
|
);
|
|
399
399
|
|
|
400
|
+
// Internal URL for server-to-server communication (uses internal Docker hostname)
|
|
401
|
+
export const StatusPageApiInternalUrl: URL = new URL(
|
|
402
|
+
Protocol.HTTP,
|
|
403
|
+
AppApiHostname.toString(),
|
|
404
|
+
new Route(StatusPageApiRoute.toString()),
|
|
405
|
+
);
|
|
406
|
+
|
|
400
407
|
export const DashboardClientUrl: URL = new URL(
|
|
401
408
|
HttpProtocol,
|
|
402
409
|
Host,
|
|
@@ -18,6 +18,17 @@ import {
|
|
|
18
18
|
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
19
19
|
import * as crypto from "crypto";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when a GitHub App installation is no longer valid (e.g., uninstalled from GitHub)
|
|
23
|
+
*/
|
|
24
|
+
export class GitHubInstallationNotFoundError extends BadDataException {
|
|
25
|
+
public constructor() {
|
|
26
|
+
super(
|
|
27
|
+
"GitHub App installation not found. The app may have been uninstalled from GitHub. Please reconnect with GitHub to reinstall the app.",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export interface GitHubRepository {
|
|
22
33
|
id: number;
|
|
23
34
|
name: string;
|
|
@@ -335,11 +346,20 @@ export default class GitHubUtil extends HostedCodeRepository {
|
|
|
335
346
|
/**
|
|
336
347
|
* Gets an installation access token for a GitHub App installation
|
|
337
348
|
* @param installationId - The GitHub App installation ID
|
|
349
|
+
* @param options - Optional configuration for the token
|
|
350
|
+
* @param options.permissions - Specific permissions to request for the token
|
|
338
351
|
* @returns Installation token and expiration date
|
|
339
352
|
*/
|
|
340
353
|
@CaptureSpan()
|
|
341
354
|
public static async getInstallationAccessToken(
|
|
342
355
|
installationId: string,
|
|
356
|
+
options?: {
|
|
357
|
+
permissions?: {
|
|
358
|
+
contents?: "read" | "write";
|
|
359
|
+
pull_requests?: "read" | "write";
|
|
360
|
+
metadata?: "read";
|
|
361
|
+
};
|
|
362
|
+
},
|
|
343
363
|
): Promise<GitHubInstallationToken> {
|
|
344
364
|
const jwt: string = GitHubUtil.generateAppJWT();
|
|
345
365
|
|
|
@@ -347,10 +367,17 @@ export default class GitHubUtil extends HostedCodeRepository {
|
|
|
347
367
|
`https://api.github.com/app/installations/${installationId}/access_tokens`,
|
|
348
368
|
);
|
|
349
369
|
|
|
370
|
+
// Build request data with optional permissions
|
|
371
|
+
const requestData: JSONObject = {};
|
|
372
|
+
|
|
373
|
+
if (options?.permissions) {
|
|
374
|
+
requestData["permissions"] = options.permissions;
|
|
375
|
+
}
|
|
376
|
+
|
|
350
377
|
const result: HTTPErrorResponse | HTTPResponse<JSONObject> = await API.post(
|
|
351
378
|
{
|
|
352
379
|
url: url,
|
|
353
|
-
data:
|
|
380
|
+
data: requestData,
|
|
354
381
|
headers: {
|
|
355
382
|
Authorization: `Bearer ${jwt}`,
|
|
356
383
|
Accept: "application/vnd.github+json",
|
|
@@ -360,6 +387,31 @@ export default class GitHubUtil extends HostedCodeRepository {
|
|
|
360
387
|
);
|
|
361
388
|
|
|
362
389
|
if (result instanceof HTTPErrorResponse) {
|
|
390
|
+
// Check if this is a permission error and provide helpful message
|
|
391
|
+
const errorMessage: string =
|
|
392
|
+
(result.data as JSONObject)?.["message"]?.toString() || "";
|
|
393
|
+
|
|
394
|
+
// Check if the installation is not found (404) - this means the app was uninstalled from GitHub
|
|
395
|
+
if (result.statusCode === 404) {
|
|
396
|
+
logger.error(
|
|
397
|
+
`GitHub App installation not found (ID: ${installationId}). ` +
|
|
398
|
+
`The app may have been uninstalled from GitHub. User needs to reinstall the app.`,
|
|
399
|
+
);
|
|
400
|
+
throw new GitHubInstallationNotFoundError();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (
|
|
404
|
+
errorMessage.includes("permissions") ||
|
|
405
|
+
result.statusCode === 403 ||
|
|
406
|
+
result.statusCode === 422
|
|
407
|
+
) {
|
|
408
|
+
logger.error(
|
|
409
|
+
`GitHub App permission error: ${errorMessage}. ` +
|
|
410
|
+
`Please ensure the GitHub App is configured with the required permissions ` +
|
|
411
|
+
`(contents: write, pull_requests: write, metadata: read) in the GitHub App settings.`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
363
415
|
throw result;
|
|
364
416
|
}
|
|
365
417
|
|
|
@@ -34,6 +34,10 @@ import OneUptimeDate from "../../../Types/Date";
|
|
|
34
34
|
import { JSONObject } from "../../../Types/JSON";
|
|
35
35
|
import Typeof from "../../../Types/Typeof";
|
|
36
36
|
import ReturnResult from "../../../Types/IsolatedVM/ReturnResult";
|
|
37
|
+
import URL from "../../../Types/API/URL";
|
|
38
|
+
import IP from "../../../Types/IP/IP";
|
|
39
|
+
import Hostname from "../../../Types/API/Hostname";
|
|
40
|
+
import Port from "../../../Types/Port";
|
|
37
41
|
|
|
38
42
|
export default class MonitorCriteriaEvaluator {
|
|
39
43
|
public static async processMonitorStep(input: {
|
|
@@ -650,6 +654,45 @@ ${contextBlock}
|
|
|
650
654
|
}
|
|
651
655
|
|
|
652
656
|
try {
|
|
657
|
+
// Handle primitive types directly
|
|
658
|
+
if (typeof value === "string") {
|
|
659
|
+
return value.trim();
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
663
|
+
return String(value);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Handle class instances with custom toString method (like URL, IP, Hostname)
|
|
667
|
+
if (
|
|
668
|
+
value instanceof URL ||
|
|
669
|
+
value instanceof IP ||
|
|
670
|
+
value instanceof Hostname ||
|
|
671
|
+
value instanceof Port
|
|
672
|
+
) {
|
|
673
|
+
return value.toString().trim();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/*
|
|
677
|
+
* Handle JSON representations of URL, IP, Hostname, Port (e.g., { _type: "URL", value: "https://..." })
|
|
678
|
+
* This can happen when the value wasn't properly deserialized from JSON
|
|
679
|
+
*/
|
|
680
|
+
if (typeof value === "object" && value !== null && "_type" in value) {
|
|
681
|
+
const typedValue: { _type: string; value?: unknown } = value as {
|
|
682
|
+
_type: string;
|
|
683
|
+
value?: unknown;
|
|
684
|
+
};
|
|
685
|
+
if (
|
|
686
|
+
(typedValue._type === "URL" ||
|
|
687
|
+
typedValue._type === "IP" ||
|
|
688
|
+
typedValue._type === "Hostname" ||
|
|
689
|
+
typedValue._type === "Port") &&
|
|
690
|
+
typeof typedValue.value === "string"
|
|
691
|
+
) {
|
|
692
|
+
return typedValue.value.trim();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
653
696
|
return String(value).trim();
|
|
654
697
|
} catch (err) {
|
|
655
698
|
logger.error(err);
|
|
@@ -50,6 +50,7 @@ interface ProbeAgreementResult {
|
|
|
50
50
|
totalActiveProbes: number;
|
|
51
51
|
agreedCriteriaId: string | null;
|
|
52
52
|
agreedRootCause: string | null;
|
|
53
|
+
agreedProbeNames: Array<string>;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
export default class MonitorResourceUtil {
|
|
@@ -566,6 +567,16 @@ export default class MonitorResourceUtil {
|
|
|
566
567
|
? probeAgreementResult.agreedCriteriaId
|
|
567
568
|
: undefined;
|
|
568
569
|
response.rootCause = probeAgreementResult.agreedRootCause;
|
|
570
|
+
|
|
571
|
+
// Add probe names in agreement to the root cause
|
|
572
|
+
if (
|
|
573
|
+
response.rootCause &&
|
|
574
|
+
probeAgreementResult.agreedProbeNames.length > 0
|
|
575
|
+
) {
|
|
576
|
+
response.rootCause += `
|
|
577
|
+
**Probes in Agreement**: ${probeAgreementResult.agreedProbeNames.join(", ")}
|
|
578
|
+
`;
|
|
579
|
+
}
|
|
569
580
|
}
|
|
570
581
|
|
|
571
582
|
if (response.criteriaMetId && response.rootCause) {
|
|
@@ -829,6 +840,7 @@ export default class MonitorResourceUtil {
|
|
|
829
840
|
lastMonitoringLog: true,
|
|
830
841
|
probe: {
|
|
831
842
|
connectionStatus: true,
|
|
843
|
+
name: true,
|
|
832
844
|
},
|
|
833
845
|
},
|
|
834
846
|
limit: LIMIT_PER_PROJECT,
|
|
@@ -861,6 +873,7 @@ export default class MonitorResourceUtil {
|
|
|
861
873
|
totalActiveProbes: 0,
|
|
862
874
|
agreedCriteriaId: currentCriteriaMetId,
|
|
863
875
|
agreedRootCause: currentRootCause,
|
|
876
|
+
agreedProbeNames: [],
|
|
864
877
|
};
|
|
865
878
|
}
|
|
866
879
|
|
|
@@ -876,11 +889,11 @@ export default class MonitorResourceUtil {
|
|
|
876
889
|
/*
|
|
877
890
|
* Count how many probes agree on each criteria result
|
|
878
891
|
* Key: criteriaId or "none" for no criteria met
|
|
879
|
-
* Value: { count, rootCause }
|
|
892
|
+
* Value: { count, rootCause, probeNames }
|
|
880
893
|
*/
|
|
881
894
|
const criteriaAgreements: Map<
|
|
882
895
|
string,
|
|
883
|
-
{ count: number; rootCause: string | null }
|
|
896
|
+
{ count: number; rootCause: string | null; probeNames: Array<string> }
|
|
884
897
|
> = new Map();
|
|
885
898
|
|
|
886
899
|
const stepId: string = monitorStep.id.toString();
|
|
@@ -921,15 +934,21 @@ export default class MonitorResourceUtil {
|
|
|
921
934
|
|
|
922
935
|
// Record the result
|
|
923
936
|
const criteriaKey: string = evaluatedResponse.criteriaMetId || "none";
|
|
924
|
-
const existing:
|
|
925
|
-
|
|
937
|
+
const existing:
|
|
938
|
+
| { count: number; rootCause: string | null; probeNames: Array<string> }
|
|
939
|
+
| undefined = criteriaAgreements.get(criteriaKey);
|
|
940
|
+
|
|
941
|
+
// Get probe name for this monitor probe
|
|
942
|
+
const probeName: string = monitorProbe.probe?.name || "Unknown Probe";
|
|
926
943
|
|
|
927
944
|
if (existing) {
|
|
928
945
|
existing.count += 1;
|
|
946
|
+
existing.probeNames.push(probeName);
|
|
929
947
|
} else {
|
|
930
948
|
criteriaAgreements.set(criteriaKey, {
|
|
931
949
|
count: 1,
|
|
932
950
|
rootCause: evaluatedResponse.rootCause,
|
|
951
|
+
probeNames: [probeName],
|
|
933
952
|
});
|
|
934
953
|
}
|
|
935
954
|
}
|
|
@@ -938,12 +957,14 @@ export default class MonitorResourceUtil {
|
|
|
938
957
|
let maxCount: number = 0;
|
|
939
958
|
let winningCriteriaId: string | null = null;
|
|
940
959
|
let winningRootCause: string | null = null;
|
|
960
|
+
let winningProbeNames: Array<string> = [];
|
|
941
961
|
|
|
942
962
|
for (const [criteriaId, data] of criteriaAgreements) {
|
|
943
963
|
if (data.count > maxCount) {
|
|
944
964
|
maxCount = data.count;
|
|
945
965
|
winningCriteriaId = criteriaId === "none" ? null : criteriaId;
|
|
946
966
|
winningRootCause = data.rootCause;
|
|
967
|
+
winningProbeNames = data.probeNames;
|
|
947
968
|
}
|
|
948
969
|
}
|
|
949
970
|
|
|
@@ -961,6 +982,7 @@ export default class MonitorResourceUtil {
|
|
|
961
982
|
totalActiveProbes: activeProbes.length,
|
|
962
983
|
agreedCriteriaId: hasAgreement ? winningCriteriaId : null,
|
|
963
984
|
agreedRootCause: hasAgreement ? winningRootCause : null,
|
|
985
|
+
agreedProbeNames: hasAgreement ? winningProbeNames : [],
|
|
964
986
|
};
|
|
965
987
|
}
|
|
966
988
|
}
|
|
@@ -32,7 +32,6 @@ import ObjectID from "../../../../Types/ObjectID";
|
|
|
32
32
|
import WorkspaceProjectAuthTokenService from "../../../Services/WorkspaceProjectAuthTokenService";
|
|
33
33
|
import WorkspaceProjectAuthToken, {
|
|
34
34
|
MicrosoftTeamsMiscData,
|
|
35
|
-
MicrosoftTeamsTeam,
|
|
36
35
|
} from "../../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
|
|
37
36
|
import Incident from "../../../../Models/DatabaseModels/Incident";
|
|
38
37
|
import IncidentState from "../../../../Models/DatabaseModels/IncidentState";
|
|
@@ -2940,12 +2939,10 @@ All monitoring checks are passing normally.`;
|
|
|
2940
2939
|
// Fetch joined teams using app-scoped token
|
|
2941
2940
|
if (data.userId) {
|
|
2942
2941
|
logger.debug("Using app-scoped token to fetch joined teams for user");
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
});
|
|
2948
|
-
allTeams = Object.values(userTeams) as any;
|
|
2942
|
+
allTeams = await this.getUserJoinedTeams({
|
|
2943
|
+
userId: data.userId,
|
|
2944
|
+
projectId: data.projectId,
|
|
2945
|
+
});
|
|
2949
2946
|
}
|
|
2950
2947
|
} catch (err) {
|
|
2951
2948
|
logger.warn(
|
|
@@ -3069,7 +3066,7 @@ All monitoring checks are passing normally.`;
|
|
|
3069
3066
|
public static async getUserJoinedTeams(data: {
|
|
3070
3067
|
userId: ObjectID;
|
|
3071
3068
|
projectId: ObjectID;
|
|
3072
|
-
}): Promise<
|
|
3069
|
+
}): Promise<Array<JSONObject>> {
|
|
3073
3070
|
logger.debug("=== getUserJoinedTeams called ===");
|
|
3074
3071
|
logger.debug(`User ID: ${data.userId.toString()}`);
|
|
3075
3072
|
logger.debug(`Project ID: ${data.projectId.toString()}`);
|
|
@@ -3123,29 +3120,9 @@ All monitoring checks are passing normally.`;
|
|
|
3123
3120
|
const teams: Array<JSONObject> =
|
|
3124
3121
|
(teamsData["value"] as Array<JSONObject>) || [];
|
|
3125
3122
|
|
|
3126
|
-
|
|
3127
|
-
logger.debug("No joined teams found for user");
|
|
3128
|
-
return {};
|
|
3129
|
-
}
|
|
3123
|
+
logger.debug(`Fetched ${teams.length} joined teams`);
|
|
3130
3124
|
|
|
3131
|
-
|
|
3132
|
-
const availableTeams: Record<string, MicrosoftTeamsTeam> = teams.reduce(
|
|
3133
|
-
(acc: Record<string, MicrosoftTeamsTeam>, t: JSONObject) => {
|
|
3134
|
-
const team: MicrosoftTeamsTeam = {
|
|
3135
|
-
id: t["id"] as string,
|
|
3136
|
-
name: (t["displayName"] as string) || "Unnamed Team",
|
|
3137
|
-
};
|
|
3138
|
-
acc[team.name] = team;
|
|
3139
|
-
return acc;
|
|
3140
|
-
},
|
|
3141
|
-
{} as Record<string, MicrosoftTeamsTeam>,
|
|
3142
|
-
);
|
|
3143
|
-
|
|
3144
|
-
logger.debug(
|
|
3145
|
-
`Fetched ${Object.keys(availableTeams).length} joined teams`,
|
|
3146
|
-
);
|
|
3147
|
-
|
|
3148
|
-
return availableTeams;
|
|
3125
|
+
return teams;
|
|
3149
3126
|
} catch (error) {
|
|
3150
3127
|
logger.error("Error getting user joined teams:");
|
|
3151
3128
|
logger.error(error);
|
package/Types/Icon/IconProp.ts
CHANGED
|
@@ -692,6 +692,14 @@ const Icon: FunctionComponent<ComponentProps> = ({
|
|
|
692
692
|
clipRule="evenodd"
|
|
693
693
|
/>,
|
|
694
694
|
);
|
|
695
|
+
} else if (icon === IconProp.GitHub) {
|
|
696
|
+
return getSvgWrapper(
|
|
697
|
+
<path
|
|
698
|
+
fill="currentColor"
|
|
699
|
+
stroke="none"
|
|
700
|
+
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
|
|
701
|
+
/>,
|
|
702
|
+
);
|
|
695
703
|
} else if (icon === IconProp.ChevronRight) {
|
|
696
704
|
return getSvgWrapper(
|
|
697
705
|
<path
|
|
@@ -1298,6 +1306,19 @@ const Icon: FunctionComponent<ComponentProps> = ({
|
|
|
1298
1306
|
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
|
|
1299
1307
|
/>,
|
|
1300
1308
|
);
|
|
1309
|
+
} else if (icon === IconProp.FlowDiagram) {
|
|
1310
|
+
// Flow diagram icon matching home page workflows - two boxes at top, one at bottom, connected
|
|
1311
|
+
return getSvgWrapper(
|
|
1312
|
+
<>
|
|
1313
|
+
<rect x="3" y="3" width="6" height="4" rx="1" strokeWidth="1.5" />
|
|
1314
|
+
<rect x="15" y="3" width="6" height="4" rx="1" strokeWidth="1.5" />
|
|
1315
|
+
<rect x="9" y="17" width="6" height="4" rx="1" strokeWidth="1.5" />
|
|
1316
|
+
<path
|
|
1317
|
+
strokeLinecap="round"
|
|
1318
|
+
d="M6 7v3a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7M12 12v5"
|
|
1319
|
+
/>
|
|
1320
|
+
</>,
|
|
1321
|
+
);
|
|
1301
1322
|
}
|
|
1302
1323
|
|
|
1303
1324
|
return <></>;
|
|
@@ -29,6 +29,7 @@ export interface MoreMenuItem {
|
|
|
29
29
|
description: string;
|
|
30
30
|
route: Route;
|
|
31
31
|
icon: IconProp;
|
|
32
|
+
iconColor?: string; // Tailwind color class like "bg-blue-500"
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export interface ComponentProps {
|
|
@@ -277,7 +278,7 @@ const Navbar: FunctionComponent<ComponentProps> = (
|
|
|
277
278
|
{isMoreMenuVisible &&
|
|
278
279
|
(props.moreMenuFooter ? (
|
|
279
280
|
<NavBarMenu footer={props.moreMenuFooter}>
|
|
280
|
-
{props.moreMenuItems.map((item:
|
|
281
|
+
{props.moreMenuItems.map((item: MoreMenuItem) => {
|
|
281
282
|
return (
|
|
282
283
|
<NavBarMenuItem
|
|
283
284
|
key={item.title}
|
|
@@ -285,6 +286,7 @@ const Navbar: FunctionComponent<ComponentProps> = (
|
|
|
285
286
|
description={item.description}
|
|
286
287
|
route={item.route}
|
|
287
288
|
icon={item.icon}
|
|
289
|
+
iconColor={item.iconColor}
|
|
288
290
|
onClick={forceHideMoreMenu}
|
|
289
291
|
/>
|
|
290
292
|
);
|
|
@@ -292,7 +294,7 @@ const Navbar: FunctionComponent<ComponentProps> = (
|
|
|
292
294
|
</NavBarMenu>
|
|
293
295
|
) : (
|
|
294
296
|
<NavBarMenu>
|
|
295
|
-
{props.moreMenuItems.map((item:
|
|
297
|
+
{props.moreMenuItems.map((item: MoreMenuItem) => {
|
|
296
298
|
return (
|
|
297
299
|
<NavBarMenuItem
|
|
298
300
|
key={item.title}
|
|
@@ -300,6 +302,7 @@ const Navbar: FunctionComponent<ComponentProps> = (
|
|
|
300
302
|
description={item.description}
|
|
301
303
|
route={item.route}
|
|
302
304
|
icon={item.icon}
|
|
305
|
+
iconColor={item.iconColor}
|
|
303
306
|
onClick={forceHideMoreMenu}
|
|
304
307
|
/>
|
|
305
308
|
);
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import Link from "../Link/Link";
|
|
2
|
+
import Icon from "../Icon/Icon";
|
|
3
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
2
4
|
import URL from "../../../Types/API/URL";
|
|
3
5
|
import React, { FunctionComponent, ReactElement } from "react";
|
|
4
6
|
|
|
7
|
+
export interface MenuSection {
|
|
8
|
+
title: string;
|
|
9
|
+
children: ReactElement | Array<ReactElement>;
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
export interface ComponentProps {
|
|
6
13
|
children: ReactElement | Array<ReactElement>;
|
|
7
14
|
footer?: {
|
|
@@ -11,7 +18,7 @@ export interface ComponentProps {
|
|
|
11
18
|
};
|
|
12
19
|
}
|
|
13
20
|
|
|
14
|
-
const
|
|
21
|
+
const NavBarMenu: FunctionComponent<ComponentProps> = (
|
|
15
22
|
props: ComponentProps,
|
|
16
23
|
): ReactElement => {
|
|
17
24
|
let children: Array<ReactElement>;
|
|
@@ -20,27 +27,54 @@ const NavBarItem: FunctionComponent<ComponentProps> = (
|
|
|
20
27
|
} else {
|
|
21
28
|
children = props.children;
|
|
22
29
|
}
|
|
30
|
+
|
|
31
|
+
// Calculate number of columns based on items count
|
|
32
|
+
const itemCount: number = children.length;
|
|
33
|
+
const columnClass: string =
|
|
34
|
+
itemCount <= 4
|
|
35
|
+
? "lg:grid-cols-2"
|
|
36
|
+
: itemCount <= 6
|
|
37
|
+
? "lg:grid-cols-3"
|
|
38
|
+
: "lg:grid-cols-3";
|
|
39
|
+
const maxWidthClass: string =
|
|
40
|
+
itemCount <= 4
|
|
41
|
+
? "lg:max-w-xl"
|
|
42
|
+
: itemCount <= 6
|
|
43
|
+
? "lg:max-w-2xl"
|
|
44
|
+
: "lg:max-w-3xl";
|
|
45
|
+
|
|
23
46
|
return (
|
|
24
|
-
<div
|
|
25
|
-
|
|
26
|
-
|
|
47
|
+
<div
|
|
48
|
+
className={`absolute left-1/2 z-10 mt-8 w-screen max-w-md -translate-x-1/2 transform px-2 sm:px-0 ${maxWidthClass}`}
|
|
49
|
+
>
|
|
50
|
+
<div className="overflow-hidden rounded-2xl shadow-xl ring-1 ring-black ring-opacity-5 bg-white">
|
|
51
|
+
{/* Menu Items */}
|
|
52
|
+
<div className={`relative grid gap-1 p-4 ${columnClass}`}>
|
|
27
53
|
{children}
|
|
28
54
|
</div>
|
|
55
|
+
|
|
56
|
+
{/* Footer */}
|
|
29
57
|
{props.footer && (
|
|
30
|
-
<div className="bg-gray-50
|
|
58
|
+
<div className="border-t border-gray-100 bg-gray-50 px-4 py-4">
|
|
31
59
|
<Link
|
|
32
60
|
to={props.footer.link}
|
|
33
61
|
openInNewTab={true}
|
|
34
|
-
className="-
|
|
62
|
+
className="group flex items-center gap-3 rounded-lg p-2.5 -m-2 transition-colors hover:bg-gray-100"
|
|
35
63
|
>
|
|
36
|
-
<
|
|
37
|
-
<
|
|
64
|
+
<div className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-gray-100 ring-1 ring-gray-200 group-hover:bg-gray-200 group-hover:ring-gray-300 transition-all">
|
|
65
|
+
<Icon
|
|
66
|
+
icon={IconProp.GitHub}
|
|
67
|
+
className="h-5 w-5 text-gray-700"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
<div className="flex-1 min-w-0 text-left">
|
|
71
|
+
<p className="text-sm font-medium text-gray-900">
|
|
38
72
|
{props.footer.title}
|
|
39
|
-
</
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</
|
|
73
|
+
</p>
|
|
74
|
+
<p className="text-xs text-gray-500">
|
|
75
|
+
{props.footer.description}
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
44
78
|
</Link>
|
|
45
79
|
</div>
|
|
46
80
|
)}
|
|
@@ -49,4 +83,4 @@ const NavBarItem: FunctionComponent<ComponentProps> = (
|
|
|
49
83
|
);
|
|
50
84
|
};
|
|
51
85
|
|
|
52
|
-
export default
|
|
86
|
+
export default NavBarMenu;
|